# 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 task input fields."""

import re
from types import SimpleNamespace
from typing import Any, cast, override
from unittest import mock

from debusine.artifacts.models import (
    ArtifactCategory,
    CollectionCategory,
    DebianBinaryPackage,
    DebianUpload,
)
from debusine.client.models import RelationType
from debusine.tasks import (
    BaseTask,
    BaseTaskWithExecutor,
    ExtraRepositoryMixin,
    TaskConfigError,
)
from debusine.tasks.executors import ExecutorImageCategory
from debusine.tasks.inputs import (
    DataFieldInput,
    EnvironmentInput,
    ExtraRepositoriesInput,
    MultiInputList,
    OptionalEnvironmentInput,
    OptionalSingleInput,
    SingleInputList,
    SuiteArchiveInput,
    TaskInput,
    UploadArtifactsInput,
)
from debusine.tasks.models import (
    ExtraDebusineRepository,
    ExtraExternalRepository,
    ExtraRepository,
    LookupMultiple,
)
from debusine.tasks.server import (
    ArtifactInfo,
    CollectionInfo,
    MultipleArtifactInfo,
    TaskDatabaseInterface,
)
from debusine.tasks.tests.helper_mixin import FakeTaskDatabase
from debusine.test import TestCase


class ConcreteTaskInput(TaskInput[str]):
    """Concrete task input."""

    def __init__(self, value: str) -> None:
        """Resolve input field to a string constant."""
        self.value = value

    @override
    def resolve(
        self, task: BaseTask[Any, Any], task_database: TaskDatabaseInterface
    ) -> str:
        return self.value


class InputTests(TestCase):
    """Test case that can work as a container for task input fields."""

    inputs: dict[str, TaskInput[Any]] = {}

    @override
    def setUp(self) -> None:
        super().setUp()
        # Restore the fields to unresolved state
        for name in self.inputs:
            self.__dict__.pop(name, None)
        # Task database for resolving fields
        self.task_database = FakeTaskDatabase()

    def mock_task(
        self, task_class: type[BaseTask[Any, Any]] = BaseTask[Any, Any]
    ) -> BaseTask[Any, Any]:
        """Instantiate a mock task."""
        task = mock.MagicMock(spec=task_class)
        task.data = SimpleNamespace()
        return task


class TaskInputTest(InputTests):
    """Tests for :py:class:`TaskInput`."""

    field = ConcreteTaskInput("test")

    def test_input_name(self) -> None:
        self.assertEqual(self.inputs["field"].name, "field")

    def test_registered_in_inputs(self) -> None:
        self.assertIn("field", self.inputs)

    def test_inputs_subclassing(self) -> None:
        class A:
            inputs: dict[str, TaskInput[Any]] = {}
            field1 = ConcreteTaskInput("one")

        class B(A):
            field2 = ConcreteTaskInput("two")

        self.assertIn("field1", A.inputs)
        self.assertNotIn("field2", A.inputs)
        self.assertIn("field1", B.inputs)
        self.assertIn("field2", B.inputs)

    def test_not_resolved(self) -> None:
        with self.assertRaisesRegex(
            TaskConfigError, r"Cannot access unresolved input 'field'"
        ):
            self.field  # type: ignore[arg-type]

    def test_resolve(self) -> None:
        TaskInput.resolve_inputs(
            cast(BaseTask[Any, Any], self), cast(TaskDatabaseInterface, None)
        )
        self.assertEqual(self.field, "test")  # type: ignore[arg-type]


class ConcreteDataFieldInput(DataFieldInput[str]):
    """Concrete version of DataFieldInput."""

    @override
    def resolve(
        self, task: BaseTask[Any, Any], task_database: TaskDatabaseInterface
    ) -> str:
        raise NotImplementedError()


