# Path: hotness/domain/package.py
# class Package:
#     """
#     Internal representation of package object.
# 
#     Attributes:
#         name (str): Name of the package
#         version (str): Latest version of the package
#         distro (str): Distribution assigned
#     """
# 
#     def __init__(self, name: str, version: str, distro: str) -> None:
#         """
#         Class constructor
#         """
#         self.name = name
#         self.version = version
#         self.distro = distro
# 
#     def to_dict(self) -> dict:
#         """
#         Convert object to dictionary.
# 
#         Return:
#             Dictionary created from package object.
#         """
#         return {"name": self.name, "version": self.version, "distro": self.distro}
# 
#     @classmethod
#     def from_dict(cls, package_dict: dict) -> object:
#         """
#         Convert dictionary to object.
# 
#         Params:
#             package_dict: Dictionary to create package from.
# 
#         Return:
#             Package object.
#         """
#         package = Package(
#             name=package_dict["name"],
#             version=package_dict["version"],
#             distro=package_dict["distro"],
#         )
#         return package
# 
#     def __eq__(self, other: object) -> bool:
#         """
#         Compare method for package object.
# 
#         Params:
#             other: object to compare with
# 
#         Return:
#             Compare result.
#         """
#         return self.to_dict() == other.to_dict()  # type: ignore
# 
# Path: hotness/exceptions/patcher_exception.py
# class PatcherException(BaseHotnessException):
#     """
#     Class representing patcher exception.
#     This exception is raised by patcher
#     when it encounters error.
# 
#     Attributes:
#         message: Error message.
#     """
# 
#     def __init__(self, message: str) -> None:
#         """
#         Class constructor.
#         """
#         self.message = message
#         super(PatcherException, self).__init__(self.message)
# 
# Path: hotness/patchers/bugzilla.py
# class Bugzilla(Patcher):
#     """
#     This class is a wrapper for https://bugzilla.redhat.com/
#     It submit patches to Bugzilla.
# 
#     Attributes:
#         bugzilla (bugzilla.Bugzilla): Bugzilla session
#     """
# 
#     def __init__(
#         self,
#         server_url: str,
#         api_key: str,
#     ) -> None:
#         """
#         Class constructor.
# 
#         It initializes bugzilla session using the provided credentials.
#         If the `api_key` is not provided it raises an `PatcherException`.
# 
#         Params:
#             server_url: URL of the bugzilla server
#             api_key: API key to use for authentication
# 
#         Raises:
#             PatcherException: When the bugzilla session can't be established
#         """
#         super(Bugzilla, self).__init__()
#         if api_key:
#             self.bugzilla = bugzilla.Bugzilla(
#                 url=server_url, api_key=api_key, cookiefile=None, tokenfile=None
#             )
#         else:
#             raise PatcherException("Authentication info not provided! Provide API key.")
#         self.bugzilla.bug_autorefresh = True
# 
#     def submit_patch(self, package: Package, patch: str, opts: dict) -> dict:
#         """
#         This method is inherited from `hotness.patchers.Patcher`.
# 
#         It attaches file to existing ticket in bugzilla.
# 
#         Params:
#             package: Package to create notification for
#             patch: Patch to attach, it needs to be converted to file first
#             opts: Additional options for bugzilla. Example:
#                 {
#                     "bz_id": 100, # Bugzilla ticket id, if provided the
#                                   # new message will be added as new comment to this ticket
#                     "patch_filename": "patch" # Name of the file, that should be attached
#                 }
# 
#         Returns:
#             Dictionary containing ticket bugzilla id
#             Example:
#             {
#                 "bz_id": 100
#             }
#         """
#         output = {}
#         bug_id = opts.get("bz_id", 0)
#         filename = opts.get("patch_filename", "")
#         if bug_id == 0 or not filename:
#             raise PatcherException(
#                 "Additional parameters are missing! "
#                 "Please provide `bz_id` and `patch_filename`."
#             )
# 
#         # Write file to temporary directory
#         # and stop bandit from complaining about a hardcoded temporary directory
#         # because it's needed for OpenShift
#         with TemporaryDirectory(prefix="thn-", dir="/var/tmp") as tmp:  # nosec
#             filepath = os.path.join(tmp, filename)
#             with open(filepath, "w") as f:
#                 f.write(patch)
# 
#             _logger.debug("Attaching file to bug %r" % bug_id)
#             description = "Update to {} (#{})".format(package.version, bug_id)
#             self.bugzilla.attachfile(bug_id, filepath, description, is_patch=True)
#             _logger.info("Attached file to bug: %r" % bug_id)
# 
#         output["bz_id"] = bug_id
#         return output


