require 'spec_helper' describe MergeRequests::MergeService do let(:user) { create(:user) } let(:user2) { create(:user) } let(:merge_request) { create(:merge_request, :simple, author: user2, assignee: user2) } let(:project) { merge_request.project } before do project.team << [user, :master] project.team << [user2, :developer] end describe '#execute' do context 'MergeRequest#merge_jid' do let(:service) do described_class.new(project, user, commit_message: 'Awesome message') end before do merge_request.update_column(:merge_jid, 'hash-123') end it 'is cleaned when no error is raised' do service.execute(merge_request) expect(merge_request.reload.merge_jid).to be_nil end it 'is cleaned when expected error is raised' do allow(service).to receive(:commit).and_raise(described_class::MergeError) service.execute(merge_request) expect(merge_request.reload.merge_jid).to be_nil end it 'is cleaned when merge request is not mergeable' do allow(merge_request).to receive(:mergeable?).and_return(false) service.execute(merge_request) expect(merge_request.reload.merge_jid).to be_nil end it 'is cleaned when no source is found' do allow(merge_request).to receive(:diff_head_sha).and_return(nil) service.execute(merge_request) expect(merge_request.reload.merge_jid).to be_nil end it 'is not cleaned when unexpected error is raised' do service = described_class.new(project, user, commit_message: 'Awesome message') allow(service).to receive(:commit).and_raise(StandardError) expect { service.execute(merge_request) }.to raise_error(StandardError) expect(merge_request.reload.merge_jid).to be_present end end context 'valid params' do let(:service) { described_class.new(project, user, commit_message: 'Awesome message') } before do allow(service).to receive(:execute_hooks) perform_enqueued_jobs do service.execute(merge_request) end end it { expect(merge_request).to be_valid } it { expect(merge_request).to be_merged } it 'sends email to user2 about merge of new merge_request' do email = ActionMailer::Base.deliveries.last expect(email.to.first).to eq(user2.email) expect(email.subject).to include(merge_request.title) end it 'creates system note about merge_request merge' do note = merge_request.notes.last expect(note.note).to include 'merged' end end context 'closes related issues' do let(:service) { described_class.new(project, user, commit_message: 'Awesome message') } before do allow(project).to receive(:default_branch).and_return(merge_request.target_branch) end it 'closes GitLab issue tracker issues' do issue = create :issue, project: project commit = double('commit', safe_message: "Fixes #{issue.to_reference}") allow(merge_request).to receive(:commits).and_return([commit]) service.execute(merge_request) expect(issue.reload.closed?).to be_truthy end context 'with JIRA integration' do include JiraServiceHelper let(:jira_tracker) { project.create_jira_service } let(:jira_issue) { ExternalIssue.new('JIRA-123', project) } let(:commit) { double('commit', safe_message: "Fixes #{jira_issue.to_reference}") } before do project.update_attributes!(has_external_issue_tracker: true) jira_service_settings stub_jira_urls(jira_issue.id) allow(merge_request).to receive(:commits).and_return([commit]) end it 'closes issues on JIRA issue tracker' do jira_issue = ExternalIssue.new('JIRA-123', project) stub_jira_urls(jira_issue) commit = double('commit', safe_message: "Fixes #{jira_issue.to_reference}") allow(merge_request).to receive(:commits).and_return([commit]) expect_any_instance_of(JiraService).to receive(:close_issue).with(merge_request, jira_issue).once service.execute(merge_request) end context "when jira_issue_transition_id is not present" do before do allow_any_instance_of(JIRA::Resource::Issue).to receive(:resolution).and_return(nil) end it "does not close issue" do allow(jira_tracker).to receive_messages(jira_issue_transition_id: nil) expect_any_instance_of(JiraService).not_to receive(:transition_issue) service.execute(merge_request) end end context "wrong issue markdown" do it 'does not close issues on JIRA issue tracker' do jira_issue = ExternalIssue.new('#JIRA-123', project) stub_jira_urls(jira_issue) commit = double('commit', safe_message: "Fixes #{jira_issue.to_reference}") allow(merge_request).to receive(:commits).and_return([commit]) expect_any_instance_of(JiraService).not_to receive(:close_issue) service.execute(merge_request) end end end end context 'closes related todos' do let(:merge_request) { create(:merge_request, assignee: user, author: user) } let(:project) { merge_request.project } let(:service) { described_class.new(project, user, commit_message: 'Awesome message') } let!(:todo) do create(:todo, :assigned, project: project, author: user, user: user, target: merge_request) end before do allow(service).to receive(:execute_hooks) perform_enqueued_jobs do service.execute(merge_request) todo.reload end end it { expect(todo).to be_done } end context 'source branch removal' do context 'when the source branch is protected' do let(:service) do described_class.new(project, user, should_remove_source_branch: '1') end before do create(:protected_branch, project: project, name: merge_request.source_branch) end it 'does not delete the source branch' do expect(DeleteBranchService).not_to receive(:new) service.execute(merge_request) end end context 'when the source branch is the default branch' do let(:service) do described_class.new(project, user, should_remove_source_branch: '1') end before do allow(project).to receive(:root_ref?).with(merge_request.source_branch).and_return(true) end it 'does not delete the source branch' do expect(DeleteBranchService).not_to receive(:new) service.execute(merge_request) end end context 'when the source branch can be removed' do context 'when MR author set the source branch to be removed' do let(:service) do merge_request.merge_params['force_remove_source_branch'] = '1' merge_request.save! described_class.new(project, user, commit_message: 'Awesome message') end it 'removes the source branch using the author user' do expect(DeleteBranchService).to receive(:new) .with(merge_request.source_project, merge_request.author) .and_call_original service.execute(merge_request) end end context 'when MR merger set the source branch to be removed' do let(:service) do described_class.new(project, user, commit_message: 'Awesome message', should_remove_source_branch: '1') end it 'removes the source branch using the current user' do expect(DeleteBranchService).to receive(:new) .with(merge_request.source_project, user) .and_call_original service.execute(merge_request) end end end end context "error handling" do let(:service) { described_class.new(project, user, commit_message: 'Awesome message') } before do allow(Rails.logger).to receive(:error) end it 'logs and saves error if there is an exception' do error_message = 'error message' allow(service).to receive(:repository).and_raise("error message") allow(service).to receive(:execute_hooks) service.execute(merge_request) expect(merge_request.merge_error).to include(error_message) expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message)) end it 'logs and saves error if there is an PreReceiveError exception' do error_message = 'error message' allow(service).to receive(:repository).and_raise(Gitlab::Git::HooksService::PreReceiveError, error_message) allow(service).to receive(:execute_hooks) service.execute(merge_request) expect(merge_request.merge_error).to include(error_message) expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message)) end it 'logs and saves error if there is a merge conflict' do error_message = 'Conflicts detected during merge' allow_any_instance_of(Repository).to receive(:merge).and_return(false) allow(service).to receive(:execute_hooks) service.execute(merge_request) expect(merge_request).to be_open expect(merge_request.merge_commit_sha).to be_nil expect(merge_request.merge_error).to include(error_message) expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message)) end end end end