# Copyright 2024 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 classes in models.py."""

from unittest import TestCase

import debusine.artifacts.models as data_models


class EnumTests(TestCase):
    """Tests for model enums."""

    def test_enums_str(self):
        """Test enum stringification."""
        for enum_cls in (
            data_models.DebianAutopkgtestResultStatus,
            data_models.DebianLintianSeverity,
        ):
            with self.subTest(enum_cls=enum_cls):
                for el in enum_cls:
                    with self.subTest(el=el):
                        self.assertEqual(str(el), el.value)


class DebianSourcePackageTests(TestCase):
    """Test the DebianSourcePackage model."""

    def test_construct(self):
        """Test model constructor."""
        data = data_models.DebianSourcePackage(
            **{
                "name": "test-name",
                "version": "test-version",
                "type": "dpkg",
                "dsc_fields": {"key": "val"},
            }
        )

        self.assertEqual(data.name, "test-name")
        self.assertEqual(data.version, "test-version")
        self.assertEqual(data.type, "dpkg")
        self.assertEqual(data.dsc_fields, {"key": "val"})

    def test_dsc_fields_missing(self):
        """Test checking that dsc_fields is present."""
        error_msg = r"dsc_fields\s+field required \(type=value_error\.missing\)"

        with self.assertRaisesRegex(Exception, error_msg):
            data_models.DebianSourcePackage(
                **{
                    "name": "test-name",
                    "version": "test-version",
                    "type": "dpkg",
                }
            )


class DebianUploadTests(TestCase):
    """Test the DebianUpload model."""

    def test_metadata_contains_debs_if_binary_requires_debs_for_binaries(self):
        """metadata_contains_debs_if_binary requires at least one .deb."""
        data = {
            "Architecture": "amd64",
            "Files": [{"name": "foo.dsc"}],
        }
        with self.assertRaisesRegex(
            ValueError,
            r"No \.debs found in \['foo\.dsc'\] which is expected to contain "
            r"binaries for amd64",
        ):
            data_models.DebianUpload.metadata_contains_debs_if_binary(data)

    def test_metadata_contains_debs_if_binary_ignores_source_uploads(self):
        """metadata_contains_debs_if_binary ignores source uploads."""
        data = {
            "Architecture": "source",
            "Files": [{"name": "foo.dsc"}],
        }
        self.assertEqual(
            data_models.DebianUpload.metadata_contains_debs_if_binary(data),
            data,
        )

    def test_metadata_contains_debs_if_binary_finds_debs_in_source_uploads(
        self,
    ):
        """metadata_contains_debs_if_binary finds debs in source uploads."""
        data = {
            "Architecture": "source",
            "Files": [{"name": "foo.deb"}],
        }
        with self.assertRaisesRegex(
            ValueError,
            r"Unexpected binary packages \['foo\.deb'\] found in source-only "
            r"upload\.",
        ):
            data_models.DebianUpload.metadata_contains_debs_if_binary(data)

    def test_metadata_contains_debs_if_binary_accepts_debs(self):
        """metadata_contains_debs_if_binary will accept one .deb."""
        data = {
            "Architecture": "amd64",
            "Files": [{"name": "foo_amd64.deb"}],
        }
        self.assertEqual(
            data,
            data_models.DebianUpload.metadata_contains_debs_if_binary(data),
        )

    def test_metadata_contains_debs_if_binary_accepts_debs_in_mixed(self):
        """metadata_contains_debs_if_binary will accept deb in mixed upload."""
        data = {
            "Architecture": "amd64 source",
            "Files": [{"name": "foo_amd64.deb"}],
        }
        self.assertEqual(
            data,
            data_models.DebianUpload.metadata_contains_debs_if_binary(data),
        )

    def test_metadata_contains_dsc_if_source_requires_1_dsc_for_source(self):
        """metadata_contains_dsc_if_source requires 1 .dsc."""
        data = {
            "Architecture": "source",
            "Files": [{"name": "foo.deb"}],
        }
        with self.assertRaisesRegex(
            ValueError,
            r"Expected to find one and only one source package in source "
            r"upload\. Found \[\].",
        ):
            data_models.DebianUpload.metadata_contains_dsc_if_source(data)

    def test_metadata_contains_dsc_if_source_rejects_2_dsc_for_source(self):
        """metadata_contains_dsc_if_source rejects 2 .dscs."""
        data = {
            "Architecture": "source",
            "Files": [{"name": "foo.dsc"}, {"name": "bar.dsc"}],
        }
        with self.assertRaisesRegex(
            ValueError,
            r"Expected to find one and only one source package in source "
            r"upload\. Found \['foo\.dsc', 'bar\.dsc'\]\.",
        ):
            data_models.DebianUpload.metadata_contains_dsc_if_source(data)

    def test_metadata_contains_dsc_if_source_ignores_binary_uploads(self):
        """metadata_contains_dsc_if_source ignores binary uploads."""
        data = {
            "Architecture": "amd64",
            "Files": [{"name": "foo.deb"}],
        }
        self.assertEqual(
            data_models.DebianUpload.metadata_contains_dsc_if_source(data), data
        )

    def test_metadata_contains_dsc_if_source_finds_dsc_in_bin_uploads(
        self,
    ):
        """metadata_contains_dsc_if_source finds dsc in binary uploads."""
        data = {
            "Architecture": "amd64",
            "Files": [{"name": "foo.dsc"}],
        }
        with self.assertRaisesRegex(
            ValueError,
            r"Binary uploads cannot contain source packages. "
            r"Found: \['foo\.dsc'\].",
        ):
            data_models.DebianUpload.metadata_contains_dsc_if_source(data)

    def test_metadata_contains_dsc_if_source_accepts_1_dsc(self):
        """metadata_contains_dsc_if_source will accept one .dsc."""
        data = {
            "Architecture": "source",
            "Files": [{"name": "foo.dsc"}],
        }
        self.assertEqual(
            data, data_models.DebianUpload.metadata_contains_dsc_if_source(data)
        )

    def test_metadata_contains_dsc_if_source_accepts_dsc_in_mixed(self):
        """metadata_contains_dsc_if_source accepts source in mixed upload."""
        data = {
            "Architecture": "amd64 source",
            "Files": [{"name": "foo.dsc"}],
        }
        self.assertEqual(
            data, data_models.DebianUpload.metadata_contains_dsc_if_source(data)
        )


class SigningResultTests(TestCase):
    """Test the SigningResult model."""

    def test_output_file(self) -> None:
        """A SigningResult may have an output_file and no error_message."""
        data_models.SigningResult(file="file", output_file="file.sig")

    def test_error_message(self) -> None:
        """A SigningResult may have an error_message and no output_file."""
        data_models.SigningResult(file="file", error_message="Boom")

    def test_both_output_file_and_error_message(self) -> None:
        """A SigningResult may not have both output_file and error_message."""
        with self.assertRaisesRegex(
            ValueError,
            "Exactly one of output_file and error_message must be set",
        ):
            data_models.SigningResult(
                file="file", output_file="file.sig", error_message="Boom"
            )

    def test_neither_output_file_nor_error_message(self) -> None:
        """A SigningResult must have either output_file or error_message."""
        with self.assertRaisesRegex(
            ValueError,
            "Exactly one of output_file and error_message must be set",
        ):
            data_models.SigningResult(file="file")