class DataFieldInputTest(InputTests):
    """Tests for :py:class:`DataFieldInput`."""

    inputs: dict[str, TaskInput[Any]] = {}
    field_default = ConcreteDataFieldInput()
    field_test = ConcreteDataFieldInput(field="test")
    field_test_nested = ConcreteDataFieldInput(field="test.nested")

    def test_field_default(self) -> None:
        self.assertEqual(
            self.inputs["field_default"].field,  # type: ignore[attr-defined]
            "field_default",
        )

    def test_resolve_field(self) -> None:
        data = SimpleNamespace()
        data.value = 1
        data.nested = SimpleNamespace()
        data.nested.value = 2

        for field, expected in (("value", 1), ("nested.value", 2)):
            with self.subTest(field=field):
                f = ConcreteDataFieldInput(field=field)
                self.assertEqual(f._resolve_field(data), expected)

    def test_resolve_field_fails(self) -> None:
        data = SimpleNamespace()
        data.nested = SimpleNamespace()

        for field in ("value", "nested.value"):
            with (
                self.subTest(Field=field),
                self.assertRaisesRegex(
                    TaskConfigError,
                    re.escape(f"Invalid input field: {field!r}"),
                ),
            ):
                f = ConcreteDataFieldInput(field=field)
                f._resolve_field(data)

    def test_get_lookup_single(self) -> None:
        field = ConcreteDataFieldInput(field="test")
        for value in None, 1, "foo":
            with (
                self.subTest(value=value),
                mock.patch.object(
                    field,
                    "_resolve_field",
                    return_value=value,
                ),
            ):
                self.assertEqual(field._get_lookup_single(mock.Mock()), value)

    def test_get_lookup_single_wrong_type(self) -> None:
        field = ConcreteDataFieldInput(field="test")
        value: Any
        for value in 3.14, [], {}:
            with (
                self.subTest(value=value),
                mock.patch.object(
                    field,
                    "_resolve_field",
                    return_value=value,
                ),
                self.assertRaises(AssertionError),
            ):
                field._get_lookup_single(mock.Mock())

    def test_get_lookup_multiple(self) -> None:
        field = ConcreteDataFieldInput(field="test")
        for value in (
            LookupMultiple((1,)),
            LookupMultiple((1, 2, "three")),
        ):
            with (
                self.subTest(value=repr(value)),
                mock.patch.object(
                    field,
                    "_resolve_field",
                    return_value=value,
                ),
            ):
                self.assertEqual(field._get_lookup_multiple(mock.Mock()), value)

    def test_get_lookup_multiple_wrong_type(self) -> None:
        field = ConcreteDataFieldInput(field="test")
        value: Any
        for value in None, 1, "foo", 3.14, [], {}:
            with (
                self.subTest(value=value),
                mock.patch.object(
                    field,
                    "_resolve_field",
                    return_value=value,
                ),
                self.assertRaises(AssertionError),
            ):
                field._get_lookup_multiple(mock.Mock())

    def test_get_lookup_list_None(self) -> None:
        field = ConcreteDataFieldInput(field="test")
        with mock.patch.object(
            field,
            "_resolve_field",
            return_value=None,
        ):
            self.assertEqual(field._get_lookup_list(mock.Mock()), [])

    def test_get_lookup_list(self) -> None:
        field = ConcreteDataFieldInput(field="test")
        for value in [], [1], [LookupMultiple((1,))]:
            with (
                self.subTest(value=repr(value)),
                mock.patch.object(
                    field,
                    "_resolve_field",
                    return_value=value,
                ),
            ):
                self.assertEqual(field._get_lookup_list(mock.Mock()), value)

    def test_get_lookup_list_wrong_type(self) -> None:
        field = ConcreteDataFieldInput(field="test")
        value: Any
        for value in 1, "foo", 3.14, {}:
            with (
                self.subTest(value=value),
                mock.patch.object(
                    field,
                    "_resolve_field",
                    return_value=value,
                ),
                self.assertRaises(AssertionError),
            ):
                field._get_lookup_list(mock.Mock())


class SingleInputListTests(InputTests):
    """Tests for :py:class:`SingleInputList`."""

    def test_empty_categories(self) -> None:
        field = SingleInputList()
        task = self.mock_task()
        with (
            mock.patch.object(
                field, "_get_lookup_list", return_value=[1, "two"]
            ),
            mock.patch.object(
                self.task_database,
                "lookup_single_artifact",
                side_effect=["foo", "bar"],
            ) as lookup_single_artifacts,
            mock.patch.object(field, "check_category") as check_category,
        ):
            self.assertEqual(
                field.resolve(task, self.task_database),
                ["foo", "bar"],
            )

        lookup_single_artifacts.assert_has_calls(
            [mock.call(1), mock.call("two")]
        )
        check_category.assert_has_calls([mock.call("foo"), mock.call("bar")])


