Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/objects/jira_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,30 @@ def relate_issues(self, inward_issue: str, outward_issue: str) -> bool:
LOGGER.error(ex)
return False

@ignore_exceptions(retry=3, retry_interval=1, raise_final_exception=True, logger=LOGGER)
def close_issue(self, issue_id: str) -> None:
"""
Closes a Jira issue by transitioning it to the "closed" state with a standard comment.

Args:
issue_id (str): The ID or key of the issue to close.
"""
try:
issue = self.get_issue_by_id_or_key(issue_id)
self.logger.info("Closing issue %s with transition 'closed'...", issue_id)

self.connection.transition_issue(
issue=issue.key,
transition="closed",
comment="Closed by [firewatch|https://github.com/CSPI-QE/firewatch].",
)

self.logger.info("Issue %s has been successfully closed.", issue_id)
except JIRAError as e:
self.logger.error("Failed to close issue %s. Jira error: %s", issue_id, e.text)
except Exception as ex:
self.logger.error("Unexpected error while closing issue %s: %s", issue_id, ex)

def project_exists(self, project_key: str) -> bool:
"""
Used to validate that the "project_key" exists in the Jira server.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import pytest
from unittest.mock import MagicMock
from jira.exceptions import JIRAError
from pytest import MonkeyPatch
from src.objects.jira_base import Jira


@pytest.fixture
def mock_jira(monkeypatch: MonkeyPatch):
"""
Provides a Jira instance with a mocked __init__ and mocked dependencies
for testing instance methods in isolation.
"""
# Patch Jira.__init__ to avoid file access and network connections
monkeypatch.setattr(Jira, "__init__", lambda self, jira_config_path=None: None)

# Create the instance and attach mock objects
jira = Jira()
jira.logger = MagicMock()
jira.get_issue_by_id_or_key = MagicMock()
jira.connection = MagicMock()
return jira


def test_close_issue_success(mock_jira: Jira):
"""
Tests the successful execution path of close_issue.
"""
issue_id = "TEST-123"
mock_issue = MagicMock()
mock_issue.key = issue_id
mock_jira.get_issue_by_id_or_key.return_value = mock_issue

# Call the method
mock_jira.close_issue(issue_id)

# Assert
# 1. Verify the method calls
mock_jira.get_issue_by_id_or_key.assert_called_once_with(issue_id)
mock_jira.connection.transition_issue.assert_called_once_with(
issue=issue_id,
transition="closed",
comment="Closed by [firewatch|https://github.com/CSPI-QE/firewatch].",
)

# 2. Verify ALL expected logs were made
mock_jira.logger.info.assert_any_call("Closing issue %s with transition 'closed'...", issue_id)
mock_jira.logger.info.assert_any_call("Issue %s has been successfully closed.", issue_id)

# 3. Verify no errors were logged
mock_jira.logger.error.assert_not_called()


def test_close_issue_jira_error(mock_jira: Jira):
"""
Tests that a JIRAError is caught and logged correctly.
"""
issue_id = "TEST-123"
error_text = "Transition not allowed"
mock_issue = MagicMock()
mock_issue.key = issue_id
mock_jira.get_issue_by_id_or_key.return_value = mock_issue

error = JIRAError(status_code=400, text=error_text)
mock_jira.connection.transition_issue.side_effect = error

# Call method
mock_jira.close_issue(issue_id)

# Assert
# Use assert_called_once_with for a stricter check than assert_any_call
mock_jira.logger.error.assert_called_once_with("Failed to close issue %s. Jira error: %s", issue_id, error_text)


def test_close_issue_unexpected_exception(mock_jira: Jira):
"""
Tests that a generic Exception is caught and logged correctly.
"""
# Arrange
issue_id = "TEST-123"
mock_issue = MagicMock()
mock_issue.key = issue_id
mock_jira.get_issue_by_id_or_key.return_value = mock_issue

# The actual exception object is what gets logged
exception_object = Exception("Unexpected failure")
mock_jira.connection.transition_issue.side_effect = exception_object

# Call method
mock_jira.close_issue(issue_id)

# Assert
# Assert that the error was logged exactly once, with the exception object itself
mock_jira.logger.error.assert_called_once_with(
"Unexpected error while closing issue %s: %s", issue_id, exception_object
)
Loading