diff --git a/src/objects/jira_base.py b/src/objects/jira_base.py index 4c7c8e1..74f78c1 100644 --- a/src/objects/jira_base.py +++ b/src/objects/jira_base.py @@ -144,15 +144,25 @@ def create_issue( self.add_attachment_to_issue(issue=issue, attachment_path=file_path) if epic is not None: - epic_search = self.connection.search_issues(f"issue={epic}", maxResults=False) - if len(epic_search) == 1: - epic_id = epic_search[0].id - self.connection.add_issues_to_epic( - epic_id=epic_id, - issue_keys=issue.key, + try: + epic_search = self.connection.search_issues(f"issue={epic}", maxResults=False) + if len(epic_search) == 1: + epic_id = epic_search[0].id + self.connection.add_issues_to_epic( + epic_id=epic_id, + issue_keys=issue.key, + ) + else: + LOGGER.warning( + f"Epic {epic} not found or not accessible; issue {issue} created without epic link", + ) + except JIRAError as e: + LOGGER.warning( + f"Failed to link {issue} to epic {epic} " + f"(status {e.status_code}): {e.text}. " + f"Issue created without epic link.", + exc_info=True, ) - else: - LOGGER.error(f"Error finding Jira ID of epic {epic}") if assignee is not None: self.assign_issue(user_email=assignee, issue=issue.key) diff --git a/tests/unittests/objects/jira/test_jira.py b/tests/unittests/objects/jira/test_jira.py index c354dcf..ed98a21 100644 --- a/tests/unittests/objects/jira/test_jira.py +++ b/tests/unittests/objects/jira/test_jira.py @@ -288,6 +288,73 @@ def test_epic_lookup_uses_unquoted_issue_key_for_cloud_compatibility(self, mock_ issue_keys="TEST-1", ) + def test_epic_lookup_jira_error_does_not_crash(self, mock_jira): + mock_jira.connection.search_issues.side_effect = JIRAError( + "The requested epic cannot be viewed", + 404, + "https://jira/rest/agile/1.0/epic", + ) + + with patch("src.objects.jira_base.LOGGER") as mock_logger: + issue = mock_jira.create_issue( + project="TEST", + summary="Test issue", + description="Description", + issue_type="Bug", + epic="INTEROP-9999", + ) + + assert issue is not None + assert issue.key == "TEST-1" + mock_jira.connection.add_issues_to_epic.assert_not_called() + warning_msg = mock_logger.warning.call_args[0][0] + assert "INTEROP-9999" in warning_msg + assert "Issue created without epic link" in warning_msg + + def test_epic_lookup_empty_results_warns_without_error(self, mock_jira): + mock_jira.connection.search_issues.return_value = [] + + with patch("src.objects.jira_base.LOGGER") as mock_logger: + issue = mock_jira.create_issue( + project="TEST", + summary="Test issue", + description="Description", + issue_type="Bug", + epic="INTEROP-9999", + ) + + assert issue is not None + assert issue.key == "TEST-1" + mock_jira.connection.add_issues_to_epic.assert_not_called() + warning_msg = mock_logger.warning.call_args[0][0] + assert "INTEROP-9999" in warning_msg + + def test_add_issues_to_epic_jira_error_does_not_crash(self, mock_jira): + epic_issue = MagicMock() + epic_issue.id = "20001" + mock_jira.connection.search_issues.return_value = [epic_issue] + mock_jira.connection.add_issues_to_epic.side_effect = JIRAError( + "You do not have permission to link to this epic", + 403, + "https://jira/rest/agile/1.0/epic/20001/issue", + ) + + with patch("src.objects.jira_base.LOGGER") as mock_logger: + issue = mock_jira.create_issue( + project="TEST", + summary="Test issue", + description="Description", + issue_type="Bug", + epic="INTEROP-1234", + ) + + assert issue is not None + assert issue.key == "TEST-1" + mock_jira.connection.add_issues_to_epic.assert_called_once() + warning_msg = mock_logger.warning.call_args[0][0] + assert "INTEROP-1234" in warning_msg + assert "Issue created without epic link" in warning_msg + class TestJiraInitAuth: @pytest.fixture