class OptionalSingleInputTests(InputTests):
    """Tests for :py:class:`OptioanlSingleInput`."""

    def setUp(self) -> None:
        super().setUp()
        self.field = OptionalSingleInput()
        self.task = self.mock_task()
        self.enterContext(
            mock.patch.object(
                self.field, "_get_lookup_single", return_value="LOOKUP"
            )
        )
        self.check_category = self.enterContext(
            mock.patch.object(self.field, "check_category")
        )

    def resolve(self, result: str | None) -> str | None:
        with mock.patch.object(
            self.task_database,
            "lookup_single_artifact",
            return_value=result,
        ) as lookup_single_artifact:
            resolved = cast(
                str | None, self.field.resolve(self.task, self.task_database)
            )
        lookup_single_artifact.assert_called_with("LOOKUP")
        return resolved

    def test_none(self) -> None:
        self.assertEqual(self.resolve(None), None)
        self.check_category.assert_not_called()

    def test_has_value(self) -> None:
        self.assertEqual(self.resolve("foo"), "foo")
        self.check_category.assert_called_with("foo")


class MultiInputListTests(InputTests):
    """Tests for :py:class:`MultiInputList`."""

    def setUp(self) -> None:
        super().setUp()
        self.task = self.mock_task()
        self.check_category = self.enterContext(
            mock.patch("debusine.tasks.inputs.ArtifactInput.check_category")
        )

    def test_empty_categories(self) -> None:
        self.field = MultiInputList()
        with (
            mock.patch.object(
                self.field,
                "_get_lookup_list",
                return_value=[LookupMultiple((1,))],
            ),
            mock.patch.object(
                self.task_database,
                "lookup_multiple_artifacts",
                return_value="foo",
            ),
        ):
            self.assertEqual(
                self.field.resolve(self.task, self.task_database),
                ["foo"],
            )

        self.check_category.assert_not_called()

    def test_with_categories(self) -> None:
        self.field = MultiInputList(
            field="test", categories=[ArtifactCategory.BINARY_PACKAGE]
        )
        with (
            mock.patch.object(
                self.field,
                "_get_lookup_list",
                return_value=[LookupMultiple((1,)), LookupMultiple((2,))],
            ),
            mock.patch.object(
                self.task_database,
                "lookup_multiple_artifacts",
                side_effect=[["foo"], ["bar", "baz"]],
            ),
        ):
            self.assertEqual(
                self.field.resolve(self.task, self.task_database),
                [["foo"], ["bar", "baz"]],
            )

        self.check_category.assert_has_calls(
            [
                mock.call("foo", "test[0][0]"),
                mock.call("bar", "test[1][0]"),
                mock.call("baz", "test[1][1]"),
            ]
        )


