class TestRepoHandler: def setup_class(self): self.repo = RepoHandler('fakerepo/doesnotexist', branch='awesomebot') @patch('requests.get') def test_get_issues(self, mock_get): # http://engineroom.trackmaven.com/blog/real-life-mocking/ mock_response = Mock() mock_response.json.return_value = [{ 'number': 42, 'state': 'open' }, { 'number': 55, 'state': 'open', 'pull_request': { 'diff_url': 'blah' } }] mock_get.return_value = mock_response assert self.repo.get_issues('open', 'Close?') == [42] assert self.repo.get_issues('open', 'Close?', exclude_pr=False) == [42, 55] def test_urls(self): assert self.repo._url_contents == 'https://api.github.com/repos/fakerepo/doesnotexist/contents/' assert self.repo._url_pull_requests == 'https://api.github.com/repos/fakerepo/doesnotexist/pulls' assert self.repo._headers is None
def test_get_config(file_contents): file_contents.return_value = """[tool.stsci-bot] changelog_check = false autoclose_stale_pull_request = false """ repo = RepoHandler('fake/fakebot') assert repo.get_config_value('changelog_check', True) is False assert repo.get_config_value('autoclose_stale_pull_request', True) is False assert repo.get_config_value('bizbaz', 42) == 42
class TestRealRepoHandler: def setup_class(self): # TODO: Use astropy/astropy-bot when #42 is merged. self.repo = RepoHandler('astropy/astropy-bot') def test_get_config(self): # These are set to False in YAML; defaults must not be used. with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always') do_changelog_check = self.repo.get_config_value( 'changelog_check', True) do_autoclose_pr = self.repo.get_config_value( 'autoclose_stale_pull_request', True) hit_api_limit = False other_warns = [] for ww in w: s = str(ww.message) if 'API limit' in s: hit_api_limit = True else: other_warns.append(s) if hit_api_limit: pytest.xfail(s) elif len(other_warns) > 0: raise AssertionError(os.linesep.join(other_warns)) else: assert not (do_changelog_check or do_autoclose_pr) @patch('requests.get') def test_get_file_contents(self, mock_get): content = b"I, for one, welcome our new robot overlords" mock_response = Mock() mock_response.ok = True mock_response.json.return_value = { 'content': base64.b64encode(content) } mock_get.return_value = mock_response result = self.repo.get_file_contents('some/file/here.txt') assert result == content.decode('utf-8') @patch('requests.get') def test_missing_file_contents(self, mock_get): mock_response = Mock() mock_response.ok = False mock_response.json.return_value = {'message': 'Not Found'} mock_get.return_value = mock_response with pytest.raises(FileNotFoundError): self.repo.get_file_contents('some/file/here.txt')
def process_issues(repository, installation): now = time.time() # Get issues labeled as 'Close?' repo = RepoHandler(repository, 'master', installation) issuelist = repo.get_issues('open', 'Close?') for n in issuelist: print(f'Checking {n}') yield f'Checking {n}' issue = IssueHandler(repository, n, installation) labeled_time = issue.get_label_added_date('Close?') if labeled_time is None: continue dt = now - labeled_time if current_app.stale_issue_close and dt > current_app.stale_issue_close_seconds: comment_ids = issue.find_comments('astropy-bot[bot]', filter_keep=is_close_epilogue) if len(comment_ids) == 0: print(f'-> CLOSING issue {n}') yield f'-> CLOSING issue {n}' issue.set_labels(['closed-by-bot']) issue.submit_comment(ISSUE_CLOSE_EPILOGUE) issue.close() else: print(f'-> Skipping issue {n} (already closed)') yield f'-> Skipping issue {n} (already closed)' elif dt > current_app.stale_issue_warn_seconds: comment_ids = issue.find_comments('astropy-bot[bot]', filter_keep=is_close_warning) if len(comment_ids) == 0: print(f'-> WARNING issue {n}') yield f'-> WARNING issue {n}' issue.submit_comment( ISSUE_CLOSE_WARNING.format( pasttime=naturaltime(dt), futuretime=naturaldelta( current_app.stale_issue_close_seconds - current_app.stale_issue_warn_seconds))) else: print(f'-> Skipping issue {n} (already warned)') yield f'-> Skipping issue {n} (already warned)' else: print(f'-> OK issue {n}') yield f'-> OK issue {n}' print('Finished checking for stale issues') yield 'Finished checking for stale issues'
def process_pull_requests(repository, installation): now = time.time() # Get issues labeled as 'Close?' repo = RepoHandler(repository, 'master', installation) pull_requests = repo.open_pull_requests() # User config enable_autoclose = repo.get_config_value('autoclose_stale_pull_request', True) for n in pull_requests: print(f'Checking {n}') pr = PullRequestHandler(repository, n, installation) if 'keep-open' in pr.labels: print('-> PROTECTED by label, skipping') continue commit_time = pr.last_commit_date dt = now - commit_time if current_app.stale_pull_requests_close and dt > current_app.stale_pull_requests_close_seconds: comment_ids = pr.find_comments('astropy-bot[bot]', filter_keep=is_close_epilogue) if not enable_autoclose: print(f'-> Skipping issue {n} (auto-close disabled)') elif len(comment_ids) == 0: print(f'-> CLOSING issue {n}') pr.submit_comment(PULL_REQUESTS_CLOSE_EPILOGUE) pr.close() else: print(f'-> Skipping issue {n} (already closed)') elif dt > current_app.stale_pull_requests_warn_seconds: comment_ids = pr.find_comments('astropy-bot[bot]', filter_keep=is_close_warning) if len(comment_ids) == 0: print(f'-> WARNING issue {n}') pr.submit_comment( PULL_REQUESTS_CLOSE_WARNING.format( pasttime=naturaldelta(dt), futuretime=naturaldelta( current_app.stale_pull_requests_close_seconds - current_app.stale_pull_requests_warn_seconds))) else: print(f'-> Skipping issue {n} (already warned)') else: print(f'-> OK issue {n}')
class TestRealRepoHandler: def setup_class(self): # TODO: Use astropy/astropy-bot when #42 is merged. self.repo = RepoHandler('pllim/astropy-bot', branch='changelog-onoff') def test_get_config(self): # These are set to False in YAML; defaults must not be used. with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always') do_changelog_check = self.repo.get_config_value( 'changelog_check', True) do_autoclose_pr = self.repo.get_config_value( 'autoclose_stale_pull_request', True) hit_api_limit = False if len(w) > 0: hit_api_limit = True if hit_api_limit: pytest.xfail(str(w[-1].message)) else: assert not (do_changelog_check or do_autoclose_pr)
def setup_class(self): # TODO: Use astropy/astropy-bot when #42 is merged. self.repo = RepoHandler('astropy/astropy-bot')
def setup_class(self): self.repo = RepoHandler('fakerepo/doesnotexist', branch='awesomebot')
def setup_class(self): # TODO: Use astropy/astropy-bot when #42 is merged. self.repo = RepoHandler('pllim/astropy-bot', branch='changelog-onoff')
def process_changelog_consistency(repository, number, installation): # TODO: cache handlers and invalidate the internal cache of the handlers on # certain events. pr_handler = PullRequestHandler(repository, number, installation) # Don't comment on closed PR if pr_handler.is_closed: return "Pull request already close, no need to check" repo_handler = RepoHandler(pr_handler.head_repo_name, pr_handler.head_branch, installation) # No-op if user so desires if not repo_handler.get_config_value('changelog_check', True): return "Repo owner does not want to check change log" # Find previous comments by this app comment_ids = pr_handler.find_comments( 'stsci-bot[bot]', filter_keep=is_changelog_message) if len(comment_ids) == 0: comment_id = None else: comment_id = comment_ids[-1] # Construct message message = CHANGELOG_PROLOGUE.format(user=pr_handler.user) approve = False # This is so that WIP and EXP shall not pass if 'Work in progress' in pr_handler.labels: message += CHANGELOG_NOT_DONE.format( status='a work in progress', is_done='is ready for review') elif 'Experimental' in pr_handler.labels: message += CHANGELOG_NOT_DONE.format( status='an experimental', is_done='discussion in settled') else: # Run checks check_milestone = repo_handler.get_config_value('check_milestone', True) issues = check_changelog_consistency(repo_handler, pr_handler, check_milestone=check_milestone) if len(issues) > 0: message += CHANGELOG_BAD_LIST for issue in issues: message += "* {0}\n".format(issue) message += CHANGELOG_BAD_EPILOGUE if len(issues) == 1: message = (message.replace('issues with', 'issue with') .replace('fix these', 'fix this')) else: message += CHANGELOG_GOOD approve = True message += CHANGELOG_EPILOGUE comment_url = pr_handler.submit_comment(message, comment_id=comment_id, return_url=True) if approve: pr_handler.set_status('success', 'All checks passed', 'stsci-bot', target_url=comment_url) else: pr_handler.set_status('failure', 'There were failures in checks - see ' 'comments by @stsci-bot above', 'stsci-bot', target_url=comment_url) return message