require 'spec_helper' describe MergeRequests::MergeService do set(:user) { create(:user) } set(:user2) { create(:user) } let(:merge_request) { create(:merge_request, :simple, author: user2, assignee: user2) } let(:project) { merge_request.project } before do project.add_maintainer(user) project.add_developer(user2) end describe '#execute' do 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]) merge_request.cache_merge_request_closes_issues! 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!(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' => true) 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' => true) 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) { described_class.new(project, user, commit_message: 'Awesome message') } before do merge_request.update_attribute(:merge_params, { 'force_remove_source_branch' => '1' }) 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 context 'when the merger set the source branch not to be removed' do let(:service) { described_class.new(project, user, commit_message: 'Awesome message', 'should_remove_source_branch' => false) } it 'does not delete the source branch' do expect(DeleteBranchService).not_to receive(:new) service.execute(merge_request) end 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' => true) 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('Something went wrong during merge') 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::PreReceiveError, error_message) allow(service).to receive(:execute_hooks) service.execute(merge_request) expect(merge_request.merge_error).to include('Something went wrong during merge pre-receive hook') 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 context 'when squashing' do before do merge_request.update!(source_branch: 'master', target_branch: 'feature') end it 'logs and saves error if there is an error when squashing' do error_message = 'Failed to squash. Should be done manually' allow_any_instance_of(MergeRequests::SquashService).to receive(:squash).and_return(nil) merge_request.update(squash: true) 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 it 'logs and saves error if there is a squash in progress' do error_message = 'another squash is already in progress' allow_any_instance_of(MergeRequest).to receive(:squash_in_progress?).and_return(true) merge_request.update(squash: true) 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 context "when fast-forward merge is not allowed" do before do allow_any_instance_of(Repository).to receive(:ancestor?).and_return(nil) end %w(semi-linear ff).each do |merge_method| it "logs and saves error if merge is #{merge_method} only" do merge_method = 'rebase_merge' if merge_method == 'semi-linear' merge_request.project.update(merge_method: merge_method) error_message = 'Only fast-forward merge is allowed for your project. Please update your source branch' 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 end end end