class UploadArtifactsInputTests(InputTests):
    """Tests for :py:class:`UploadArtifactsInput`."""

    @override
    def setUp(self) -> None:
        super().setUp()
        self.task_database = FakeTaskDatabase()

    def resolve(
        self,
        value: list[ArtifactInfo],
        categories: list[ArtifactCategory] | None = None,
    ) -> MultipleArtifactInfo:
        field = UploadArtifactsInput(
            field="test",
            categories=categories or [ArtifactCategory.BINARY_PACKAGE],
        )
        task = mock.MagicMock()
        task.data = SimpleNamespace()
        lookup = mock.MagicMock(spec=LookupMultiple)
        with (
            mock.patch.object(field, "_resolve_field", return_value=lookup),
            mock.patch.object(
                self.task_database,
                "lookup_multiple_artifacts",
                return_value=MultipleArtifactInfo(tuple(value)),
            ),
        ):
            return field.resolve(task, self.task_database)

    def test_empty(self) -> None:
        self.assertEqual(self.resolve([]), MultipleArtifactInfo())

    def test_no_uploads(self) -> None:
        binary = ArtifactInfo(
            id=1,
            category=ArtifactCategory.BINARY_PACKAGE,
            data=DebianBinaryPackage(
                srcpkg_name="unsigned",
                srcpkg_version="1.0",
                deb_fields={
                    "Package": "hello",
                    "Version": "1.0-1",
                    "Architecture": "amd64",
                },
                deb_control_files=[],
            ),
        )
        self.assertEqual(
            self.resolve([binary]), MultipleArtifactInfo((binary,))
        )

    def test_expand_uploads(self) -> None:
        binary1 = ArtifactInfo(
            id=1,
            category=ArtifactCategory.BINARY_PACKAGE,
            data=DebianBinaryPackage(
                srcpkg_name="unsigned",
                srcpkg_version="1.0",
                deb_fields={
                    "Package": "hello",
                    "Version": "1.0-1",
                    "Architecture": "amd64",
                },
                deb_control_files=[],
            ),
        )
        binary2 = ArtifactInfo(
            id=2,
            category=ArtifactCategory.BINARY_PACKAGE,
            data=DebianBinaryPackage(
                srcpkg_name="unsigned-doc",
                srcpkg_version="1.0",
                deb_fields={
                    "Package": "unsigned",
                    "Version": "1.0-1",
                    "Architecture": "all",
                },
                deb_control_files=[],
            ),
        )
        upload = ArtifactInfo(
            id=3,
            category=ArtifactCategory.UPLOAD,
            data=DebianUpload(
                type="dpkg",
                changes_fields={
                    "Architecture": "all",
                    "Files": [{"name": "unsigned-doc_1.0_all.deb"}],
                },
            ),
        )
        self.task_database.relations = {
            (
                (upload.id,),
                ArtifactCategory.BINARY_PACKAGE,
                RelationType.EXTENDS,
            ): [binary2]
        }
        self.assertEqual(
            [x.id for x in self.resolve([binary1, upload])],
            [x.id for x in (binary1, binary2)],
        )


class EnvironmentInputTests(InputTests):
    """Tests for :py:class:`EnvironmentInput`."""

    @override
    def setUp(self) -> None:
        super().setUp()
        self.task_database = FakeTaskDatabase()
        self.task = mock.MagicMock(spec=BaseTaskWithExecutor)
        self.task.name = "TASKNAME"
        self.task.build_architecture = mock.Mock(return_value="BUILDARCH")
        self.task.backend = "BACKEND"
        self.task.data = SimpleNamespace()

    def assertResolves(self, field: EnvironmentInput) -> None:
        with mock.patch.object(field, "_resolve_field", return_value="LOOKUP"):
            self.assertEqual(
                field.resolve(
                    cast(BaseTask[Any, Any], self.task), self.task_database
                ),
                "foo",
            )

    def test_defaults(self) -> None:
        field = EnvironmentInput()
        with (
            mock.patch.object(
                self.task_database, "get_environment", return_value="foo"
            ) as get_environment,
            mock.patch.object(field, "check_category") as check_category,
        ):
            self.assertResolves(field)

        check_category.assert_called()
        get_environment.assert_called_once_with(
            "LOOKUP",
            architecture="BUILDARCH",
            backend="BACKEND",
            default_category=CollectionCategory.ENVIRONMENTS,
            image_category=None,
            try_variant="TASKNAME",
        )

    def test_set_backend_false(self) -> None:
        field = EnvironmentInput(set_backend=False)
        with (
            mock.patch.object(
                self.task_database, "get_environment", return_value="foo"
            ) as get_environment,
            mock.patch.object(field, "check_category") as check_category,
        ):
            self.assertResolves(field)

        check_category.assert_called()
        get_environment.assert_called_once_with(
            "LOOKUP",
            architecture="BUILDARCH",
            backend=None,
            default_category=CollectionCategory.ENVIRONMENTS,
            image_category=None,
            try_variant="TASKNAME",
        )

    def test_try_variant_failse(self) -> None:
        field = EnvironmentInput(try_variant=False)
        with (
            mock.patch.object(
                self.task_database, "get_environment", return_value="foo"
            ) as get_environment,
            mock.patch.object(field, "check_category") as check_category,
        ):
            self.assertResolves(field)

        check_category.assert_called()
        get_environment.assert_called_once_with(
            "LOOKUP",
            architecture="BUILDARCH",
            backend="BACKEND",
            default_category=CollectionCategory.ENVIRONMENTS,
            image_category=None,
            try_variant=None,
        )

    def test_image_category(self) -> None:
        field = EnvironmentInput(image_category=ExecutorImageCategory.TARBALL)
        with (
            mock.patch.object(
                self.task_database, "get_environment", return_value="foo"
            ) as get_environment,
            mock.patch.object(field, "check_category") as check_category,
        ):
            self.assertResolves(field)

        check_category.assert_called()
        get_environment.assert_called_once_with(
            "LOOKUP",
            architecture="BUILDARCH",
            backend="BACKEND",
            default_category=CollectionCategory.ENVIRONMENTS,
            image_category=ExecutorImageCategory.TARBALL,
            try_variant="TASKNAME",
        )

    def test_field_is_required(self) -> None:
        field = EnvironmentInput(field="test")
        self.task.data.test = None
        with self.assertRaises(AssertionError):
            (
                field.resolve(
                    cast(BaseTask[Any, Any], self.task), self.task_database
                ),
            )


