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_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_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_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_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_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_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_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_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_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_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 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 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*", ]