diff --git a/jf_agent/git/bitbucket_server.py b/jf_agent/git/bitbucket_server.py index 805bc70..2a5d0f8 100644 --- a/jf_agent/git/bitbucket_server.py +++ b/jf_agent/git/bitbucket_server.py @@ -221,7 +221,11 @@ def get_repos(client, api_projects, include_repos, exclude_repos, redact_names_a for repo in project.repos.list(): if all(filt(repo) for filt in filters): api_repo = project.repos[repo['name']] - yield api_repo, _standardize_repo(api_project, api_repo, redact_names_and_urls) + try: + yield api_repo, _standardize_repo(api_project, api_repo, redact_names_and_urls) + except stashy.errors.NotFoundException as e: + logger.warning(f'WARN: Got NotFoundException fetching repo, skipping: {e}') + continue logger.info('✓') @@ -257,7 +261,11 @@ def get_commits_for_included_branches( ): for i, api_repo in enumerate(api_repos, start=1): with logging_helper.log_loop_iters('repo for branch commits', i, 1): - repo = api_repo.get() + try: + repo = api_repo.get() + except stashy.errors.NotFoundException as e: + logger.warning(f'WARN: Got NotFoundException fetching repo, skipping: {e}') + continue if verbose: logger.info(f"Beginning download of commits for repo {repo}") api_project = client.projects[repo['project']['key']] @@ -270,7 +278,7 @@ def get_commits_for_included_branches( # We are working with the BBS api object rather than a StandardizedRepository here, # so we can not use get_branches_for_standardized_repo as we do in bitbucket_cloud_adapter and gitlab_adapter. branches_to_process = [_get_default_branch_name(api_repo)] - additional_branch_patterns = included_branches.get(api_repo.get()['name']) + additional_branch_patterns = included_branches.get(repo['name']) if additional_branch_patterns: repo_branches = [b['displayId'] for b in api_repo.branches()] @@ -333,7 +341,11 @@ def get_pull_requests( ): for i, api_repo in enumerate(api_repos, start=1): with logging_helper.log_loop_iters('repo for pull requests', i, 1): - repo = api_repo.get() + try: + repo = api_repo.get() + except stashy.errors.NotFoundException as e: + logger.warning(f'WARN: Got NotFoundException fetching repo, skipping: {e}') + continue if verbose: logger.info(f"Beginning download of PRs for repo {repo}") api_project = client.projects[repo['project']['key']] @@ -369,7 +381,7 @@ def get_pull_requests( additions, deletions, changed_files = None, None, None except RetryError: logger.warning( - f"Could not retrieve diff data for PR {pr['id']} in repo {api_repo.get()['name']}" + f"Could not retrieve diff data for PR {pr['id']} in repo {repo['name']}" ) additions, deletions, changed_files = None, None, None except ChunkedEncodingError as e: diff --git a/tests/test_bitbucket_server.py b/tests/test_bitbucket_server.py index b6270c4..2caeecf 100644 --- a/tests/test_bitbucket_server.py +++ b/tests/test_bitbucket_server.py @@ -1,9 +1,10 @@ import json import unittest - from unittest import TestCase from unittest.mock import MagicMock +import stashy.errors + from jf_agent.git import bitbucket_server TEST_INPUT_FILE_PATH = f'tests/test_data/bitbucket_server/' @@ -324,6 +325,109 @@ def test_get_pull_requests(self): "resulting PR body does not match input", ) + def test_get_repos_skips_deleted_repo(self): + # Arrange + test_projects = _get_test_data('test_projects.json') + test_repos = _get_test_data('test_repos.json') + test_branches = _get_test_data('test_branches.json') + + mock_client = MagicMock() + mock_project = MagicMock() + + mock_deleted_repo = MagicMock() + mock_deleted_repo.get.side_effect = stashy.errors.NotFoundException(MagicMock()) + + mock_live_repo = MagicMock() + mock_live_repo.get.return_value = test_repos[0] + mock_live_repo.branches.return_value = test_branches + mock_live_repo.default_branch = MagicMock() + + mock_client.projects = {'test_project_key': mock_project} + mock_project.repos.list.return_value = [test_repos[0], test_repos[0]] + mock_project.repos.__getitem__.side_effect = [mock_deleted_repo, mock_live_repo] + + # Act — should not raise despite first repo being deleted + result_repos = list(bitbucket_server.get_repos(mock_client, test_projects, {}, {}, False)) + + # Assert — deleted repo skipped, live repo still returned + self.assertEqual(len(result_repos), 1) + self.assertEqual(result_repos[0][0], mock_live_repo) + + def test_get_branch_commits_skips_deleted_repo(self): + # Arrange + test_repos = _get_test_data('test_repos.json') + test_branches = _get_test_data('test_branches.json') + test_commits = _get_test_data('test_commits.json') + + mock_client = MagicMock() + mock_project = MagicMock() + + mock_deleted_repo = MagicMock() + mock_deleted_repo.get.side_effect = stashy.errors.NotFoundException(MagicMock()) + + mock_live_repo = MagicMock() + mock_live_repo.get.return_value = test_repos[0] + mock_live_repo.default_branch = test_branches[0] + + mock_client.projects = {'test_project_key': mock_project} + mock_project.repos = {'test_repo_name': mock_live_repo} + mock_live_repo.commits.return_value = test_commits + + test_git_instance_info = {'pull_from': '1900-07-23', 'repos_dict_v2': {}} + + # Act — should not raise despite the first repo being deleted + result_commits = list( + bitbucket_server.get_commits_for_included_branches( + mock_client, + [mock_deleted_repo, mock_live_repo], + {}, + False, + test_git_instance_info, + False, + False, + ) + ) + + # Assert — deleted repo produced nothing, live repo's commits still returned + self.assertEqual(len(result_commits), len(test_commits)) + + def test_get_pull_requests_skips_deleted_repo(self): + # Arrange + test_prs = _get_test_data('test_prs.json') + test_repos = _get_test_data('test_repos.json') + test_commits = _get_test_data('test_commits.json') + + mock_client = MagicMock() + mock_project = MagicMock() + + mock_deleted_repo = MagicMock() + mock_deleted_repo.get.side_effect = stashy.errors.NotFoundException(MagicMock()) + + mock_live_repo = MagicMock() + mock_live_repo.get.return_value = test_repos[0] + mock_live_repo.pull_requests.all.return_value = test_prs + + mock_client.projects = {'test_project_key': mock_project} + mock_project.repos = {'test_repo_name': mock_live_repo} + mock_live_repo.commits.return_value = test_commits + + test_git_instance_info = {'pull_from': '1900-07-23', 'repos_dict_v2': {}} + + # Act — should not raise despite the first repo being deleted + result_prs = list( + bitbucket_server.get_pull_requests( + mock_client, + [mock_deleted_repo, mock_live_repo], + False, + test_git_instance_info, + False, + False, + ) + ) + + # Assert — deleted repo produced nothing, live repo's PRs still returned + self.assertEqual(len(result_prs), len(test_prs)) + def _get_test_data(file_name): with open(f'{TEST_INPUT_FILE_PATH}{file_name}', 'r') as f: