async def test_issue_release_notes(doof, test_repo, mocker): """issue release notes should list closed issues over the last seven days""" org, repo = get_org_and_repo(test_repo.repo_url) channel_id = test_repo.channel_id pr = make_pr(123, "A PR") fetch_prs = mocker.patch('bot.fetch_pull_requests_since_date', return_value=[pr]) tups = [ (pr, [(make_issue(333), make_parsed_issue(333, False))]) ] fetch_issues = mocker.patch('bot.fetch_issues_for_pull_requests', return_value=async_gen_wrapper(tups)) notes = "some release notes" make_release_notes = mocker.patch('bot.make_issue_release_notes', return_value=notes) await doof.issue_release_notes(CommandArgs( repo_info=test_repo, channel_id=test_repo.channel_id, args=[], manager="me" )) assert doof.said("Release notes for issues closed by PRs", channel_id=channel_id) assert doof.said(notes, channel_id=channel_id) fetch_prs.assert_called_once_with( github_access_token=GITHUB_ACCESS, org=org, repo=repo, since=(now_in_utc() - timedelta(days=7)).date() ) fetch_issues.assert_called_once_with( github_access_token=GITHUB_ACCESS, pull_requests=[pr], ) make_release_notes.assert_called_once_with( tups, )
async def test_release_notes(doof, test_repo, test_repo_directory, mocker): """Doof should show release notes""" old_version = "0.1.2" update_version_mock = mocker.patch('bot.update_version', autospec=True, return_value=old_version) mocker.patch( 'bot.init_working_dir', side_effect=async_context_manager_yielder(test_repo_directory) ) notes = "some notes" create_release_notes_mock = mocker.async_patch('bot.create_release_notes', return_value=notes) any_new_commits_mock = mocker.async_patch('bot.any_new_commits', return_value=True) org, repo = get_org_and_repo(test_repo.repo_url) release_pr = ReleasePR('version', f'https://github.com/{org}/{repo}/pulls/123456', 'body') get_release_pr_mock = mocker.async_patch('bot.get_release_pr', return_value=release_pr) await doof.run_command( manager='mitodl_user', channel_id=test_repo.channel_id, words=['release', 'notes'], ) update_version_mock.assert_called_once_with("9.9.9", working_dir=test_repo_directory) create_release_notes_mock.assert_called_once_with( old_version, with_checkboxes=False, base_branch="master", root=test_repo_directory ) any_new_commits_mock.assert_called_once_with(old_version, base_branch="master", root=test_repo_directory) get_release_pr_mock.assert_called_once_with(github_access_token=GITHUB_ACCESS, org=org, repo=repo) assert doof.said("Release notes since {}".format(old_version)) assert doof.said(notes) assert doof.said(f"And also! There is a release already in progress: {release_pr.url}")
async def test_release(doof, repo_info, event_loop, mocker, command): """ Doof should do a release when asked """ version = '1.2.3' pr = ReleasePR( version=version, url='http://new.url', body='Release PR body', ) get_release_pr_mock = mocker.patch('bot.get_release_pr', autospec=True, side_effect=[None, pr, pr]) release_mock = mocker.patch('bot.release', autospec=True) wait_for_deploy_sync_mock = mocker.Mock() async def wait_for_deploy_fake(*args, **kwargs): """await cannot be used with mock objects""" wait_for_deploy_sync_mock(*args, **kwargs) mocker.patch('bot.wait_for_deploy', wait_for_deploy_fake) authors = ['author1', 'author2'] mocker.patch('bot.get_unchecked_authors', return_value=authors) wait_for_checkboxes_sync_mock = mocker.Mock() async def wait_for_checkboxes_fake(*args, **kwargs): """await cannot be used with mock objects""" wait_for_checkboxes_sync_mock(*args, **kwargs) mocker.patch('bot.wait_for_checkboxes', wait_for_checkboxes_fake) command_words = command.split() + [version] me = 'mitodl_user' await doof.run_command( manager=me, channel_id=repo_info.channel_id, words=command_words, loop=event_loop, ) org, repo = get_org_and_repo(repo_info.repo_url) get_release_pr_mock.assert_any_call(GITHUB_ACCESS, org, repo) release_mock.assert_called_once_with( github_access_token=GITHUB_ACCESS, repo_url=repo_info.repo_url, new_version=pr.version, ) wait_for_deploy_sync_mock.assert_called_once_with( github_access_token=GITHUB_ACCESS, repo_url=repo_info.repo_url, hash_url=repo_info.rc_hash_url, watch_branch='release-candidate', ) assert doof.said("Now deploying to RC...") assert doof.said("These people have commits in this release: {}".format(', '.join(authors))) wait_for_checkboxes_sync_mock.assert_called_once_with(GITHUB_ACCESS, org, repo) assert doof.said( "Release {version} is ready for the Merginator {name}".format( version=pr.version, name=format_user_id(me), ) )
async def test_get_org_and_repo(): """get_org_and_repo should get the GitHub organization and repo from the directory""" for git_url in [ "[email protected]:mitodl/release-script.git", "https://github.com/mitodl/release-script.git", ]: assert get_org_and_repo(git_url) == ("mitodl", "release-script")
async def test_webhook_finish_release(doof, event_loop, mocker): """ Finish the release """ wait_for_deploy_sync_mock = Mock() async def wait_for_deploy_fake(*args, **kwargs): """await cannot be used with mock objects""" wait_for_deploy_sync_mock(*args, **kwargs) pr_body = ReleasePR( version='version', url='url', body='body', ) get_release_pr_mock = mocker.patch('bot.get_release_pr', autospec=True, return_value=pr_body) finish_release_mock = mocker.patch('bot.finish_release', autospec=True) mocker.patch('bot.wait_for_deploy', wait_for_deploy_fake) await doof.handle_webhook( loop=event_loop, webhook_dict={ "token": "token", "callback_id": FINISH_RELEASE_ID, "channel": { "id": "doof" }, "user": { "id": "doofenshmirtz" }, "message_ts": "123.45", "original_message": { "text": "Doof's original text", } }, ) repo_url = WEB_TEST_REPO_INFO.repo_url hash_url = WEB_TEST_REPO_INFO.prod_hash_url org, repo = get_org_and_repo(repo_url) wait_for_deploy_sync_mock.assert_any_call( github_access_token=doof.github_access_token, hash_url=hash_url, repo_url=repo_url, watch_branch='release', ) get_release_pr_mock.assert_any_call( github_access_token=doof.github_access_token, org=org, repo=repo, ) finish_release_mock.assert_any_call( github_access_token=doof.github_access_token, repo_url=repo_url, version=pr_body.version, timezone=doof.timezone) assert doof.said("Merging...") assert not doof.said("Error")
async def test_release_notes_no_new_notes(doof, test_repo, test_repo_directory, mocker): """Doof should show that there are no new commits""" mocker.patch( 'bot.init_working_dir', side_effect=async_context_manager_yielder(test_repo_directory) ) old_version = "0.1.2" update_version_mock = mocker.patch('bot.update_version', autospec=True, return_value=old_version) notes = "no new commits" create_release_notes_mock = mocker.async_patch('bot.create_release_notes', return_value=notes) org, repo = get_org_and_repo(test_repo.repo_url) get_release_pr_mock = mocker.async_patch('bot.get_release_pr', return_value=None) any_new_commits_mock = mocker.async_patch('bot.any_new_commits', return_value=False) await doof.run_command( manager='mitodl_user', channel_id=test_repo.channel_id, words=['release', 'notes'], ) any_new_commits_mock.assert_called_once_with(old_version, base_branch="master", root=test_repo_directory) update_version_mock.assert_called_once_with("9.9.9", working_dir=test_repo_directory) create_release_notes_mock.assert_called_once_with( old_version, with_checkboxes=False, base_branch="master", root=test_repo_directory ) get_release_pr_mock.assert_called_once_with(github_access_token=GITHUB_ACCESS, org=org, repo=repo) assert doof.said("Release notes since {}".format(old_version)) assert not doof.said("Start a new release?")
async def wait_for_checkboxes(self, repo_info, manager): """ Poll the Release PR and wait until all checkboxes are checked off Args: repo_info (RepoInfo): Information for a repo manager (str): User id for the release manager """ channel_id = repo_info.channel_id await self.say( channel_id=channel_id, text= "Wait, wait. Time out. My evil plan for {project} isn't evil enough " "until all the checkboxes are checked...".format( project=repo_info.name, )) org, repo = get_org_and_repo(repo_info.repo_url) await wait_for_checkboxes(self.github_access_token, org, repo) pr = get_release_pr(self.github_access_token, org, repo) await self.say( channel_id=channel_id, text= "All checkboxes checked off. Release {version} is ready for the Merginator {name}!" .format(name=format_user_id(manager), version=pr.version), attachments=[{ "fallback": "Finish the release", "callback_id": FINISH_RELEASE_ID, "actions": [{ "name": "finish_release", "text": "Finish the release", "type": "button", }] }])
async def test_wait_for_deploy_rc(doof, test_repo, mocker): """Bot._wait_for_deploy_prod should wait until repo has been deployed to RC""" wait_for_deploy_mock = mocker.async_patch('bot.wait_for_deploy') get_unchecked_patch = mocker.async_patch( 'bot.get_unchecked_authors', return_value={'author1', 'author2', 'author3'} ) org, repo = get_org_and_repo(test_repo.repo_url) release_pr = ReleasePR('version', f'https://github.com/{org}/{repo}/pulls/123456', 'body') get_release_pr_mock = mocker.async_patch('bot.get_release_pr', return_value=release_pr) await doof._wait_for_deploy_rc(repo_info=test_repo) # pylint: disable=protected-access assert doof.said('These people have commits in this release') wait_for_deploy_mock.assert_called_once_with( github_access_token=GITHUB_ACCESS, repo_url=test_repo.repo_url, hash_url=test_repo.rc_hash_url, watch_branch='release-candidate' ) get_unchecked_patch.assert_called_once_with( github_access_token=GITHUB_ACCESS, org=org, repo=repo, ) get_release_pr_mock.assert_called_once_with( github_access_token=GITHUB_ACCESS, org=org, repo=repo, )
async def test_release_library(doof, library_test_repo, event_loop, mocker): """Do a library release""" version = '1.2.3' pr = ReleasePR( version=version, url='http://new.url', body='Release PR body', ) get_release_pr_mock = mocker.patch('bot.get_release_pr', autospec=True, side_effect=[None, pr, pr]) release_mock = mocker.patch('bot.release', autospec=True) finish_release_mock = mocker.patch('bot.finish_release', autospec=True) wait_for_travis_sync_mock = mocker.Mock() wait_for_travis_sync_mock.return_value = TRAVIS_SUCCESS async def wait_for_travis_fake(*args, **kwargs): """await cannot be used with mock objects""" return wait_for_travis_sync_mock(*args, **kwargs) mocker.patch('bot.wait_for_travis', wait_for_travis_fake) command_words = ['release', version] me = 'mitodl_user' await doof.run_command( manager=me, channel_id=library_test_repo.channel_id, words=command_words, loop=event_loop, ) org, repo = get_org_and_repo(library_test_repo.repo_url) release_mock.assert_called_once_with( github_access_token=GITHUB_ACCESS, repo_url=library_test_repo.repo_url, new_version=pr.version, ) wait_for_travis_sync_mock.assert_called_once_with( github_access_token=GITHUB_ACCESS, org=org, repo=repo, branch='release-candidate', ) get_release_pr_mock.assert_called_once_with( github_access_token=GITHUB_ACCESS, org=org, repo=repo, ) finish_release_mock.assert_called_once_with( github_access_token=GITHUB_ACCESS, repo_url=library_test_repo.repo_url, version=version, timezone=doof.timezone) assert doof.said( "My evil scheme {version} for {project} has been merged!".format( version=pr.version, project=library_test_repo.name, ))
async def test_release_library(doof, library_test_repo, mocker): """Do a library release""" version = '1.2.3' pr = ReleasePR( version=version, url='http://new.url', body='Release PR body', ) get_release_pr_mock = mocker.async_patch('bot.get_release_pr', side_effect=[None, pr, pr]) release_mock = mocker.async_patch('bot.release') finish_release_mock = mocker.async_patch('bot.finish_release') wait_for_travis_sync_mock = mocker.async_patch('bot.wait_for_travis', return_value=TRAVIS_SUCCESS) command_words = ['release', version] me = 'mitodl_user' await doof.run_command( manager=me, channel_id=library_test_repo.channel_id, words=command_words, ) org, repo = get_org_and_repo(library_test_repo.repo_url) release_mock.assert_called_once_with( github_access_token=GITHUB_ACCESS, repo_url=library_test_repo.repo_url, new_version=pr.version, ) wait_for_travis_sync_mock.assert_called_once_with( github_access_token=GITHUB_ACCESS, org=org, repo=repo, branch='release-candidate', ) get_release_pr_mock.assert_called_once_with( github_access_token=GITHUB_ACCESS, org=org, repo=repo, ) finish_release_mock.assert_called_once_with( github_access_token=GITHUB_ACCESS, repo_url=library_test_repo.repo_url, version=version, timezone=doof.timezone ) assert doof.said( f"Merging evil scheme {pr.version} for {library_test_repo.name}..." ) assert doof.said( f"My evil scheme {pr.version} for {library_test_repo.name} has been released! Waiting for Travis..." ) assert doof.said( "My evil scheme {version} for {project} has been merged!".format( version=pr.version, project=library_test_repo.name, ) )
def test_get_org_and_repo(): """get_org_and_repo should get the GitHub organization and repo from the directory""" # I would be fine with testing this on cwd but Travis has a really old version of git that doesn't support # get-url for git_url in [ "[email protected]:mitodl/release-script.git", "https://github.com/mitodl/release-script.git" ]: assert get_org_and_repo(git_url) == ("mitodl", "release-script")
def url_with_access_token(github_access_token, repo_url): """ Inserts the access token into the URL Returns:FILE_VERSION str: The URL formatted with an access token """ org, repo = get_org_and_repo(repo_url) return f"https://{github_access_token}@github.com/{org}/{repo}.git"
async def test_release(doof, test_repo, mocker, command): """ Doof should do a release when asked """ version = '1.2.3' pr = ReleasePR( version=version, url='http://new.url', body='Release PR body', ) get_release_pr_mock = mocker.async_patch('bot.get_release_pr', side_effect=[None, pr, pr]) release_mock = mocker.async_patch('bot.release') wait_for_deploy_sync_mock = mocker.async_patch('bot.wait_for_deploy') authors = {'author1', 'author2'} mocker.async_patch('bot.get_unchecked_authors', return_value=authors) wait_for_checkboxes_sync_mock = mocker.async_patch( 'bot.Bot.wait_for_checkboxes') command_words = command.split() + [version] me = 'mitodl_user' await doof.run_command( manager=me, channel_id=test_repo.channel_id, words=command_words, ) org, repo = get_org_and_repo(test_repo.repo_url) get_release_pr_mock.assert_any_call( github_access_token=GITHUB_ACCESS, org=org, repo=repo, ) release_mock.assert_called_once_with( github_access_token=GITHUB_ACCESS, repo_url=test_repo.repo_url, new_version=pr.version, ) wait_for_deploy_sync_mock.assert_called_once_with( github_access_token=GITHUB_ACCESS, repo_url=test_repo.repo_url, hash_url=test_repo.rc_hash_url, watch_branch='release-candidate', ) assert doof.said("Now deploying to RC...") for channel_id in [test_repo.channel_id, ANNOUNCEMENTS_CHANNEL.channel_id]: assert doof.said( "These people have commits in this release", channel_id=channel_id, ) for author in authors: assert doof.said(author, channel_id=channel_id) assert wait_for_checkboxes_sync_mock.called is True
async def release_command(self, command_args): """ Start a new release and wait for deployment Args: command_args (CommandArgs): The arguments for this command """ repo_info = command_args.repo_info version = command_args.args[0] repo_url = repo_info.repo_url channel_id = repo_info.channel_id org, repo = get_org_and_repo(repo_url) pr = get_release_pr(self.github_access_token, org, repo) if pr: raise ReleaseException( "A release is already in progress: {}".format(pr.url)) release( github_access_token=self.github_access_token, repo_url=repo_url, new_version=version, ) await self.say( channel_id=channel_id, text= "Behold, my new evil scheme - release {version} for {project}! Now deploying to RC..." .format( version=version, project=repo_info.name, ), ) await wait_for_deploy( github_access_token=self.github_access_token, repo_url=repo_url, hash_url=repo_info.rc_hash_url, watch_branch="release-candidate", ) unchecked_authors = get_unchecked_authors(self.github_access_token, org, repo) slack_usernames = self.translate_slack_usernames(unchecked_authors) pr = get_release_pr(self.github_access_token, org, repo) await self.say( channel_id=channel_id, text= "Release {version} for {project} was deployed! PR is up at {pr_url}." " These people have commits in this release: {authors}".format( version=version, authors=", ".join(slack_usernames), pr_url=pr.url, project=repo_info.name, )) await self.wait_for_checkboxes(repo_info, command_args.manager) command_args.loop.create_task(self.delay_message(repo_info))
def url_with_access_token(github_access_token, repo_url): """ Inserts the access token into the URL Returns: str: The URL formatted with an access token """ org, repo = get_org_and_repo(repo_url) return "https://{token}@github.com/{org}/{repo}.git".format( token=github_access_token, org=org, repo=repo, )
async def _web_application_release(self, command_args): """Do a web application release""" repo_info = command_args.repo_info version = command_args.args[0] repo_url = repo_info.repo_url channel_id = repo_info.channel_id org, repo = get_org_and_repo(repo_url) release( github_access_token=self.github_access_token, repo_url=repo_url, new_version=version, ) await self.say( channel_id=channel_id, text="Behold, my new evil scheme - release {version} for {project}! Now deploying to RC...".format( version=version, project=repo_info.name, ), ) await wait_for_deploy( github_access_token=self.github_access_token, repo_url=repo_url, hash_url=repo_info.rc_hash_url, watch_branch="release-candidate", ) unchecked_authors = get_unchecked_authors( github_access_token=self.github_access_token, org=org, repo=repo, ) slack_usernames = self.translate_slack_usernames(unchecked_authors) pr = get_release_pr( github_access_token=self.github_access_token, org=org, repo=repo, ) await self.say( channel_id=channel_id, text="Release {version} for {project} was deployed! PR is up at {pr_url}." " These people have commits in this release: {authors}".format( version=version, authors=", ".join(slack_usernames), pr_url=pr.url, project=repo_info.name, ) ) await self.wait_for_checkboxes(repo_info, command_args.manager) command_args.loop.create_task(self.delay_message(repo_info))
async def test_webhook_finish_release(doof, mocker): """ Finish the release """ pr_body = ReleasePR( version='version', url='url', body='body', ) get_release_pr_mock = mocker.async_patch('bot.get_release_pr', return_value=pr_body) finish_release_mock = mocker.async_patch('bot.finish_release') wait_for_deploy_prod_mock = mocker.async_patch('bot.Bot._wait_for_deploy_prod') await doof.handle_webhook( webhook_dict={ "token": "token", "callback_id": FINISH_RELEASE_ID, "channel": { "id": "doof" }, "user": { "id": "doofenshmirtz" }, "message_ts": "123.45", "original_message": { "text": "Doof's original text", } }, ) repo_url = WEB_TEST_REPO_INFO.repo_url org, repo = get_org_and_repo(repo_url) wait_for_deploy_prod_mock.assert_any_call( doof, repo_info=WEB_TEST_REPO_INFO, ) get_release_pr_mock.assert_any_call( github_access_token=doof.github_access_token, org=org, repo=repo, ) finish_release_mock.assert_any_call( github_access_token=doof.github_access_token, repo_url=repo_url, version=pr_body.version, timezone=doof.timezone ) assert doof.said("Merging...") assert not doof.said("Error")
async def test_finish_release(doof, test_repo, event_loop, mocker): """ Doof should finish a release when asked """ version = '1.2.3' pr = ReleasePR( version=version, url='http://new.url', body='Release PR body', ) get_release_pr_mock = mocker.patch('bot.get_release_pr', autospec=True, return_value=pr) finish_release_mock = mocker.patch('bot.finish_release', autospec=True) wait_for_deploy_sync_mock = mocker.Mock() async def wait_for_deploy_fake(*args, **kwargs): """await cannot be used with mock objects""" wait_for_deploy_sync_mock(*args, **kwargs) mocker.patch('bot.wait_for_deploy', wait_for_deploy_fake) await doof.run_command( manager='mitodl_user', channel_id=test_repo.channel_id, words=['finish', 'release'], loop=event_loop, ) org, repo = get_org_and_repo(test_repo.repo_url) get_release_pr_mock.assert_called_once_with( github_access_token=GITHUB_ACCESS, org=org, repo=repo, ) finish_release_mock.assert_called_once_with( github_access_token=GITHUB_ACCESS, repo_url=test_repo.repo_url, version=version, timezone=doof.timezone) assert doof.said('deploying to production...') wait_for_deploy_sync_mock.assert_called_once_with( github_access_token=GITHUB_ACCESS, repo_url=test_repo.repo_url, hash_url=test_repo.prod_hash_url, watch_branch='release', ) assert doof.said('has been released to production')
async def test_release_library_failure(doof, library_test_repo, event_loop, mocker): """If a library release fails we shouldn't merge it""" version = '1.2.3' pr = ReleasePR( version=version, url='http://new.url', body='Release PR body', ) mocker.patch('bot.get_release_pr', autospec=True, side_effect=[None, pr, pr]) release_mock = mocker.patch('bot.release', autospec=True) finish_release_mock = mocker.patch('bot.finish_release', autospec=True) wait_for_travis_sync_mock = mocker.Mock() wait_for_travis_sync_mock.return_value = TRAVIS_FAILURE async def wait_for_travis_fake(*args, **kwargs): """await cannot be used with mock objects""" return wait_for_travis_sync_mock(*args, **kwargs) mocker.patch('bot.wait_for_travis', wait_for_travis_fake) command_words = ['release', version] me = 'mitodl_user' await doof.run_command( manager=me, channel_id=library_test_repo.channel_id, words=command_words, loop=event_loop, ) org, repo = get_org_and_repo(library_test_repo.repo_url) release_mock.assert_called_once_with( github_access_token=GITHUB_ACCESS, repo_url=library_test_repo.repo_url, new_version=pr.version, ) wait_for_travis_sync_mock.assert_called_once_with( github_access_token=GITHUB_ACCESS, org=org, repo=repo, branch='release-candidate', ) assert finish_release_mock.call_count == 0 assert doof.said( "Uh-oh, it looks like, uh, coffee break's over. During the release Travis had a failure." )
async def test_finish_release_no_release(doof, repo_info, event_loop, mocker): """ If there's no release to finish doof should complain """ get_release_pr_mock = mocker.patch('bot.get_release_pr', autospec=True, return_value=None) with pytest.raises(ReleaseException) as ex: await doof.run_command( manager='mitodl_user', channel_id=repo_info.channel_id, words=['finish', 'release'], loop=event_loop, ) assert 'No release currently in progress' in ex.value.args[0] org, repo = get_org_and_repo(repo_info.repo_url) get_release_pr_mock.assert_called_once_with(GITHUB_ACCESS, org, repo)
async def finish_release(self, command_args): """ Merge the release candidate into the release branch, tag it, merge to master, and wait for deployment Args: command_args (CommandArgs): The arguments for this command """ repo_info = command_args.repo_info channel_id = repo_info.channel_id repo_url = repo_info.repo_url org, repo = get_org_and_repo(repo_url) pr = get_release_pr( github_access_token=self.github_access_token, org=org, repo=repo, ) if not pr: raise ReleaseException("No release currently in progress for {project}".format(project=repo_info.name)) version = pr.version finish_release( github_access_token=self.github_access_token, repo_url=repo_url, version=version, timezone=self.timezone ) await self.say( channel_id=channel_id, text="Merged evil scheme {version} for {project}! Now deploying to production...".format( version=version, project=repo_info.name, ), ) await wait_for_deploy( github_access_token=self.github_access_token, repo_url=repo_url, hash_url=repo_info.prod_hash_url, watch_branch="release", ) await self.say( channel_id=channel_id, text="My evil scheme {version} for {project} has been released to production. " "And by 'released', I mean completely...um...leased.".format( version=version, project=repo_info.name, ) )
async def test_finish_release_no_release(doof, test_repo, mocker): """ If there's no release to finish doof should complain """ get_release_pr_mock = mocker.async_patch('bot.get_release_pr', return_value=None) with pytest.raises(ReleaseException) as ex: await doof.run_command( manager='mitodl_user', channel_id=test_repo.channel_id, words=['finish', 'release'], ) assert 'No release currently in progress' in ex.value.args[0] org, repo = get_org_and_repo(test_repo.repo_url) get_release_pr_mock.assert_called_once_with( github_access_token=GITHUB_ACCESS, org=org, repo=repo, )
async def _library_release(self, command_args): """Do a library release""" repo_info = command_args.repo_info version = command_args.args[0] repo_url = repo_info.repo_url channel_id = repo_info.channel_id release( github_access_token=self.github_access_token, repo_url=repo_url, new_version=version, ) org, repo = get_org_and_repo(repo_url) status = await wait_for_travis( github_access_token=self.github_access_token, org=org, repo=repo, branch="release-candidate", ) if status != TRAVIS_SUCCESS: await self.say( channel_id=channel_id, text="Uh-oh, it looks like, uh, coffee break's over. During the release Travis had a {}.".format( status, ) ) return finish_release( github_access_token=self.github_access_token, repo_url=repo_url, version=version, timezone=self.timezone ) await self.say( channel_id=channel_id, text="My evil scheme {version} for {project} has been merged!".format( version=version, project=repo_info.name, ) )
async def message_if_unchecked(self, repo_info): """ Send a message next morning if any boxes are not yet checked off Args: repo_info (RepoInfo): Information for a repo """ org, repo = get_org_and_repo(repo_info.repo_url) unchecked_authors = get_unchecked_authors(self.github_access_token, org, repo) if unchecked_authors: slack_usernames = self.translate_slack_usernames(unchecked_authors) await self.say( channel_id=repo_info.channel_id, text="What an unexpected surprise! " "The following authors have not yet checked off their boxes for {project}: {names}" .format( names=", ".join(slack_usernames), project=repo_info.name, ))
async def test_release_notes_buttons(doof, test_repo, test_repo_directory, mocker): """Doof should show release notes and then offer buttons to start a release""" mocker.patch( 'bot.init_working_dir', side_effect=async_context_manager_yielder(test_repo_directory) ) old_version = "0.1.2" update_version_mock = mocker.patch('bot.update_version', autospec=True, return_value=old_version) notes = "some notes" create_release_notes_mock = mocker.async_patch('bot.create_release_notes', return_value=notes) org, repo = get_org_and_repo(test_repo.repo_url) get_release_pr_mock = mocker.async_patch('bot.get_release_pr', return_value=None) any_new_commits_mock = mocker.async_patch('bot.any_new_commits', return_value=True) await doof.run_command( manager='mitodl_user', channel_id=test_repo.channel_id, words=['release', 'notes'], ) any_new_commits_mock.assert_called_once_with(old_version, base_branch="master", root=test_repo_directory) update_version_mock.assert_called_once_with("9.9.9", working_dir=test_repo_directory) create_release_notes_mock.assert_called_once_with( old_version, with_checkboxes=False, base_branch="master", root=test_repo_directory ) get_release_pr_mock.assert_called_once_with(github_access_token=GITHUB_ACCESS, org=org, repo=repo) assert doof.said("Release notes since {}".format(old_version)) assert doof.said(notes) minor_version, patch_version = next_versions(old_version) assert doof.said("Start a new release?", attachments=[ { 'fallback': 'New release', 'callback_id': 'new_release', 'actions': [ {'name': 'minor_release', 'text': minor_version, 'value': minor_version, 'type': 'button'}, {'name': 'patch_release', 'text': patch_version, 'value': patch_version, 'type': 'button'}, {'name': 'cancel', 'text': "Dismiss", 'value': "cancel", 'type': 'button', "style": "danger"} ] } ]) assert not doof.said(f"And also! There is a release already in progress")
async def test_release_library_failure(doof, library_test_repo, mocker): """If a library release fails we shouldn't merge it""" version = '1.2.3' pr = ReleasePR( version=version, url='http://new.url', body='Release PR body', ) mocker.async_patch('bot.get_release_pr', side_effect=[None, pr, pr]) release_mock = mocker.async_patch('bot.release') finish_release_mock = mocker.async_patch('bot.finish_release') wait_for_travis_sync_mock = mocker.async_patch('bot.wait_for_travis', return_value=TRAVIS_FAILURE) command_words = ['release', version] me = 'mitodl_user' await doof.run_command( manager=me, channel_id=library_test_repo.channel_id, words=command_words, ) org, repo = get_org_and_repo(library_test_repo.repo_url) release_mock.assert_called_once_with( github_access_token=GITHUB_ACCESS, repo_url=library_test_repo.repo_url, new_version=pr.version, ) wait_for_travis_sync_mock.assert_called_once_with( github_access_token=GITHUB_ACCESS, org=org, repo=repo, branch='release-candidate', ) assert finish_release_mock.call_count == 0 assert doof.said( f"Merging evil scheme {pr.version} for {library_test_repo.name}..." ) assert doof.said( "Uh-oh, it looks like, uh, coffee break's over. During the release Travis had a failure." )
async def test_finish_release(doof, mocker, project_type): """ Doof should finish a release when asked """ version = '1.2.3' pr = ReleasePR( version=version, url='http://new.url', body='Release PR body', ) get_release_pr_mock = mocker.async_patch('bot.get_release_pr', return_value=pr) finish_release_mock = mocker.async_patch('bot.finish_release') wait_for_deploy_prod_mock = mocker.async_patch( 'bot.Bot._wait_for_deploy_prod') test_repo = LIBRARY_TEST_REPO_INFO if project_type == LIBRARY_TYPE else WEB_TEST_REPO_INFO await doof.run_command( manager='mitodl_user', channel_id=test_repo.channel_id, words=['finish', 'release'], ) org, repo = get_org_and_repo(test_repo.repo_url) get_release_pr_mock.assert_called_once_with( github_access_token=GITHUB_ACCESS, org=org, repo=repo, ) finish_release_mock.assert_called_once_with( github_access_token=GITHUB_ACCESS, repo_url=test_repo.repo_url, version=version, timezone=doof.timezone) assert doof.said(f"Merged evil scheme {version} for {test_repo.name}!") if project_type == WEB_APPLICATION_TYPE: assert doof.said('deploying to production...') wait_for_deploy_prod_mock.assert_called_once_with(doof, repo_info=test_repo)
async def test_webhook_start_release(doof, test_repo, mocker): """ Start a new release """ org, repo = get_org_and_repo(test_repo.repo_url) get_release_pr_mock = mocker.async_patch('bot.get_release_pr', return_value=None) release_mock = mocker.async_patch('bot.Bot._web_application_release') version = "3.4.5" await doof.handle_webhook(webhook_dict={ "token": "token", "callback_id": NEW_RELEASE_ID, "channel": { "id": "doof" }, "user": { "id": "doofenshmirtz" }, "message_ts": "123.45", "original_message": { "text": "Doof's original text", }, "actions": [{ "value": version, "name": "minor_release", }] }, ) assert doof.said(f"Starting release {version}...") assert release_mock.call_count == 1 assert release_mock.call_args[0][1].args == [version] assert not doof.said("Error") get_release_pr_mock.assert_called_once_with( github_access_token=GITHUB_ACCESS, org=org, repo=repo)
async def test_finish_release(doof, test_repo, mocker): """ Doof should finish a release when asked """ version = '1.2.3' pr = ReleasePR( version=version, url='http://new.url', body='Release PR body', ) get_release_pr_mock = mocker.async_patch('bot.get_release_pr', return_value=pr) finish_release_mock = mocker.async_patch('bot.finish_release') wait_for_deploy_prod_mock = mocker.async_patch('bot.Bot._wait_for_deploy_prod') await doof.run_command( manager='mitodl_user', channel_id=test_repo.channel_id, words=['finish', 'release'], ) org, repo = get_org_and_repo(test_repo.repo_url) get_release_pr_mock.assert_called_once_with( github_access_token=GITHUB_ACCESS, org=org, repo=repo, ) finish_release_mock.assert_called_once_with( github_access_token=GITHUB_ACCESS, repo_url=test_repo.repo_url, version=version, timezone=doof.timezone ) assert doof.said('deploying to production...') wait_for_deploy_prod_mock.assert_called_once_with( doof, repo_info=test_repo )
async def release_command(self, command_args): """ Start a new release and wait for deployment Args: command_args (CommandArgs): The arguments for this command """ repo_info = command_args.repo_info repo_url = repo_info.repo_url org, repo = get_org_and_repo(repo_url) pr = get_release_pr( github_access_token=self.github_access_token, org=org, repo=repo, ) if pr: raise ReleaseException("A release is already in progress: {}".format(pr.url)) if repo_info.project_type == LIBRARY_TYPE: await self._library_release(command_args) elif repo_info.project_type == WEB_APPLICATION_TYPE: await self._web_application_release(command_args) else: raise Exception("Configuration error: unknown project type {}".format(repo_info.project_type))