class OptionalEnvironmentInputTests(InputTests):
    """Tests for :py:class:`OptionalEnvironmentInput`."""

    @override
    def setUp(self) -> None:
        super().setUp()
        self.task_database = FakeTaskDatabase()
        self.task = mock.MagicMock(spec=BaseTaskWithExecutor)
        self.task.name = "TASKNAME"
        self.task.build_architecture = mock.Mock(return_value="BUILDARCH")
        self.task.backend = "BACKEND"
        self.task.data = {}

    def test_defaults(self) -> None:
        field = OptionalEnvironmentInput()
        with (
            mock.patch.object(field, "_resolve_field", return_value="LOOKUP"),
            mock.patch.object(
                self.task_database, "get_environment", return_value="foo"
            ) as get_environment,
            mock.patch.object(field, "check_category") as check_category,
        ):
            self.assertEqual(
                field.resolve(
                    cast(BaseTask[Any, Any], self.task), self.task_database
                ),
                "foo",
            )

        check_category.assert_called()
        get_environment.assert_called_once_with(
            "LOOKUP",
            architecture="BUILDARCH",
            backend="BACKEND",
            default_category=CollectionCategory.ENVIRONMENTS,
            image_category=None,
            try_variant="TASKNAME",
        )

    def test_missing(self) -> None:
        field = OptionalEnvironmentInput()
        with (
            mock.patch.object(field, "_resolve_field", return_value=None),
            mock.patch.object(
                self.task_database, "get_environment", return_value="foo"
            ) as get_environment,
            mock.patch.object(field, "check_category") as check_category,
        ):
            self.assertIsNone(
                field.resolve(
                    cast(BaseTask[Any, Any], self.task), self.task_database
                )
            )

        check_category.assert_not_called()
        get_environment.assert_not_called()


class SuiteArchiveInputTests(InputTests):
    """Tests for :py:class:`SuiteArchiveInput`."""

    @override
    def setUp(self) -> None:
        super().setUp()
        self.task_database = FakeTaskDatabase()
        self.suite = CollectionInfo(
            id=1,
            scope_name="scope",
            workspace_name="workspace",
            category=CollectionCategory.SUITE,
            name="sid",
            data={},
        )
        self.archive = CollectionInfo(
            id=2,
            scope_name="scope",
            workspace_name="workspace",
            category=CollectionCategory.ARCHIVE,
            name="_",
            data={},
        )

    def resolve(
        self, suite: CollectionInfo, archive: CollectionInfo | None = None
    ) -> tuple[CollectionInfo, CollectionInfo | None]:
        field = SuiteArchiveInput(field="test")
        task = mock.MagicMock()
        task.data = SimpleNamespace()
        lookup = "LOOKUP"
        with (
            mock.patch.object(field, "_resolve_field", return_value=lookup),
            mock.patch.object(
                self.task_database,
                "lookup_single_collection",
                return_value=suite,
            ) as lookup_single_collection,
            mock.patch.object(
                self.task_database,
                "lookup_singleton_collection",
                return_value=archive,
            ) as lookup_singleton_collection,
        ):
            res = field.resolve(task, self.task_database)

        lookup_single_collection.assert_called_once_with(lookup)
        lookup_singleton_collection.assert_called_once_with(
            CollectionCategory.ARCHIVE
        )

        return res

    def test_suite_only(self) -> None:
        self.assertEqual(self.resolve(self.suite, None), (self.suite, None))

    def test_suite_archive(self) -> None:
        self.assertEqual(
            self.resolve(self.suite, self.archive), (self.suite, self.archive)
        )

    def test_wrong_suite_category(self) -> None:
        self.suite.category = CollectionCategory.TEST
        with self.assertRaisesRegex(
            TaskConfigError,
            rf"^test: unexpected collection category: "
            rf"'{CollectionCategory.TEST}'\. "
            rf"Expected: '{CollectionCategory.SUITE}'$",
        ):
            self.resolve(self.suite, self.archive), (self.suite, self.archive)