import os
import pytest
from unittest import mock
from hotness.domain import Package
from hotness.exceptions import PatcherException
from hotness.patchers import Bugzilla
        Assert that submit_patch works correctly.
        """
        # Prepare package
        package = Package(name="test", version="1.0", distro="Fedora")
        patch = "Fix everything"

        opts = {"bz_id": 100, "patch_filename": "patch"}

        mock_temp_dir.return_value.__enter__.return_value = tmpdir
        filepath = os.path.join(tmpdir, "patch")

        output = self.patcher.submit_patch(package, patch, opts)

        self.patcher.bugzilla.attachfile.assert_called_with(
            100, filepath, "Update to 1.0 (#100)", is_patch=True
        )

        assert output == {
            "bz_id": 100,
        }

    @pytest.mark.parametrize("opts", [{"bz_id": 100}, {"patch_filename": "patch"}, {}])
    def test_submit_patch_missing_opts(self, opts):
        """
        Assert that PatcherException is raised when the opts are missing.
        """
        # Prepare package
        package = Package(name="test", version="1.0", distro="Fedora")
        patch = "Fix everything"


# Path: hotness/domain/package.py
# class Package:
#     """
#     Internal representation of package object.
# 
#     Attributes:
#         name (str): Name of the package
#         version (str): Latest version of the package
#         distro (str): Distribution assigned
#     """
# 
#     def __init__(self, name: str, version: str, distro: str) -> None:
#         """
#         Class constructor
#         """
#         self.name = name
#         self.version = version
#         self.distro = distro
# 
#     def to_dict(self) -> dict:
#         """
#         Convert object to dictionary.
# 
#         Return:
#             Dictionary created from package object.
#         """
#         return {"name": self.name, "version": self.version, "distro": self.distro}
# 
#     @classmethod
#     def from_dict(cls, package_dict: dict) -> object:
#         """
#         Convert dictionary to object.
# 
#         Params:
#             package_dict: Dictionary to create package from.
# 
#         Return:
#             Package object.
#         """
#         package = Package(
#             name=package_dict["name"],
#             version=package_dict["version"],
#             distro=package_dict["distro"],
#         )
#         return package
# 
#     def __eq__(self, other: object) -> bool:
#         """
#         Compare method for package object.
# 
#         Params:
#             other: object to compare with
# 
#         Return:
#             Compare result.
#         """
#         return self.to_dict() == other.to_dict()  # type: ignore
# 
# Path: hotness/exceptions/patcher_exception.py
# class PatcherException(BaseHotnessException):
#     """
#     Class representing patcher exception.
#     This exception is raised by patcher
#     when it encounters error.
# 
#     Attributes:
#         message: Error message.
#     """
# 
#     def __init__(self, message: str) -> None:
#         """
#         Class constructor.
#         """
#         self.message = message
#         super(PatcherException, self).__init__(self.message)
# 
# Path: hotness/patchers/bugzilla.py
# class Bugzilla(Patcher):
#     """
#     This class is a wrapper for https://bugzilla.redhat.com/
#     It submit patches to Bugzilla.
# 
#     Attributes:
#         bugzilla (bugzilla.Bugzilla): Bugzilla session
#     """
# 
#     def __init__(
#         self,
#         server_url: str,
#         api_key: str,
#     ) -> None:
#         """
#         Class constructor.
# 
#         It initializes bugzilla session using the provided credentials.
#         If the `api_key` is not provided it raises an `PatcherException`.
# 
#         Params:
#             server_url: URL of the bugzilla server
#             api_key: API key to use for authentication
# 
#         Raises:
#             PatcherException: When the bugzilla session can't be established
#         """
#         super(Bugzilla, self).__init__()
#         if api_key:
#             self.bugzilla = bugzilla.Bugzilla(
#                 url=server_url, api_key=api_key, cookiefile=None, tokenfile=None
#             )
#         else:
#             raise PatcherException("Authentication info not provided! Provide API key.")
#         self.bugzilla.bug_autorefresh = True
# 
#     def submit_patch(self, package: Package, patch: str, opts: dict) -> dict:
#         """
#         This method is inherited from `hotness.patchers.Patcher`.
# 
#         It attaches file to existing ticket in bugzilla.
# 
#         Params:
#             package: Package to create notification for
#             patch: Patch to attach, it needs to be converted to file first
#             opts: Additional options for bugzilla. Example:
#                 {
#                     "bz_id": 100, # Bugzilla ticket id, if provided the
#                                   # new message will be added as new comment to this ticket
#                     "patch_filename": "patch" # Name of the file, that should be attached
#                 }
# 
#         Returns:
#             Dictionary containing ticket bugzilla id
#             Example:
#             {
#                 "bz_id": 100
#             }
#         """
#         output = {}
#         bug_id = opts.get("bz_id", 0)
#         filename = opts.get("patch_filename", "")
#         if bug_id == 0 or not filename:
#             raise PatcherException(
#                 "Additional parameters are missing! "
#                 "Please provide `bz_id` and `patch_filename`."
#             )
# 
#         # Write file to temporary directory
#         # and stop bandit from complaining about a hardcoded temporary directory
#         # because it's needed for OpenShift
#         with TemporaryDirectory(prefix="thn-", dir="/var/tmp") as tmp:  # nosec
#             filepath = os.path.join(tmp, filename)
#             with open(filepath, "w") as f:
#                 f.write(patch)
# 
#             _logger.debug("Attaching file to bug %r" % bug_id)
#             description = "Update to {} (#{})".format(package.version, bug_id)
#             self.bugzilla.attachfile(bug_id, filepath, description, is_patch=True)
#             _logger.info("Attached file to bug: %r" % bug_id)
# 
#         output["bz_id"] = bug_id
#         return output


# Path: tests/patchers/test_bugzilla.py
import os
import pytest
from unittest import mock
from hotness.domain import Package
from hotness.exceptions import PatcherException
from hotness.patchers import Bugzilla

        Assert that submit_patch works correctly.
        """
        # Prepare package
        package = Package(name="test", version="1.0", distro="Fedora")
        patch = "Fix everything"

        opts = {"bz_id": 100, "patch_filename": "patch"}

        mock_temp_dir.return_value.__enter__.return_value = tmpdir
        filepath = os.path.join(tmpdir, "patch")

        output = self.patcher.submit_patch(package, patch, opts)

        self.patcher.bugzilla.attachfile.assert_called_with(
            100, filepath, "Update to 1.0 (#100)", is_patch=True
        )

        assert output == {
            "bz_id": 100,
        }

    @pytest.mark.parametrize("opts", [{"bz_id": 100}, {"patch_filename": "patch"}, {}])
    def test_submit_patch_missing_opts(self, opts):
        """
        Assert that PatcherException is raised when the opts are missing.
        """
        # Prepare package
        package = Package(name="test", version="1.0", distro="Fedora")
        patch = "Fix everything"


        with pytest.raises(PatcherException) as exc: