diff --git a/bert_e/tests/unit/test_jira.py b/bert_e/tests/unit/test_jira.py index 1f3a4963..b6bbb433 100644 --- a/bert_e/tests/unit/test_jira.py +++ b/bert_e/tests/unit/test_jira.py @@ -1,4 +1,4 @@ -"""Unit tests for check_fix_versions in jira.py. +"""Unit tests for check_fix_versions and _notify_pending_hotfix_if_needed. Rules: - Hotfix branch PRs require the exact 4-digit fix version in Jira @@ -11,9 +11,13 @@ """ import pytest from types import SimpleNamespace +from unittest.mock import MagicMock, patch from bert_e import exceptions -from bert_e.workflow.gitwaterflow.jira import check_fix_versions +from bert_e.workflow.gitwaterflow.jira import ( + check_fix_versions, + _notify_pending_hotfix_if_needed, +) def _make_issue(*version_names): @@ -143,3 +147,47 @@ def test_dev_branch_rejects_mismatch(): _make_job('4.3.19', '5.1.4'), _make_issue('4.3.18', '5.1.4'), ) + + +# --------------------------------------------------------------------------- +# _notify_pending_hotfix_if_needed — dedup behaviour +# --------------------------------------------------------------------------- + +def _make_notify_job(phantom_hotfix_versions=None): + """Build a minimal job for _notify_pending_hotfix_if_needed tests.""" + cascade = SimpleNamespace( + phantom_hotfix_versions=phantom_hotfix_versions or set(), + ) + settings = SimpleNamespace(robot='bert-e') + return SimpleNamespace( + git=SimpleNamespace(cascade=cascade), + settings=settings, + pull_request=MagicMock(), + active_options=[], + ) + + +@patch('bert_e.workflow.gitwaterflow.jira.notify_user') +@patch('bert_e.workflow.gitwaterflow.jira.find_comment', return_value=None) +def test_pending_hotfix_posts_when_not_yet_in_history(mock_find, mock_notify): + """Reminder is posted when no previous comment with that title exists.""" + job = _make_notify_job(phantom_hotfix_versions={'10.0.0.0'}) + issue = _make_issue('9.5.3', '10.0.0.0', '10.1.0') + _notify_pending_hotfix_if_needed(job, issue) + mock_notify.assert_called_once() + + +@patch('bert_e.workflow.gitwaterflow.jira.notify_user') +@patch('bert_e.workflow.gitwaterflow.jira.find_comment', + return_value=MagicMock()) +def test_pending_hotfix_skips_when_already_in_history(mock_find, mock_notify): + """Reminder is NOT posted when a previous comment with that title exists. + + This covers the active_options footer dedup fix: even if active_options + changed between runs (making the full text differ), the title-prefix + check prevents a second post. + """ + job = _make_notify_job(phantom_hotfix_versions={'10.0.0.0'}) + issue = _make_issue('9.5.3', '10.0.0.0', '10.1.0') + _notify_pending_hotfix_if_needed(job, issue) + mock_notify.assert_not_called() diff --git a/bert_e/workflow/gitwaterflow/jira.py b/bert_e/workflow/gitwaterflow/jira.py index 7db1255c..1662a29b 100644 --- a/bert_e/workflow/gitwaterflow/jira.py +++ b/bert_e/workflow/gitwaterflow/jira.py @@ -25,7 +25,7 @@ from bert_e import exceptions from bert_e.lib import jira as jira_api -from ..pr_utils import notify_user +from ..pr_utils import find_comment, notify_user from .utils import bypass_jira_check @@ -193,13 +193,23 @@ def check_fix_versions(job, issue): ) +_PENDING_HOTFIX_TITLE = '# Pending hotfix branch' + + def _notify_pending_hotfix_if_needed(job, issue): """Post a one-time reminder when the ticket carries a pre-GA hotfix fix version (X.Y.Z.0) so the developer knows to open a cherry-pick PR to the corresponding hotfix branch. This is an informational message: it is posted at most once per PR - (dont_repeat_if_in_history = NEVER_REPEAT) and never blocks the flow. + and never blocks the flow. + + The standard dont_repeat_if_in_history dedup uses the full rendered + message as the match key, which includes the active_options footer. + Because active_options can change between runs (e.g. when + create_integration_branches is added), the footer-sensitive match would + miss a comment posted in an earlier run and post again. We guard with + an explicit title-prefix check first, which is stable across runs. """ phantom_versions = job.git.cascade.phantom_hotfix_versions if not phantom_versions: @@ -208,6 +218,10 @@ def _notify_pending_hotfix_if_needed(job, issue): matching = sorted(phantom_versions & issue_versions) if not matching: return + # Stable dedup: any previous comment with this title means skip. + if find_comment(job.pull_request, job.settings.robot, + startswith=_PENDING_HOTFIX_TITLE): + return reminder = exceptions.PendingHotfixVersionReminder( issue=issue, hotfix_versions=matching,