def test_handles_request_becoming_wip_after_push(self, unused_time_sleep): api, mocklab = self.api, self.mocklab rewritten_sha = mocklab.rewritten_sha api.add_transition( PUT( '/projects/1234/merge_requests/54/merge', dict(sha=rewritten_sha, should_remove_source_branch=True, merge_when_pipeline_succeeds=True), ), Error( marge.gitlab.MethodNotAllowed( 405, {'message': '405 Method Not Allowed'})), from_state='passed', to_state='now_is_wip', ) api.add_merge_request( dict(mocklab.merge_request_info, work_in_progress=True), from_state='now_is_wip', ) message = 'The request was marked as WIP as I was processing it (maybe a WIP commit?)' with patch('marge.job.update_from_target_branch_and_push', side_effect=mocklab.push_updated): with mocklab.expected_failure(message): job = self.make_job() job.execute() assert api.state == 'now_is_wip' assert api.notes == ["I couldn't merge this branch: %s" % message]
def test_assumes_unresolved_discussions_on_merge_refusal(self, mocks): mocklab, api, job = mocks rewritten_sha = mocklab.rewritten_sha api.add_transition( PUT( '/projects/1234/merge_requests/{iid}/merge'.format( iid=mocklab.merge_request_info['iid']), dict(sha=rewritten_sha, remove_source_branch=True, merge_when_pipeline_succeeds=True), ), Error( marge.gitlab.MethodNotAllowed( 405, {'message': '405 Method Not Allowed'})), from_state='passed', to_state='unresolved_discussions', ) api.add_merge_request( dict(mocklab.merge_request_info), from_state='unresolved_discussions', ) message = ( "Gitlab refused to merge this request and I don't know why! " "Maybe you have unresolved discussions?") with mocklab.expected_failure(message): with patch.dict( mocklab.project_info, only_allow_merge_if_all_discussions_are_resolved=True): job.execute() assert api.state == 'unresolved_discussions' assert api.notes == ["I couldn't merge this branch: %s" % message]
def test_handles_races_for_merging(self, unused_time_sleep): api, mocklab = self.api, self.mocklab rewritten_sha = mocklab.rewritten_sha api.add_transition( PUT( '/projects/1234/merge_requests/54/merge', dict(sha=rewritten_sha, should_remove_source_branch=True, merge_when_pipeline_succeeds=True), ), Error( marge.gitlab.NotFound(404, {'message': '404 Branch Not Found'})), from_state='passed', to_state='someone_else_merged', ) api.add_merge_request( dict(mocklab.merge_request_info, state='merged'), from_state='someone_else_merged', ) with patch('marge.job.update_from_target_branch_and_push', side_effect=mocklab.push_updated): job = self.make_job() job.execute() assert api.state == 'someone_else_merged' assert api.notes == []
def test_discovers_if_someone_closed_the_merge_request(self, mocks): mocklab, api, job = mocks rewritten_sha = mocklab.rewritten_sha api.add_transition( PUT( '/projects/1234/merge_requests/{iid}/merge'.format( iid=mocklab.merge_request_info['iid']), dict(sha=rewritten_sha, remove_source_branch=True, merge_when_pipeline_succeeds=True), ), Error( marge.gitlab.MethodNotAllowed( 405, {'message': '405 Method Not Allowed'})), from_state='passed', to_state='oops_someone_closed_it', ) api.add_merge_request( dict(mocklab.merge_request_info, state='closed'), from_state='oops_someone_closed_it', ) message = 'Someone closed the merge request while I was attempting to merge it.' with mocklab.expected_failure(message): job.execute() assert api.state == 'oops_someone_closed_it' assert api.notes == ["I couldn't merge this branch: %s" % message]
def test_handles_request_becoming_wip_after_push(self, mocks): mocklab, api, job = mocks rewritten_sha = mocklab.rewritten_sha api.add_transition( PUT( '/projects/1234/merge_requests/{iid}/merge'.format( iid=mocklab.merge_request_info['iid']), dict(sha=rewritten_sha, remove_source_branch=True, merge_when_pipeline_succeeds=True), ), Error( marge.gitlab.MethodNotAllowed( 405, {'message': '405 Method Not Allowed'})), from_state='passed', to_state='now_is_wip', ) api.add_merge_request( dict(mocklab.merge_request_info, work_in_progress=True), from_state='now_is_wip', ) message = 'The request was marked as WIP as I was processing it (maybe a WIP commit?)' with mocklab.expected_failure(message): job.execute() assert api.state == 'now_is_wip' assert api.notes == ["I couldn't merge this branch: %s" % message]
def test_guesses_git_hook_error_on_merge_refusal(self, mocks): mocklab, api, job = mocks rewritten_sha = mocklab.rewritten_sha api.add_transition( PUT( '/projects/1234/merge_requests/{iid}/merge'.format( iid=mocklab.merge_request_info['iid']), dict(sha=rewritten_sha, remove_source_branch=True, merge_when_pipeline_succeeds=True), ), Error( marge.gitlab.MethodNotAllowed( 405, {'message': '405 Method Not Allowed'})), from_state='passed', to_state='rejected_by_git_hook', ) api.add_merge_request( dict(mocklab.merge_request_info, state='reopened'), from_state='rejected_by_git_hook', ) message = ( 'GitLab refused to merge this branch. I suspect that a Push Rule or a git-hook ' 'is rejecting my commits; maybe my email needs to be white-listed?' ) with mocklab.expected_failure(message): job.execute() assert api.state == 'rejected_by_git_hook' assert api.notes == ["I couldn't merge this branch: %s" % message]
def test_handles_races_for_merging(self, api, mocklab): rewritten_sha = mocklab.rewritten_sha api.add_transition( PUT( '/projects/1234/merge_requests/{iid}/merge'.format( iid=mocklab.merge_request_info['iid']), dict(sha=rewritten_sha, should_remove_source_branch=True, merge_when_pipeline_succeeds=True), ), Error( marge.gitlab.NotFound(404, {'message': '404 Branch Not Found'})), from_state='passed', to_state='someone_else_merged', ) api.add_merge_request( dict(mocklab.merge_request_info, state='merged'), from_state='someone_else_merged', ) with mocklab.branch_update(): job = self.make_job(api, mocklab) job.execute() assert api.state == 'someone_else_merged' assert api.notes == []
def test_tells_explicitly_that_gitlab_refused_to_merge( self, unused_time_sleep): api, mocklab = self.api, self.mocklab rewritten_sha = mocklab.rewritten_sha api.add_transition( PUT( '/projects/1234/merge_requests/54/merge', dict(sha=rewritten_sha, should_remove_source_branch=True, merge_when_pipeline_succeeds=True), ), Error( marge.gitlab.MethodNotAllowed( 405, {'message': '405 Method Not Allowed'})), from_state='passed', to_state='rejected_for_misterious_reasons', ) message = "Gitlab refused to merge this request and I don't know why!" with patch('marge.job.update_from_target_branch_and_push', side_effect=mocklab.push_updated): with mocklab.expected_failure(message): job = self.make_job() job.execute() assert api.state == 'rejected_for_misterious_reasons' assert api.notes == ["I couldn't merge this branch: %s" % message]
def test_fails_if_branch_is_protected(self, mocks_factory, fusion): def reject_push(*_args, **_kwargs): raise marge.git.GitError() mocklab, api, job = mocks_factory(on_push=reject_push) api.add_transition( GET( '/projects/{source_project_id}/repository/branches/useless_new_feature'.format( source_project_id=mocklab.merge_request_info['source_project_id'], ), ), Ok(_branch('useless_new_feature', protected=True)), from_state='initial', to_state='protected' ) if fusion is Fusion.gitlab_rebase: api.add_transition( PUT( '/projects/{project_id}/merge_requests/{iid}/rebase'.format( project_id=mocklab.merge_request_info['project_id'], iid=mocklab.merge_request_info['iid'], ), ), Error(marge.gitlab.MethodNotAllowed(405, {'message': '405 Method Not Allowed'})), from_state='initial', ) with mocklab.expected_failure("Sorry, I can't modify protected branches!"): job.execute() assert api.state == 'protected'
def test_discovers_if_someone_closed_the_merge_request( self, unused_time_sleep): api, mocklab = self.api, self.mocklab rewritten_sha = mocklab.rewritten_sha api.add_transition( PUT( '/projects/1234/merge_requests/54/merge', dict(sha=rewritten_sha, should_remove_source_branch=True, merge_when_pipeline_succeeds=True), ), Error( marge.gitlab.MethodNotAllowed( 405, {'message': '405 Method Not Allowed'})), from_state='passed', to_state='oops_someone_closed_it', ) api.add_merge_request( dict(mocklab.merge_request_info, state='closed'), from_state='oops_someone_closed_it', ) message = 'Someone closed the merge request while I was attempting to merge it.' with patch('marge.job.update_from_target_branch_and_push', side_effect=mocklab.push_updated): with mocklab.expected_failure(message): job = self.make_job() job.execute() assert api.state == 'oops_someone_closed_it' assert api.notes == ["I couldn't merge this branch: %s" % message]
def test_guesses_git_hook_error_on_merge_refusal(self, unused_time_sleep): api, mocklab = self.api, self.mocklab rewritten_sha = mocklab.rewritten_sha api.add_transition( PUT( '/projects/1234/merge_requests/54/merge', dict(sha=rewritten_sha, should_remove_source_branch=True, merge_when_pipeline_succeeds=True), ), Error( marge.gitlab.MethodNotAllowed( 405, {'message': '405 Method Not Allowed'})), from_state='passed', to_state='rejected_by_git_hook', ) api.add_merge_request( dict(mocklab.merge_request_info, state='reopened'), from_state='rejected_by_git_hook', ) message = ( 'GitLab refused to merge this branch. I suspect that a Push Rule or a git-hook ' 'is rejecting my commits; maybe my email needs to be white-listed?' ) with patch('marge.job.update_from_target_branch_and_push', side_effect=mocklab.push_updated): with mocklab.expected_failure(message): job = self.make_job() job.execute() assert api.state == 'rejected_by_git_hook' assert api.notes == ["I couldn't merge this branch: %s" % message]
def test_succeeds_second_time_if_master_moved(self, unused_time_sleep): api, mocklab = self.api, self.mocklab moved_master_sha = 'fafafa' first_rewritten_sha = '1o1' api.add_pipelines( mocklab.project_info['id'], _pipeline(sha1=first_rewritten_sha, status='success'), from_state=['pushed_but_master_moved', 'merged_rejected'], ) api.add_transition( PUT( '/projects/1234/merge_requests/54/merge', dict( sha=first_rewritten_sha, should_remove_source_branch=True, merge_when_pipeline_succeeds=True, ), ), Error(marge.gitlab.NotAcceptable()), from_state='pushed_but_master_moved', to_state='merge_rejected', ) api.add_transition( GET('/projects/1234/repository/branches/useless_new_feature'), Ok({ 'commit': _commit(commit_id=first_rewritten_sha, status='success') }), from_state='pushed_but_master_moved') api.add_transition(GET('/projects/1234/repository/branches/master'), Ok({ 'commit': _commit(commit_id=moved_master_sha, status='success') }), from_state='merge_rejected') def push_effects(): assert api.state == 'initial' api.state = 'pushed_but_master_moved' yield mocklab.initial_master_sha, 'f00ba4', first_rewritten_sha assert api.state == 'merge_rejected' api.state = 'pushed' yield moved_master_sha, 'deadbeef', mocklab.rewritten_sha with patch('marge.job.update_from_target_branch_and_push', side_effect=push_effects()): job = self.make_job( marge.job.MergeJobOptions.default(add_tested=True, add_reviewers=False)) job.execute() assert api.state == 'merged' assert api.notes == [ "My job would be easier if people didn't jump the queue and push directly... *sigh*", ]
def __init__(self, gitlab_url=None, fork=False, merge_request_options=None): super().__init__(gitlab_url, fork=fork, merge_request_options=merge_request_options) api = self.api self.rewritten_sha = rewritten_sha = 'af7a' api.add_pipelines( self.merge_request_info['source_project_id'], _pipeline(sha1=rewritten_sha, status='running', ref=self.merge_request_info['source_branch']), from_state='pushed', to_state='passed', ) api.add_pipelines( self.merge_request_info['source_project_id'], _pipeline(sha1=rewritten_sha, status='success', ref=self.merge_request_info['source_branch']), from_state=['passed', 'merged'], ) source_project_id = self.merge_request_info['source_project_id'] api.add_transition( GET( '/projects/{}/repository/branches/{}'.format( source_project_id, self.merge_request_info['source_branch'], ), ), Ok({'commit': _commit(commit_id=rewritten_sha, status='running')}), from_state='pushed', ) api.add_transition( GET( '/projects/{}/repository/branches/{}'.format( source_project_id, self.merge_request_info['source_branch'], ), ), Ok({'commit': _commit(commit_id=rewritten_sha, status='success')}), from_state='passed' ) api.add_transition( PUT( '/projects/1234/merge_requests/{iid}/merge'.format(iid=self.merge_request_info['iid']), dict(sha=rewritten_sha, should_remove_source_branch=True, merge_when_pipeline_succeeds=True), ), Ok({}), from_state=['passed', 'skipped'], to_state='merged', ) api.add_merge_request(dict(self.merge_request_info, state='merged'), from_state='merged') api.add_transition( GET('/projects/1234/repository/branches/{}'.format(self.merge_request_info['target_branch'])), Ok({'commit': {'id': self.rewritten_sha}}), from_state='merged' ) api.expected_note( self.merge_request_info, "My job would be easier if people didn't jump the queue and push directly... *sigh*", from_state=['pushed_but_master_moved', 'merge_rejected'], ) api.expected_note( self.merge_request_info, "I'm broken on the inside, please somebody fix me... :cry:" )
def test_accept_merge_when_pipeline_succeeds(self): self._load(dict(INFO, sha='badc0de')) self.merge_request.accept(merge_when_pipeline_succeeds=False) self.api.call.assert_called_once_with( PUT( '/projects/1234/merge_requests/54/merge', dict( merge_when_pipeline_succeeds=False, should_remove_source_branch=False, sha='badc0de', )))
def test_accept(self): self._load(dict(INFO, sha='badc0de')) for boolean in (True, False): self.merge_request.accept(remove_branch=boolean) self.api.call.assert_called_once_with( PUT('/projects/1234/merge_requests/54/merge', dict( should_remove_source_branch=boolean, sha='badc0de', ))) self.api.call.reset_mock() self.merge_request.accept(sha='g00dc0de') self.api.call.assert_called_once_with( PUT('/projects/1234/merge_requests/54/merge', dict( should_remove_source_branch=False, sha='g00dc0de', )))
def __init__(self, gitlab_url=None): super().__init__(gitlab_url) api = self.api self.rewritten_sha = rewritten_sha = 'af7a' api.add_pipelines( self.project_info['id'], _pipeline(sha1=rewritten_sha, status='running'), from_state='pushed', to_state='passed', ) api.add_pipelines( self.project_info['id'], _pipeline(sha1=rewritten_sha, status='success'), from_state=['passed', 'merged'], ) api.add_transition( GET('/projects/1234/repository/branches/useless_new_feature'), Ok({'commit': _commit(commit_id=rewritten_sha, status='running')}), from_state='pushed', ) api.add_transition( GET('/projects/1234/repository/branches/useless_new_feature'), Ok({'commit': _commit(commit_id=rewritten_sha, status='success')}), from_state='passed') api.add_transition( PUT( '/projects/1234/merge_requests/54/merge', dict(sha=rewritten_sha, should_remove_source_branch=True, merge_when_pipeline_succeeds=True), ), Ok({}), from_state='passed', to_state='merged', ) api.add_merge_request(dict(self.merge_request_info, state='merged'), from_state='merged') api.add_transition(GET('/projects/1234/repository/branches/master'), Ok({'commit': { 'id': self.rewritten_sha }}), from_state='merged') api.expected_note( self.merge_request_info, "My job would be easier if people didn't jump the queue and push directly... *sigh*", from_state=['pushed_but_master_moved', 'merge_rejected'], ) api.expected_note( self.merge_request_info, "I'm broken on the inside, please somebody fix me... :cry:")
def test_tells_explicitly_that_gitlab_refused_to_merge(self, mocks): mocklab, api, job = mocks rewritten_sha = mocklab.rewritten_sha api.add_transition( PUT( '/projects/1234/merge_requests/{iid}/merge'.format(iid=mocklab.merge_request_info['iid']), dict(sha=rewritten_sha, should_remove_source_branch=True), ), Error(marge.gitlab.MethodNotAllowed(405, {'message': '405 Method Not Allowed'})), from_state='passed', to_state='rejected_for_mysterious_reasons', ) message = "Gitlab refused to merge this request and I don't know why!" with mocklab.expected_failure(message): job.execute() assert api.state == 'rejected_for_mysterious_reasons' assert api.notes == ["I couldn't merge this branch: %s" % message]
def expected_failure(self, message): author_assigned = False def assign_to_author(): nonlocal author_assigned author_assigned = True self.api.add_transition( PUT('/projects/1234/merge_requests/54', args={'assignee_id': self.author_id}), assign_to_author, ) error_note = "I couldn't merge this branch: %s" % message self.api.expected_note(self.merge_request_info, error_note) yield assert author_assigned assert error_note in self.api.notes
def test_rebase_was_not_in_progress_error(self): expected = [ ( GET('/projects/1234/merge_requests/54' ), # refetch_info -> not in progress INFO), (PUT('/projects/1234/merge_requests/54/rebase'), True), ( GET('/projects/1234/merge_requests/54' ), # refetch_info -> BOOM dict(INFO, rebase_in_progress=False, merge_error="Rebase failed. Please rebase locally")), ] self.api.call = Mock(side_effect=[resp for (req, resp) in expected]) with pytest.raises(MergeRequestRebaseFailed): self.merge_request.rebase() self.api.call.assert_has_calls([call(req) for (req, resp) in expected])
def test_rebase_was_not_in_progress_no_error(self): expected = [ ( GET('/projects/1234/merge_requests/54' ), # refetch_info -> not in progress INFO), (PUT('/projects/1234/merge_requests/54/rebase'), True), ( GET('/projects/1234/merge_requests/54' ), # refetch_info -> in progress dict(INFO, rebase_in_progress=True)), ( GET('/projects/1234/merge_requests/54' ), # refetch_info -> succeeded dict(INFO, rebase_in_progress=False)), ] self.api.call = Mock(side_effect=[resp for (req, resp) in expected]) self.merge_request.rebase() self.api.call.assert_has_calls([call(req) for (req, resp) in expected])
def test_calculates_merge_when_pipeline_succeeds_correctly( self, mocks, only_allow_merge_if_pipeline_succeeds): mocklab, api, job = mocks rewritten_sha = mocklab.rewritten_sha project_info = dict(TEST_PROJECT_INFO) project_info[ "only_allow_merge_if_pipeline_succeeds"] = only_allow_merge_if_pipeline_succeeds api.add_project(project_info) api.add_transition( PUT( '/projects/{pid}/merge_requests/{iid}/merge'.format( iid=mocklab.merge_request_info['iid'], pid=project_info["id"]), dict(sha=rewritten_sha, should_remove_source_branch=True, merge_when_pipeline_succeeds= only_allow_merge_if_pipeline_succeeds), ), Ok(True), to_state='merged', ) job.execute() assert api.state == 'merged'
def test_unassign(self): self.merge_request.unassign() self.api.call.assert_called_once_with( PUT('/projects/1234/merge_requests/54', {'assignee_id': 0}))
def test_assign(self): self.merge_request.assign_to(42) self.api.call.assert_called_once_with( PUT('/projects/1234/merge_requests/54', {'assignee_id': 42}))
def test_second_time_if_master_moved(self, mocks_factory, fusion, update_sha, rewrite_sha): initial_master_sha = 'eaeaea9e9e' moved_master_sha = 'fafafa' first_rewritten_sha = rewrite_sha(update_sha(INITIAL_MR_SHA, initial_master_sha)) second_rewritten_sha = rewrite_sha(update_sha(first_rewritten_sha, moved_master_sha)) # pylint: disable=unused-argument def push_effects(remote_url, remote_branch, old_sha, new_sha): nonlocal mocklab, target_branch, remote_target_repo if api.state == 'initial': assert old_sha == INITIAL_MR_SHA assert new_sha == first_rewritten_sha api.state = 'pushed_but_master_moved' remote_target_repo.set_ref(target_branch, moved_master_sha) elif api.state == 'merge_rejected': assert new_sha == second_rewritten_sha api.state = 'pushed' mocklab, api, job = mocks_factory( initial_master_sha=initial_master_sha, rewritten_sha=second_rewritten_sha, on_push=push_effects, ) source_project_info = mocklab.forked_project_info or mocklab.project_info target_project_info = mocklab.project_info source_project_url = source_project_info['ssh_url_to_repo'] target_project_url = target_project_info['ssh_url_to_repo'] source_branch = mocklab.merge_request_info['source_branch'] target_branch = mocklab.merge_request_info['target_branch'] remote_source_repo = job.repo.mock_impl.remote_repos[source_project_url] remote_target_repo = job.repo.mock_impl.remote_repos[target_project_url] api.add_merge_request( dict( mocklab.merge_request_info, sha=first_rewritten_sha, ), from_state=['pushed_but_master_moved', 'merge_rejected'], ) api.add_pipelines( mocklab.merge_request_info['source_project_id'], _pipeline(sha1=first_rewritten_sha, status='success'), from_state=['pushed_but_master_moved', 'merge_rejected'], ) api.add_transition( PUT( '/projects/1234/merge_requests/{iid}/merge'.format(iid=mocklab.merge_request_info['iid']), dict( sha=first_rewritten_sha, should_remove_source_branch=True, ), ), Error(marge.gitlab.NotAcceptable()), from_state='pushed_but_master_moved', to_state='merge_rejected', ) api.add_transition( GET( '/projects/{source_project_id}/repository/branches/useless_new_feature'.format( source_project_id=mocklab.merge_request_info['source_project_id'], ), ), Ok({'commit': _commit(commit_id=first_rewritten_sha, status='success')}), from_state='pushed_but_master_moved' ) api.add_transition( GET('/projects/1234/repository/branches/master'), Ok({'commit': _commit(commit_id=moved_master_sha, status='success')}), from_state='merge_rejected' ) if fusion is Fusion.gitlab_rebase: rebase_url = '/projects/{project_id}/merge_requests/{iid}/rebase'.format( project_id=mocklab.merge_request_info['project_id'], iid=mocklab.merge_request_info['iid'], ) api.add_transition( PUT(rebase_url), Ok(True), from_state='initial', to_state='pushed_but_master_moved', side_effect=lambda: ( remote_source_repo.set_ref(source_branch, first_rewritten_sha), remote_target_repo.set_ref(target_branch, moved_master_sha) ) ) api.add_transition( PUT(rebase_url), Ok(True), from_state='merge_rejected', to_state='rebase-in-progress', side_effect=lambda: remote_source_repo.set_ref(source_branch, second_rewritten_sha) ) job.execute() assert api.state == 'merged' assert api.notes == [ "My job would be easier if people didn't jump the queue and push directly... *sigh*", ]
def __init__(self, gitlab_url=None): self.gitlab_url = gitlab_url = gitlab_url or 'http://git.example.com' self.api = api = ApiMock(gitlab_url=gitlab_url, auth_token='no-token', initial_state='initial') api.add_transition(GET('/version'), Ok({'version': '9.2.3-ee'})) self.user_info = dict(test_user.INFO) self.user_id = self.user_info['id'] api.add_user(self.user_info, is_current=True) self.project_info = dict(test_project.INFO) api.add_project(self.project_info) self.commit_info = dict(test_commit.INFO) api.add_commit(self.project_info['id'], self.commit_info) self.author_id = 234234 self.merge_request_info = { 'id': 53, 'iid': 54, 'title': 'a title', 'project_id': 1234, 'author': { 'id': self.author_id }, 'assignee': { 'id': self.user_id }, 'approved_by': [], 'state': 'opened', 'sha': self.commit_info['id'], 'source_project_id': 1234, 'target_project_id': 1234, 'source_branch': 'useless_new_feature', 'target_branch': 'master', 'work_in_progress': False, 'web_url': 'http://git.example.com/group/project/merge_request/666', } api.add_merge_request(self.merge_request_info) self.initial_master_sha = '505e' self.rewritten_sha = rewritten_sha = 'af7a' api.add_pipelines( self.project_info['id'], _pipeline(sha1=rewritten_sha, status='running'), from_state='pushed', to_state='passed', ) api.add_pipelines( self.project_info['id'], _pipeline(sha1=rewritten_sha, status='success'), from_state=['passed', 'merged'], ) api.add_transition( GET('/projects/1234/repository/branches/useless_new_feature'), Ok({'commit': _commit(commit_id=rewritten_sha, status='running')}), from_state='pushed', ) api.add_transition( GET('/projects/1234/repository/branches/useless_new_feature'), Ok({'commit': _commit(commit_id=rewritten_sha, status='success')}), from_state='passed') api.add_transition( PUT( '/projects/1234/merge_requests/54/merge', dict(sha=rewritten_sha, should_remove_source_branch=True, merge_when_pipeline_succeeds=True), ), Ok({}), from_state='passed', to_state='merged', ) api.add_merge_request(dict(self.merge_request_info, state='merged'), from_state='merged') self.approvals_info = dict( test_approvals.INFO, id=self.merge_request_info['id'], iid=self.merge_request_info['iid'], project_id=self.merge_request_info['project_id'], approvals_left=0, ) api.add_approvals(self.approvals_info) api.add_transition( GET('/projects/1234/repository/branches/master'), Ok({'commit': { 'id': self.initial_master_sha }}), ) api.add_transition(GET('/projects/1234/repository/branches/master'), Ok({'commit': { 'id': self.rewritten_sha }}), from_state='merged') api.expected_note( self.merge_request_info, "My job would be easier if people didn't jump the queue and push directly... *sigh*", from_state=['pushed_but_master_moved', 'merge_rejected'], ) api.expected_note( self.merge_request_info, "I'm broken on the inside, please somebody fix me... :cry:")