From 0a641e708018c06fc5fca44766eb35a301c616ea Mon Sep 17 00:00:00 2001 From: Veranika Saltanava Date: Fri, 20 Mar 2026 12:35:56 +0100 Subject: [PATCH] fix: skip the org member check for bot users --- .gitignore | 2 ++ sync_jira_actions/sync_pr.py | 8 +++++-- sync_jira_actions/sync_to_jira.py | 9 +++++-- tests/test_sync_pr.py | 31 ++++++++++++++++++++++++ tests/test_sync_to_jira.py | 40 +++++++++++++++++++++++++++++++ 5 files changed, 86 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 046d2ff..22911e8 100644 --- a/.gitignore +++ b/.gitignore @@ -111,3 +111,5 @@ version.py # AI Agents .crush + +.history/ diff --git a/sync_jira_actions/sync_pr.py b/sync_jira_actions/sync_pr.py index 91334b7..639c85d 100755 --- a/sync_jira_actions/sync_pr.py +++ b/sync_jira_actions/sync_pr.py @@ -17,6 +17,7 @@ import os from github import Github +from github import GithubException from sync_issue import _create_jira_issue from sync_issue import _find_jira_issue @@ -30,8 +31,11 @@ def _is_collaborator_or_org_member(github, repo, username): return 'collaborator' if repo.owner.type == 'Organization': org = github.get_organization(repo.owner.login) - if org.has_in_members(github.get_user(username)): - return 'organization member' + try: + if org.has_in_members(github.get_user(username)): + return 'organization member' + except GithubException: + print(f'WARNING ⚠️ Could not check org membership for @{username}, treating as external contributor') return None diff --git a/sync_jira_actions/sync_to_jira.py b/sync_jira_actions/sync_to_jira.py index f865799..ee3b06e 100755 --- a/sync_jira_actions/sync_to_jira.py +++ b/sync_jira_actions/sync_to_jira.py @@ -18,6 +18,7 @@ import os from github import Github +from github import GithubException from jira import JIRA from sync_issue import handle_comment_created from sync_issue import handle_comment_deleted @@ -135,8 +136,12 @@ def main(): # noqa user_type = 'collaborator' elif repo.owner.type == 'Organization': org = github.get_organization(repo.owner.login) - if org.has_in_members(github.get_user(gh_issue['user']['login'])): - user_type = 'organization member' + try: + if org.has_in_members(github.get_user(gh_issue['user']['login'])): + user_type = 'organization member' + except GithubException: + username = gh_issue['user']['login'] + print(f'WARNING ⚠️ Could not check org membership for @{username},' ' treating as external contributor') if user_type: print(f'Skipping PR sync - author @{gh_issue["user"]["login"]} is a {user_type}') return diff --git a/tests/test_sync_pr.py b/tests/test_sync_pr.py index aa8757a..1f5d802 100644 --- a/tests/test_sync_pr.py +++ b/tests/test_sync_pr.py @@ -104,3 +104,34 @@ def test_sync_remain_prs_skips_collaborators(sync_pr_module, mock_sync_issue, mo # Verify no JIRA issue was created for collaborator PR assert mock_create_jira_issue.call_count == 0 assert mock_find_jira_issue.call_count == 0 + + +def test_is_collaborator_or_org_member_handles_bot_accounts(sync_pr_module): + """Test that _is_collaborator_or_org_member returns None for bot accounts""" + from github import GithubException + + mock_github_instance = MagicMock() + mock_repo = MagicMock() + mock_repo.has_in_collaborators.return_value = False + mock_repo.owner.type = 'Organization' + mock_repo.owner.login = 'fake-org' + + mock_github_instance.get_user.side_effect = GithubException(404, 'Not Found', None) + + result = sync_pr_module._is_collaborator_or_org_member(mock_github_instance, mock_repo, 'copilot[bot]') + + assert result is None + + +def test_sync_remain_prs_handles_bot_accounts(sync_pr_module, mock_sync_issue, mock_github): + """Test that PRs from bot accounts (e.g. copilot[bot]) don't crash the sync""" + mock_jira = MagicMock() + mock_create_jira_issue, mock_find_jira_issue = mock_sync_issue + + mock_github.get_pulls.return_value[0].user.login = 'copilot[bot]' + + with patch.object(sync_pr_module, '_is_collaborator_or_org_member', return_value=None): + sync_pr_module.sync_remain_prs(mock_jira) + + assert mock_find_jira_issue.call_count == 1 + assert mock_create_jira_issue.call_count == 1 diff --git a/tests/test_sync_to_jira.py b/tests/test_sync_to_jira.py index 37232b6..56c6f7c 100644 --- a/tests/test_sync_to_jira.py +++ b/tests/test_sync_to_jira.py @@ -60,3 +60,43 @@ def test_handle_issue_opened_event(mock_environment, sync_to_jira_main, monkeypa with patch('sync_jira_actions.sync_to_jira.handle_issue_opened') as mock_handle_issue_opened: sync_to_jira_main() mock_handle_issue_opened.assert_called_once() + + +def test_pr_opened_by_bot_account_does_not_crash(mock_environment, monkeypatch): + """Test that PRs opened by bot accounts (e.g. copilot[bot]) don't crash the sync""" + from github import GithubException + + event_data = { + 'action': 'opened', + 'pull_request': { + 'number': 42, + 'title': 'Bot PR', + 'body': 'Automated PR', + 'user': {'login': 'copilot[bot]'}, + 'html_url': 'https://github.com/espressif/esp-idf/pull/42', + 'state': 'open', + 'labels': [], + }, + } + mock_environment.write_text(json.dumps(event_data)) + monkeypatch.setenv('GITHUB_EVENT_NAME', 'pull_request') + monkeypatch.setenv('JIRA_PROJECT', 'TEST_PROJECT') + + mock_repo = MagicMock() + mock_repo.has_in_collaborators.return_value = False + mock_repo.owner.type = 'Organization' + mock_repo.owner.login = 'espressif' + + mock_github_instance = MagicMock() + mock_github_instance.get_repo.return_value = mock_repo + mock_github_instance.get_user.side_effect = GithubException(404, 'Not Found', None) + + with ( + patch('sync_jira_actions.sync_to_jira.Github', return_value=mock_github_instance), + patch('sync_jira_actions.sync_to_jira._JIRA'), + patch('sync_jira_actions.sync_to_jira.handle_issue_opened') as mock_handle_issue_opened, + ): + from sync_jira_actions.sync_to_jira import main + + main() + mock_handle_issue_opened.assert_called_once()