class ExtraRepositoriesInputTests(InputTests):
    """Tests for :py:class:`ExtraRepositoriesInput`."""

    def setUp(self) -> None:
        super().setUp()
        self.suite_trixie_local = CollectionInfo(
            id=1,
            scope_name="scope",
            workspace_name="workspace",
            category=CollectionCategory.SUITE,
            name="trixie-local",
            data={"components": ["main"]},
        )
        self.signing_key = "\n".join(
            (
                "-----BEGIN PGP PUBLIC KEY BLOCK-----",
                "",
                "ABCDEFGHI",
                "-----END PGP PUBLIC KEY BLOCK-----",
            )
        )
        self.task_database.single_collection_lookups[
            ("trixie-local@debian:suite", None)
        ] = self.suite_trixie_local
        self.task_database.suite_signing_keys[1] = self.signing_key
        self.task_database.settings["DEBUSINE_DEBIAN_ARCHIVE_PRIMARY_FQDN"] = (
            "deb.example.com"
        )
        self.task = self.mock_task(ExtraRepositoryMixin)  # type: ignore[type-abstract]

    def resolve(
        self, value: list[ExtraRepository] | None
    ) -> list[ExtraExternalRepository] | None:
        field = ExtraRepositoriesInput(field="test")
        with mock.patch.object(field, "_resolve_field", return_value=value):
            return field.resolve(self.task, self.task_database)

    def test_empty(self) -> None:
        self.assertEqual(self.resolve(None), None)
        self.assertEqual(self.resolve([]), [])

    def test_external(self) -> None:
        repo = ExtraExternalRepository(
            url="https://example.org", suite="sid", components=["main"]
        )
        self.assertEqual(self.resolve([repo]), [repo])

    def test_debusine(self) -> None:
        resolved = self.resolve(
            [
                ExtraDebusineRepository(
                    suite="trixie-local@debian:suite", components=["main"]
                )
            ]
        )
        self.assertEqual(
            resolved,
            [
                ExtraExternalRepository(
                    url="http://deb.example.com/scope/workspace",
                    suite="trixie-local",
                    components=["main"],
                    signing_key=self.signing_key,
                )
            ],
        )

    def test_missing_components(self) -> None:
        self.suite_trixie_local.data = {}

        with self.assertRaisesRegex(
            TaskConfigError,
            "'components' not set for trixie-local@debian:suite",
        ):
            self.resolve(
                [
                    ExtraDebusineRepository(
                        suite="trixie-local@debian:suite", components=None
                    )
                ]
            )

    def test_default_components(self) -> None:
        self.suite_trixie_local.data = {
            "components": ["main", "contrib", "non-free"]
        }

        resolved = self.resolve(
            [
                ExtraDebusineRepository(
                    suite="trixie-local@debian:suite", components=None
                )
            ]
        )

        self.assertEqual(
            resolved,
            [
                ExtraExternalRepository(
                    url="http://deb.example.com/scope/workspace",
                    suite="trixie-local",
                    components=["main", "contrib", "non-free"],
                    signing_key=self.signing_key,
                )
            ],
        )

    def test_explicit_components(self) -> None:
        self.suite_trixie_local.data = {
            "components": ["main", "contrib", "non-free"]
        }

        resolved = self.resolve(
            [
                ExtraDebusineRepository(
                    suite="trixie-local@debian:suite",
                    components=["main", "contrib"],
                )
            ]
        )

        self.assertEqual(
            resolved,
            [
                ExtraExternalRepository(
                    url="http://deb.example.com/scope/workspace",
                    suite="trixie-local",
                    components=["main", "contrib"],
                    signing_key=self.signing_key,
                )
            ],
        )
