3265 lines
		
	
	
		
			104 KiB
		
	
	
	
		
			Ruby
		
	
	
	
			
		
		
	
	
			3265 lines
		
	
	
		
			104 KiB
		
	
	
	
		
			Ruby
		
	
	
	
# frozen_string_literal: true
 | 
						|
 | 
						|
require 'spec_helper'
 | 
						|
 | 
						|
describe MergeRequest do
 | 
						|
  include RepoHelpers
 | 
						|
  include ProjectForksHelper
 | 
						|
  include ReactiveCachingHelpers
 | 
						|
 | 
						|
  using RSpec::Parameterized::TableSyntax
 | 
						|
 | 
						|
  subject { create(:merge_request) }
 | 
						|
 | 
						|
  describe 'associations' do
 | 
						|
    it { is_expected.to belong_to(:target_project).class_name('Project') }
 | 
						|
    it { is_expected.to belong_to(:source_project).class_name('Project') }
 | 
						|
    it { is_expected.to belong_to(:merge_user).class_name("User") }
 | 
						|
    it { is_expected.to have_many(:assignees).through(:merge_request_assignees) }
 | 
						|
    it { is_expected.to have_many(:merge_request_diffs) }
 | 
						|
 | 
						|
    context 'for forks' do
 | 
						|
      let!(:project) { create(:project) }
 | 
						|
      let!(:fork) { fork_project(project) }
 | 
						|
      let!(:merge_request) { create(:merge_request, target_project: project, source_project: fork) }
 | 
						|
 | 
						|
      it 'does not load another project due to inverse relationship' do
 | 
						|
        expect(project.merge_requests.first.target_project.object_id).to eq(project.object_id)
 | 
						|
      end
 | 
						|
 | 
						|
      it 'finds the associated merge request' do
 | 
						|
        expect(project.merge_requests.find(merge_request.id)).to eq(merge_request)
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe 'locking' do
 | 
						|
    using RSpec::Parameterized::TableSyntax
 | 
						|
 | 
						|
    where(:lock_version) do
 | 
						|
      [
 | 
						|
        [0],
 | 
						|
        ["0"]
 | 
						|
      ]
 | 
						|
    end
 | 
						|
 | 
						|
    with_them do
 | 
						|
      it 'works when a merge request has a NULL lock_version' do
 | 
						|
        merge_request = create(:merge_request)
 | 
						|
 | 
						|
        described_class.where(id: merge_request.id).update_all('lock_version = NULL')
 | 
						|
 | 
						|
        merge_request.update!(lock_version: lock_version, title: 'locking test')
 | 
						|
 | 
						|
        expect(merge_request.reload.title).to eq('locking test')
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#squash_in_progress?' do
 | 
						|
    let(:repo_path) do
 | 
						|
      Gitlab::GitalyClient::StorageSettings.allow_disk_access do
 | 
						|
        subject.source_project.repository.path
 | 
						|
      end
 | 
						|
    end
 | 
						|
    let(:squash_path) { File.join(repo_path, "gitlab-worktree", "squash-#{subject.id}") }
 | 
						|
 | 
						|
    before do
 | 
						|
      system(*%W(#{Gitlab.config.git.bin_path} -C #{repo_path} worktree add --detach #{squash_path} master))
 | 
						|
    end
 | 
						|
 | 
						|
    it 'returns true when there is a current squash directory' do
 | 
						|
      expect(subject.squash_in_progress?).to be_truthy
 | 
						|
    end
 | 
						|
 | 
						|
    it 'returns false when there is no squash directory' do
 | 
						|
      FileUtils.rm_rf(squash_path)
 | 
						|
 | 
						|
      expect(subject.squash_in_progress?).to be_falsey
 | 
						|
    end
 | 
						|
 | 
						|
    it 'returns false when the squash directory has expired' do
 | 
						|
      time = 20.minutes.ago.to_time
 | 
						|
      File.utime(time, time, squash_path)
 | 
						|
 | 
						|
      expect(subject.squash_in_progress?).to be_falsey
 | 
						|
    end
 | 
						|
 | 
						|
    it 'returns false when the source project has been removed' do
 | 
						|
      allow(subject).to receive(:source_project).and_return(nil)
 | 
						|
 | 
						|
      expect(subject.squash_in_progress?).to be_falsey
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#squash?' do
 | 
						|
    let(:merge_request) { build(:merge_request, squash: squash) }
 | 
						|
    subject { merge_request.squash? }
 | 
						|
 | 
						|
    context 'disabled in database' do
 | 
						|
      let(:squash) { false }
 | 
						|
 | 
						|
      it { is_expected.to be_falsy }
 | 
						|
    end
 | 
						|
 | 
						|
    context 'enabled in database' do
 | 
						|
      let(:squash) { true }
 | 
						|
 | 
						|
      it { is_expected.to be_truthy }
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#default_squash_commit_message' do
 | 
						|
    let(:project) { subject.project }
 | 
						|
    let(:is_multiline) { -> (c) { c.description.present? } }
 | 
						|
    let(:multiline_commits) { subject.commits.select(&is_multiline) }
 | 
						|
    let(:singleline_commits) { subject.commits.reject(&is_multiline) }
 | 
						|
 | 
						|
    it 'returns the oldest multiline commit message' do
 | 
						|
      expect(subject.default_squash_commit_message).to eq(multiline_commits.last.message)
 | 
						|
    end
 | 
						|
 | 
						|
    it 'returns the merge request title if there are no multiline commits' do
 | 
						|
      expect(subject).to receive(:commits).and_return(
 | 
						|
        CommitCollection.new(project, singleline_commits)
 | 
						|
      )
 | 
						|
 | 
						|
      expect(subject.default_squash_commit_message).to eq(subject.title)
 | 
						|
    end
 | 
						|
 | 
						|
    it 'does not return commit messages from multiline merge commits' do
 | 
						|
      collection = CommitCollection.new(project, multiline_commits).enrich!
 | 
						|
 | 
						|
      expect(collection.commits).to all( receive(:merge_commit?).and_return(true) )
 | 
						|
      expect(subject).to receive(:commits).and_return(collection)
 | 
						|
      expect(subject.default_squash_commit_message).to eq(subject.title)
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe 'modules' do
 | 
						|
    subject { described_class }
 | 
						|
 | 
						|
    it { is_expected.to include_module(Issuable) }
 | 
						|
    it { is_expected.to include_module(Referable) }
 | 
						|
    it { is_expected.to include_module(Sortable) }
 | 
						|
    it { is_expected.to include_module(Taskable) }
 | 
						|
 | 
						|
    it_behaves_like 'AtomicInternalId' do
 | 
						|
      let(:internal_id_attribute) { :iid }
 | 
						|
      let(:instance) { build(:merge_request) }
 | 
						|
      let(:scope) { :target_project }
 | 
						|
      let(:scope_attrs) { { project: instance.target_project } }
 | 
						|
      let(:usage) { :merge_requests }
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe 'validation' do
 | 
						|
    it { is_expected.to validate_presence_of(:target_branch) }
 | 
						|
    it { is_expected.to validate_presence_of(:source_branch) }
 | 
						|
 | 
						|
    context "Validation of merge user with Merge When Pipeline Succeeds" do
 | 
						|
      it "allows user to be nil when the feature is disabled" do
 | 
						|
        expect(subject).to be_valid
 | 
						|
      end
 | 
						|
 | 
						|
      it "is invalid without merge user" do
 | 
						|
        subject.merge_when_pipeline_succeeds = true
 | 
						|
        expect(subject).not_to be_valid
 | 
						|
      end
 | 
						|
 | 
						|
      it "is valid with merge user" do
 | 
						|
        subject.merge_when_pipeline_succeeds = true
 | 
						|
        subject.merge_user = build(:user)
 | 
						|
 | 
						|
        expect(subject).to be_valid
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'for branch' do
 | 
						|
      before do
 | 
						|
        stub_feature_flags(stricter_mr_branch_name: false)
 | 
						|
      end
 | 
						|
 | 
						|
      using RSpec::Parameterized::TableSyntax
 | 
						|
 | 
						|
      where(:branch_name, :valid) do
 | 
						|
        'foo' | true
 | 
						|
        'foo:bar' | false
 | 
						|
        '+foo:bar' | false
 | 
						|
        'foo bar' | false
 | 
						|
        '-foo' | false
 | 
						|
        'HEAD' | true
 | 
						|
        'refs/heads/master' | true
 | 
						|
      end
 | 
						|
 | 
						|
      with_them do
 | 
						|
        it "validates source_branch" do
 | 
						|
          subject = build(:merge_request, source_branch: branch_name, target_branch: 'master')
 | 
						|
 | 
						|
          subject.valid?
 | 
						|
 | 
						|
          expect(subject.errors.added?(:source_branch)).to eq(!valid)
 | 
						|
        end
 | 
						|
 | 
						|
        it "validates target_branch" do
 | 
						|
          subject = build(:merge_request, source_branch: 'master', target_branch: branch_name)
 | 
						|
 | 
						|
          subject.valid?
 | 
						|
 | 
						|
          expect(subject.errors.added?(:target_branch)).to eq(!valid)
 | 
						|
        end
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'for forks' do
 | 
						|
      let(:project) { create(:project) }
 | 
						|
      let(:fork1) { fork_project(project) }
 | 
						|
      let(:fork2) { fork_project(project) }
 | 
						|
 | 
						|
      it 'allows merge requests for sibling-forks' do
 | 
						|
        subject.source_project = fork1
 | 
						|
        subject.target_project = fork2
 | 
						|
 | 
						|
        expect(subject).to be_valid
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe 'callbacks' do
 | 
						|
    describe '#ensure_merge_request_metrics' do
 | 
						|
      it 'creates metrics after saving' do
 | 
						|
        merge_request = create(:merge_request)
 | 
						|
 | 
						|
        expect(merge_request.metrics).to be_persisted
 | 
						|
        expect(MergeRequest::Metrics.count).to eq(1)
 | 
						|
      end
 | 
						|
 | 
						|
      it 'does not duplicate metrics for a merge request' do
 | 
						|
        merge_request = create(:merge_request)
 | 
						|
 | 
						|
        merge_request.mark_as_merged!
 | 
						|
 | 
						|
        expect(MergeRequest::Metrics.count).to eq(1)
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe 'respond to' do
 | 
						|
    it { is_expected.to respond_to(:unchecked?) }
 | 
						|
    it { is_expected.to respond_to(:can_be_merged?) }
 | 
						|
    it { is_expected.to respond_to(:cannot_be_merged?) }
 | 
						|
    it { is_expected.to respond_to(:merge_params) }
 | 
						|
    it { is_expected.to respond_to(:merge_when_pipeline_succeeds) }
 | 
						|
  end
 | 
						|
 | 
						|
  describe '.by_commit_sha' do
 | 
						|
    subject(:by_commit_sha) { described_class.by_commit_sha(sha) }
 | 
						|
 | 
						|
    let!(:merge_request) { create(:merge_request, :with_diffs) }
 | 
						|
 | 
						|
    context 'with sha contained in latest merge request diff' do
 | 
						|
      let(:sha) { 'b83d6e391c22777fca1ed3012fce84f633d7fed0' }
 | 
						|
 | 
						|
      it 'returns merge requests' do
 | 
						|
        expect(by_commit_sha).to eq([merge_request])
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'with sha contained not in latest merge request diff' do
 | 
						|
      let(:sha) { 'b83d6e391c22777fca1ed3012fce84f633d7fed0' }
 | 
						|
 | 
						|
      it 'returns empty requests' do
 | 
						|
        latest_merge_request_diff = merge_request.merge_request_diffs.create
 | 
						|
        latest_merge_request_diff.merge_request_diff_commits.where(sha: 'b83d6e391c22777fca1ed3012fce84f633d7fed0').delete_all
 | 
						|
 | 
						|
        expect(by_commit_sha).to be_empty
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'with sha not contained in' do
 | 
						|
      let(:sha) { 'b83d6e3' }
 | 
						|
 | 
						|
      it 'returns empty result' do
 | 
						|
        expect(by_commit_sha).to be_empty
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '.in_projects' do
 | 
						|
    it 'returns the merge requests for a set of projects' do
 | 
						|
      expect(described_class.in_projects(Project.all)).to eq([subject])
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '.set_latest_merge_request_diff_ids!' do
 | 
						|
    def create_merge_request_with_diffs(source_branch, diffs: 2)
 | 
						|
      params = {
 | 
						|
        target_project: project,
 | 
						|
        target_branch: 'master',
 | 
						|
        source_project: project,
 | 
						|
        source_branch: source_branch
 | 
						|
      }
 | 
						|
 | 
						|
      create(:merge_request, params).tap do |mr|
 | 
						|
        diffs.times { mr.merge_request_diffs.create }
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    let(:project) { create(:project) }
 | 
						|
 | 
						|
    it 'sets IDs for merge requests, whether they are already set or not' do
 | 
						|
      merge_requests = [
 | 
						|
        create_merge_request_with_diffs('feature'),
 | 
						|
        create_merge_request_with_diffs('feature-conflict'),
 | 
						|
        create_merge_request_with_diffs('wip', diffs: 0),
 | 
						|
        create_merge_request_with_diffs('csv')
 | 
						|
      ]
 | 
						|
 | 
						|
      merge_requests.take(2).each do |merge_request|
 | 
						|
        merge_request.update_column(:latest_merge_request_diff_id, nil)
 | 
						|
      end
 | 
						|
 | 
						|
      expected = merge_requests.map do |merge_request|
 | 
						|
        merge_request.merge_request_diffs.maximum(:id)
 | 
						|
      end
 | 
						|
 | 
						|
      expect { project.merge_requests.set_latest_merge_request_diff_ids! }
 | 
						|
        .to change { merge_requests.map { |mr| mr.reload.latest_merge_request_diff_id } }.to(expected)
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '.recent_target_branches' do
 | 
						|
    let(:project) { create(:project) }
 | 
						|
    let!(:merge_request1) { create(:merge_request, :opened, source_project: project, target_branch: 'feature') }
 | 
						|
    let!(:merge_request2) { create(:merge_request, :closed, source_project: project, target_branch: 'merge-test') }
 | 
						|
    let!(:merge_request3) { create(:merge_request, :opened, source_project: project, target_branch: 'fix') }
 | 
						|
    let!(:merge_request4) { create(:merge_request, :closed, source_project: project, target_branch: 'feature') }
 | 
						|
 | 
						|
    before do
 | 
						|
      merge_request1.update_columns(updated_at: 1.day.since)
 | 
						|
      merge_request2.update_columns(updated_at: 2.days.since)
 | 
						|
      merge_request3.update_columns(updated_at: 3.days.since)
 | 
						|
      merge_request4.update_columns(updated_at: 4.days.since)
 | 
						|
    end
 | 
						|
 | 
						|
    it 'returns target branches sort by updated at desc' do
 | 
						|
      expect(described_class.recent_target_branches).to match_array(['feature', 'merge-test', 'fix'])
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#target_branch_sha' do
 | 
						|
    let(:project) { create(:project, :repository) }
 | 
						|
 | 
						|
    subject { create(:merge_request, source_project: project, target_project: project) }
 | 
						|
 | 
						|
    context 'when the target branch does not exist' do
 | 
						|
      before do
 | 
						|
        project.repository.rm_branch(subject.author, subject.target_branch)
 | 
						|
        subject.clear_memoized_shas
 | 
						|
      end
 | 
						|
 | 
						|
      it 'returns nil' do
 | 
						|
        expect(subject.target_branch_sha).to be_nil
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    it 'returns memoized value' do
 | 
						|
      subject.target_branch_sha = '8ffb3c15a5475e59ae909384297fede4badcb4c7'
 | 
						|
 | 
						|
      expect(subject.target_branch_sha).to eq '8ffb3c15a5475e59ae909384297fede4badcb4c7'
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#card_attributes' do
 | 
						|
    it 'includes the author name' do
 | 
						|
      allow(subject).to receive(:author).and_return(double(name: 'Robert'))
 | 
						|
      allow(subject).to receive(:assignees).and_return([])
 | 
						|
 | 
						|
      expect(subject.card_attributes)
 | 
						|
        .to eq({ 'Author' => 'Robert', 'Assignee' => "" })
 | 
						|
    end
 | 
						|
 | 
						|
    it 'includes the assignees name' do
 | 
						|
      allow(subject).to receive(:author).and_return(double(name: 'Robert'))
 | 
						|
      allow(subject).to receive(:assignees).and_return([double(name: 'Douwe'), double(name: 'Robert')])
 | 
						|
 | 
						|
      expect(subject.card_attributes)
 | 
						|
        .to eq({ 'Author' => 'Robert', 'Assignee' => 'Douwe and Robert' })
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#assignee_or_author?' do
 | 
						|
    let(:user) { create(:user) }
 | 
						|
 | 
						|
    it 'returns true for a user that is assigned to a merge request' do
 | 
						|
      subject.assignees = [user]
 | 
						|
 | 
						|
      expect(subject.assignee_or_author?(user)).to eq(true)
 | 
						|
    end
 | 
						|
 | 
						|
    it 'returns true for a user that is the author of a merge request' do
 | 
						|
      subject.author = user
 | 
						|
 | 
						|
      expect(subject.assignee_or_author?(user)).to eq(true)
 | 
						|
    end
 | 
						|
 | 
						|
    it 'returns false for a user that is not the assignee or author' do
 | 
						|
      expect(subject.assignee_or_author?(user)).to eq(false)
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#visible_closing_issues_for' do
 | 
						|
    let(:guest) { create(:user) }
 | 
						|
    let(:developer) { create(:user) }
 | 
						|
    let(:issue_1) { create(:issue, project: subject.source_project) }
 | 
						|
    let(:issue_2) { create(:issue, project: subject.source_project) }
 | 
						|
    let(:confidential_issue) { create(:issue, :confidential, project: subject.source_project) }
 | 
						|
 | 
						|
    before do
 | 
						|
      subject.project.add_developer(subject.author)
 | 
						|
      subject.target_branch = subject.project.default_branch
 | 
						|
      commit = double('commit1', safe_message: "Fixes #{issue_1.to_reference} #{issue_2.to_reference} #{confidential_issue.to_reference}")
 | 
						|
      allow(subject).to receive(:commits).and_return([commit])
 | 
						|
    end
 | 
						|
 | 
						|
    it 'shows only allowed issues to guest' do
 | 
						|
      subject.project.add_guest(guest)
 | 
						|
 | 
						|
      subject.cache_merge_request_closes_issues!
 | 
						|
 | 
						|
      expect(subject.visible_closing_issues_for(guest)).to match_array([issue_1, issue_2])
 | 
						|
    end
 | 
						|
 | 
						|
    it 'shows only allowed issues to developer' do
 | 
						|
      subject.project.add_developer(developer)
 | 
						|
 | 
						|
      subject.cache_merge_request_closes_issues!
 | 
						|
 | 
						|
      expect(subject.visible_closing_issues_for(developer)).to match_array([issue_1, confidential_issue, issue_2])
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when external issue tracker is enabled' do
 | 
						|
      before do
 | 
						|
        subject.project.has_external_issue_tracker = true
 | 
						|
        subject.project.save!
 | 
						|
      end
 | 
						|
 | 
						|
      it 'calls non #closes_issues to retrieve data' do
 | 
						|
        expect(subject).to receive(:closes_issues)
 | 
						|
        expect(subject).not_to receive(:cached_closes_issues)
 | 
						|
 | 
						|
        subject.visible_closing_issues_for
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#cache_merge_request_closes_issues!' do
 | 
						|
    before do
 | 
						|
      subject.project.add_developer(subject.author)
 | 
						|
      subject.target_branch = subject.project.default_branch
 | 
						|
    end
 | 
						|
 | 
						|
    it 'caches closed issues' do
 | 
						|
      issue  = create :issue, project: subject.project
 | 
						|
      commit = double('commit1', safe_message: "Fixes #{issue.to_reference}")
 | 
						|
      allow(subject).to receive(:commits).and_return([commit])
 | 
						|
 | 
						|
      expect { subject.cache_merge_request_closes_issues!(subject.author) }.to change(subject.merge_requests_closing_issues, :count).by(1)
 | 
						|
    end
 | 
						|
 | 
						|
    it 'does not cache closed issues when merge request is closed' do
 | 
						|
      issue  = create :issue, project: subject.project
 | 
						|
      commit = double('commit1', safe_message: "Fixes #{issue.to_reference}")
 | 
						|
 | 
						|
      allow(subject).to receive(:commits).and_return([commit])
 | 
						|
      allow(subject).to receive(:state).and_return("closed")
 | 
						|
 | 
						|
      expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to change(subject.merge_requests_closing_issues, :count)
 | 
						|
    end
 | 
						|
 | 
						|
    it 'does not cache closed issues when merge request is merged' do
 | 
						|
      issue  = create :issue, project: subject.project
 | 
						|
      commit = double('commit1', safe_message: "Fixes #{issue.to_reference}")
 | 
						|
      allow(subject).to receive(:commits).and_return([commit])
 | 
						|
      allow(subject).to receive(:state).and_return("merged")
 | 
						|
 | 
						|
      expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to change(subject.merge_requests_closing_issues, :count)
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when both internal and external issue trackers are enabled' do
 | 
						|
      before do
 | 
						|
        subject.project.has_external_issue_tracker = true
 | 
						|
        subject.project.save!
 | 
						|
        create(:jira_service, project: subject.project)
 | 
						|
      end
 | 
						|
 | 
						|
      it 'does not cache issues from external trackers' do
 | 
						|
        issue  = ExternalIssue.new('JIRA-123', subject.project)
 | 
						|
        commit = double('commit1', safe_message: "Fixes #{issue.to_reference}")
 | 
						|
        allow(subject).to receive(:commits).and_return([commit])
 | 
						|
 | 
						|
        expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to raise_error
 | 
						|
        expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to change(subject.merge_requests_closing_issues, :count)
 | 
						|
      end
 | 
						|
 | 
						|
      it 'caches an internal issue' do
 | 
						|
        issue  = create(:issue, project: subject.project)
 | 
						|
        commit = double('commit1', safe_message: "Fixes #{issue.to_reference}")
 | 
						|
        allow(subject).to receive(:commits).and_return([commit])
 | 
						|
 | 
						|
        expect { subject.cache_merge_request_closes_issues!(subject.author) }
 | 
						|
          .to change(subject.merge_requests_closing_issues, :count).by(1)
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when only external issue tracker enabled' do
 | 
						|
      before do
 | 
						|
        subject.project.has_external_issue_tracker = true
 | 
						|
        subject.project.issues_enabled = false
 | 
						|
        subject.project.save!
 | 
						|
      end
 | 
						|
 | 
						|
      it 'does not cache issues from external trackers' do
 | 
						|
        issue  = ExternalIssue.new('JIRA-123', subject.project)
 | 
						|
        commit = double('commit1', safe_message: "Fixes #{issue.to_reference}")
 | 
						|
        allow(subject).to receive(:commits).and_return([commit])
 | 
						|
 | 
						|
        expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to change(subject.merge_requests_closing_issues, :count)
 | 
						|
      end
 | 
						|
 | 
						|
      it 'does not cache an internal issue' do
 | 
						|
        issue  = create(:issue, project: subject.project)
 | 
						|
        commit = double('commit1', safe_message: "Fixes #{issue.to_reference}")
 | 
						|
        allow(subject).to receive(:commits).and_return([commit])
 | 
						|
 | 
						|
        expect { subject.cache_merge_request_closes_issues!(subject.author) }
 | 
						|
          .not_to change(subject.merge_requests_closing_issues, :count)
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#source_branch_sha' do
 | 
						|
    let(:last_branch_commit) { subject.source_project.repository.commit(Gitlab::Git::BRANCH_REF_PREFIX + subject.source_branch) }
 | 
						|
 | 
						|
    context 'with diffs' do
 | 
						|
      subject { create(:merge_request, :with_diffs) }
 | 
						|
      it 'returns the sha of the source branch last commit' do
 | 
						|
        expect(subject.source_branch_sha).to eq(last_branch_commit.sha)
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'without diffs' do
 | 
						|
      subject { create(:merge_request, :without_diffs) }
 | 
						|
      it 'returns the sha of the source branch last commit' do
 | 
						|
        expect(subject.source_branch_sha).to eq(last_branch_commit.sha)
 | 
						|
      end
 | 
						|
 | 
						|
      context 'when there is a tag name matching the branch name' do
 | 
						|
        let(:tag_name) { subject.source_branch }
 | 
						|
 | 
						|
        it 'returns the sha of the source branch last commit' do
 | 
						|
          subject.source_project.repository.add_tag(subject.author,
 | 
						|
                                                    tag_name,
 | 
						|
                                                    subject.target_branch_sha,
 | 
						|
                                                    'Add a tag')
 | 
						|
 | 
						|
          expect(subject.source_branch_sha).to eq(last_branch_commit.sha)
 | 
						|
 | 
						|
          subject.source_project.repository.rm_tag(subject.author, tag_name)
 | 
						|
        end
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when the merge request is being created' do
 | 
						|
      subject { build(:merge_request, source_branch: nil, compare_commits: []) }
 | 
						|
      it 'returns nil' do
 | 
						|
        expect(subject.source_branch_sha).to be_nil
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    it 'returns memoized value' do
 | 
						|
      subject.source_branch_sha = '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b'
 | 
						|
 | 
						|
      expect(subject.source_branch_sha).to eq '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b'
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#to_reference' do
 | 
						|
    let(:project) { build(:project, name: 'sample-project') }
 | 
						|
    let(:merge_request) { build(:merge_request, target_project: project, iid: 1) }
 | 
						|
 | 
						|
    it 'returns a String reference to the object' do
 | 
						|
      expect(merge_request.to_reference).to eq "!1"
 | 
						|
    end
 | 
						|
 | 
						|
    it 'supports a cross-project reference' do
 | 
						|
      another_project = build(:project, name: 'another-project', namespace: project.namespace)
 | 
						|
      expect(merge_request.to_reference(another_project)).to eq "sample-project!1"
 | 
						|
    end
 | 
						|
 | 
						|
    it 'returns a String reference with the full path' do
 | 
						|
      expect(merge_request.to_reference(full: true)).to eq(project.full_path + '!1')
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#raw_diffs' do
 | 
						|
    let(:merge_request) { build(:merge_request) }
 | 
						|
    let(:options) { { paths: ['a/b', 'b/a', 'c/*'] } }
 | 
						|
 | 
						|
    context 'when there are MR diffs' do
 | 
						|
      it 'delegates to the MR diffs' do
 | 
						|
        merge_request.merge_request_diff = MergeRequestDiff.new
 | 
						|
 | 
						|
        expect(merge_request.merge_request_diff).to receive(:raw_diffs).with(options)
 | 
						|
 | 
						|
        merge_request.raw_diffs(options)
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when there are no MR diffs' do
 | 
						|
      it 'delegates to the compare object' do
 | 
						|
        merge_request.compare = double(:compare)
 | 
						|
 | 
						|
        expect(merge_request.compare).to receive(:raw_diffs).with(options)
 | 
						|
 | 
						|
        merge_request.raw_diffs(options)
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#diffs' do
 | 
						|
    let(:merge_request) { build(:merge_request) }
 | 
						|
    let(:options) { { paths: ['a/b', 'b/a', 'c/*'] } }
 | 
						|
 | 
						|
    context 'when there are MR diffs' do
 | 
						|
      it 'delegates to the MR diffs' do
 | 
						|
        merge_request.save
 | 
						|
 | 
						|
        expect(merge_request.merge_request_diff).to receive(:raw_diffs).with(hash_including(options)).and_call_original
 | 
						|
 | 
						|
        merge_request.diffs(options).diff_files
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when there are no MR diffs' do
 | 
						|
      it 'delegates to the compare object, setting expanded: true' do
 | 
						|
        merge_request.compare = double(:compare)
 | 
						|
 | 
						|
        expect(merge_request.compare).to receive(:diffs).with(options.merge(expanded: true))
 | 
						|
 | 
						|
        merge_request.diffs(options)
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#preload_discussions_diff_highlight' do
 | 
						|
    let(:merge_request) { create(:merge_request) }
 | 
						|
 | 
						|
    context 'with commit diff note' do
 | 
						|
      let(:other_merge_request) { create(:merge_request) }
 | 
						|
 | 
						|
      let!(:diff_note) do
 | 
						|
        create(:diff_note_on_commit, project: merge_request.project)
 | 
						|
      end
 | 
						|
 | 
						|
      let!(:other_mr_diff_note) do
 | 
						|
        create(:diff_note_on_commit, project: other_merge_request.project)
 | 
						|
      end
 | 
						|
 | 
						|
      it 'preloads diff highlighting' do
 | 
						|
        expect_next_instance_of(Gitlab::DiscussionsDiff::FileCollection) do |collection|
 | 
						|
          note_diff_file = diff_note.note_diff_file
 | 
						|
 | 
						|
          expect(collection)
 | 
						|
            .to receive(:load_highlight)
 | 
						|
            .with([note_diff_file.id]).and_call_original
 | 
						|
        end
 | 
						|
 | 
						|
        merge_request.preload_discussions_diff_highlight
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'with merge request diff note' do
 | 
						|
      let!(:unresolved_diff_note) do
 | 
						|
        create(:diff_note_on_merge_request, project: merge_request.project, noteable: merge_request)
 | 
						|
      end
 | 
						|
 | 
						|
      let!(:resolved_diff_note) do
 | 
						|
        create(:diff_note_on_merge_request, :resolved, project: merge_request.project, noteable: merge_request)
 | 
						|
      end
 | 
						|
 | 
						|
      it 'preloads diff highlighting' do
 | 
						|
        expect_next_instance_of(Gitlab::DiscussionsDiff::FileCollection) do |collection|
 | 
						|
          note_diff_file = unresolved_diff_note.note_diff_file
 | 
						|
 | 
						|
          expect(collection)
 | 
						|
            .to receive(:load_highlight)
 | 
						|
            .with([note_diff_file.id])
 | 
						|
            .and_call_original
 | 
						|
        end
 | 
						|
 | 
						|
        merge_request.preload_discussions_diff_highlight
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#diff_size' do
 | 
						|
    let(:merge_request) do
 | 
						|
      build(:merge_request, source_branch: 'expand-collapse-files', target_branch: 'master')
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when there are MR diffs' do
 | 
						|
      it 'returns the correct count' do
 | 
						|
        merge_request.save
 | 
						|
 | 
						|
        expect(merge_request.diff_size).to eq('105')
 | 
						|
      end
 | 
						|
 | 
						|
      it 'returns the correct overflow count' do
 | 
						|
        allow(Commit).to receive(:max_diff_options).and_return(max_files: 2)
 | 
						|
        merge_request.save
 | 
						|
 | 
						|
        expect(merge_request.diff_size).to eq('2+')
 | 
						|
      end
 | 
						|
 | 
						|
      it 'does not perform highlighting' do
 | 
						|
        merge_request.save
 | 
						|
 | 
						|
        expect(Gitlab::Diff::Highlight).not_to receive(:new)
 | 
						|
 | 
						|
        merge_request.diff_size
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when there are no MR diffs' do
 | 
						|
      def set_compare(merge_request)
 | 
						|
        merge_request.compare = CompareService.new(
 | 
						|
          merge_request.source_project,
 | 
						|
          merge_request.source_branch
 | 
						|
        ).execute(
 | 
						|
          merge_request.target_project,
 | 
						|
          merge_request.target_branch
 | 
						|
        )
 | 
						|
      end
 | 
						|
 | 
						|
      it 'returns the correct count' do
 | 
						|
        set_compare(merge_request)
 | 
						|
 | 
						|
        expect(merge_request.diff_size).to eq('105')
 | 
						|
      end
 | 
						|
 | 
						|
      it 'returns the correct overflow count' do
 | 
						|
        allow(Commit).to receive(:max_diff_options).and_return(max_files: 2)
 | 
						|
        set_compare(merge_request)
 | 
						|
 | 
						|
        expect(merge_request.diff_size).to eq('2+')
 | 
						|
      end
 | 
						|
 | 
						|
      it 'does not perform highlighting' do
 | 
						|
        set_compare(merge_request)
 | 
						|
 | 
						|
        expect(Gitlab::Diff::Highlight).not_to receive(:new)
 | 
						|
 | 
						|
        merge_request.diff_size
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#modified_paths' do
 | 
						|
    let(:paths) { double(:paths) }
 | 
						|
    subject(:merge_request) { build(:merge_request) }
 | 
						|
 | 
						|
    before do
 | 
						|
      expect(diff).to receive(:modified_paths).and_return(paths)
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when past_merge_request_diff is specified' do
 | 
						|
      let(:another_diff) { double(:merge_request_diff) }
 | 
						|
      let(:diff) { another_diff }
 | 
						|
 | 
						|
      it 'returns affected file paths from specified past_merge_request_diff' do
 | 
						|
        expect(merge_request.modified_paths(past_merge_request_diff: another_diff)).to eq(paths)
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when compare is present' do
 | 
						|
      let(:compare) { double(:compare) }
 | 
						|
      let(:diff) { compare }
 | 
						|
 | 
						|
      it 'returns affected file paths from compare' do
 | 
						|
        merge_request.compare = compare
 | 
						|
 | 
						|
        expect(merge_request.modified_paths).to eq(paths)
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when no arguments provided' do
 | 
						|
      let(:diff) { merge_request.merge_request_diff }
 | 
						|
      subject(:merge_request) { create(:merge_request, source_branch: 'feature', target_branch: 'master') }
 | 
						|
 | 
						|
      it 'returns affected file paths for merge_request_diff' do
 | 
						|
        expect(merge_request.modified_paths).to eq(paths)
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe "#related_notes" do
 | 
						|
    let!(:merge_request) { create(:merge_request) }
 | 
						|
 | 
						|
    before do
 | 
						|
      allow(merge_request).to receive(:commits) { [merge_request.source_project.repository.commit] }
 | 
						|
      create(:note_on_commit, commit_id: merge_request.commits.first.id,
 | 
						|
                              project: merge_request.project)
 | 
						|
      create(:note, noteable: merge_request, project: merge_request.project)
 | 
						|
    end
 | 
						|
 | 
						|
    it "includes notes for commits" do
 | 
						|
      expect(merge_request.commits).not_to be_empty
 | 
						|
      expect(merge_request.related_notes.count).to eq(2)
 | 
						|
    end
 | 
						|
 | 
						|
    it "includes notes for commits from target project as well" do
 | 
						|
      create(:note_on_commit, commit_id: merge_request.commits.first.id,
 | 
						|
                              project: merge_request.target_project)
 | 
						|
 | 
						|
      expect(merge_request.commits).not_to be_empty
 | 
						|
      expect(merge_request.related_notes.count).to eq(3)
 | 
						|
    end
 | 
						|
 | 
						|
    it "excludes system notes for commits" do
 | 
						|
      system_note = create(:note_on_commit, :system, commit_id: merge_request.commits.first.id,
 | 
						|
                                                     project: merge_request.project)
 | 
						|
 | 
						|
      expect(merge_request.related_notes.count).to eq(2)
 | 
						|
      expect(merge_request.related_notes).not_to include(system_note)
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#for_fork?' do
 | 
						|
    it 'returns true if the merge request is for a fork' do
 | 
						|
      subject.source_project = build_stubbed(:project, namespace: create(:group))
 | 
						|
      subject.target_project = build_stubbed(:project, namespace: create(:group))
 | 
						|
 | 
						|
      expect(subject.for_fork?).to be_truthy
 | 
						|
    end
 | 
						|
 | 
						|
    it 'returns false if is not for a fork' do
 | 
						|
      expect(subject.for_fork?).to be_falsey
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#closes_issues' do
 | 
						|
    let(:issue0) { create :issue, project: subject.project }
 | 
						|
    let(:issue1) { create :issue, project: subject.project }
 | 
						|
 | 
						|
    let(:commit0) { double('commit0', safe_message: "Fixes #{issue0.to_reference}") }
 | 
						|
    let(:commit1) { double('commit1', safe_message: "Fixes #{issue0.to_reference}") }
 | 
						|
    let(:commit2) { double('commit2', safe_message: "Fixes #{issue1.to_reference}") }
 | 
						|
 | 
						|
    before do
 | 
						|
      subject.project.add_developer(subject.author)
 | 
						|
      allow(subject).to receive(:commits).and_return([commit0, commit1, commit2])
 | 
						|
    end
 | 
						|
 | 
						|
    it 'accesses the set of issues that will be closed on acceptance' do
 | 
						|
      allow(subject.project).to receive(:default_branch)
 | 
						|
        .and_return(subject.target_branch)
 | 
						|
 | 
						|
      closed = subject.closes_issues
 | 
						|
 | 
						|
      expect(closed).to include(issue0, issue1)
 | 
						|
    end
 | 
						|
 | 
						|
    it 'only lists issues as to be closed if it targets the default branch' do
 | 
						|
      allow(subject.project).to receive(:default_branch).and_return('master')
 | 
						|
      subject.target_branch = 'something-else'
 | 
						|
 | 
						|
      expect(subject.closes_issues).to be_empty
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#issues_mentioned_but_not_closing' do
 | 
						|
    let(:closing_issue) { create :issue, project: subject.project }
 | 
						|
    let(:mentioned_issue) { create :issue, project: subject.project }
 | 
						|
 | 
						|
    let(:commit) { double('commit', safe_message: "Fixes #{closing_issue.to_reference}") }
 | 
						|
 | 
						|
    it 'detects issues mentioned in description but not closed' do
 | 
						|
      subject.project.add_developer(subject.author)
 | 
						|
      subject.description = "Is related to #{mentioned_issue.to_reference} and #{closing_issue.to_reference}"
 | 
						|
 | 
						|
      allow(subject).to receive(:commits).and_return([commit])
 | 
						|
      allow(subject.project).to receive(:default_branch)
 | 
						|
        .and_return(subject.target_branch)
 | 
						|
      subject.cache_merge_request_closes_issues!
 | 
						|
 | 
						|
      expect(subject.issues_mentioned_but_not_closing(subject.author)).to match_array([mentioned_issue])
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when the project has an external issue tracker' do
 | 
						|
      before do
 | 
						|
        subject.project.add_developer(subject.author)
 | 
						|
        commit = double(:commit, safe_message: 'Fixes TEST-3')
 | 
						|
 | 
						|
        create(:jira_service, project: subject.project)
 | 
						|
 | 
						|
        allow(subject).to receive(:commits).and_return([commit])
 | 
						|
        allow(subject).to receive(:description).and_return('Is related to TEST-2 and TEST-3')
 | 
						|
        allow(subject.project).to receive(:default_branch).and_return(subject.target_branch)
 | 
						|
      end
 | 
						|
 | 
						|
      it 'detects issues mentioned in description but not closed' do
 | 
						|
        subject.cache_merge_request_closes_issues!
 | 
						|
 | 
						|
        expect(subject.issues_mentioned_but_not_closing(subject.author).map(&:to_s)).to match_array(['TEST-2'])
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe "#work_in_progress?" do
 | 
						|
    ['WIP ', 'WIP:', 'WIP: ', '[WIP]', '[WIP] ', ' [WIP] WIP [WIP] WIP: WIP '].each do |wip_prefix|
 | 
						|
      it "detects the '#{wip_prefix}' prefix" do
 | 
						|
        subject.title = "#{wip_prefix}#{subject.title}"
 | 
						|
        expect(subject.work_in_progress?).to eq true
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    it "doesn't detect WIP for words starting with WIP" do
 | 
						|
      subject.title = "Wipwap #{subject.title}"
 | 
						|
      expect(subject.work_in_progress?).to eq false
 | 
						|
    end
 | 
						|
 | 
						|
    it "doesn't detect WIP for words containing with WIP" do
 | 
						|
      subject.title = "WupWipwap #{subject.title}"
 | 
						|
      expect(subject.work_in_progress?).to eq false
 | 
						|
    end
 | 
						|
 | 
						|
    it "doesn't detect WIP by default" do
 | 
						|
      expect(subject.work_in_progress?).to eq false
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe "#wipless_title" do
 | 
						|
    ['WIP ', 'WIP:', 'WIP: ', '[WIP]', '[WIP] ', '[WIP] WIP [WIP] WIP: WIP '].each do |wip_prefix|
 | 
						|
      it "removes the '#{wip_prefix}' prefix" do
 | 
						|
        wipless_title = subject.title
 | 
						|
        subject.title = "#{wip_prefix}#{subject.title}"
 | 
						|
 | 
						|
        expect(subject.wipless_title).to eq wipless_title
 | 
						|
      end
 | 
						|
 | 
						|
      it "is satisfies the #work_in_progress? method" do
 | 
						|
        subject.title = "#{wip_prefix}#{subject.title}"
 | 
						|
        subject.title = subject.wipless_title
 | 
						|
 | 
						|
        expect(subject.work_in_progress?).to eq false
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe "#wip_title" do
 | 
						|
    it "adds the WIP: prefix to the title" do
 | 
						|
      wip_title = "WIP: #{subject.title}"
 | 
						|
 | 
						|
      expect(subject.wip_title).to eq wip_title
 | 
						|
    end
 | 
						|
 | 
						|
    it "does not add the WIP: prefix multiple times" do
 | 
						|
      wip_title = "WIP: #{subject.title}"
 | 
						|
      subject.title = subject.wip_title
 | 
						|
      subject.title = subject.wip_title
 | 
						|
 | 
						|
      expect(subject.wip_title).to eq wip_title
 | 
						|
    end
 | 
						|
 | 
						|
    it "is satisfies the #work_in_progress? method" do
 | 
						|
      subject.title = subject.wip_title
 | 
						|
 | 
						|
      expect(subject.work_in_progress?).to eq true
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#can_remove_source_branch?' do
 | 
						|
    set(:user) { create(:user) }
 | 
						|
    set(:merge_request) { create(:merge_request, :simple) }
 | 
						|
 | 
						|
    subject { merge_request }
 | 
						|
 | 
						|
    before do
 | 
						|
      subject.source_project.add_maintainer(user)
 | 
						|
    end
 | 
						|
 | 
						|
    it "can't be removed when its a protected branch" do
 | 
						|
      allow(ProtectedBranch).to receive(:protected?).and_return(true)
 | 
						|
 | 
						|
      expect(subject.can_remove_source_branch?(user)).to be_falsey
 | 
						|
    end
 | 
						|
 | 
						|
    it "can't remove a root ref" do
 | 
						|
      subject.update(source_branch: 'master', target_branch: 'feature')
 | 
						|
 | 
						|
      expect(subject.can_remove_source_branch?(user)).to be_falsey
 | 
						|
    end
 | 
						|
 | 
						|
    it "is unable to remove the source branch for a project the user cannot push to" do
 | 
						|
      user2 = create(:user)
 | 
						|
 | 
						|
      expect(subject.can_remove_source_branch?(user2)).to be_falsey
 | 
						|
    end
 | 
						|
 | 
						|
    it "can be removed if the last commit is the head of the source branch" do
 | 
						|
      allow(subject).to receive(:source_branch_head).and_return(subject.diff_head_commit)
 | 
						|
 | 
						|
      expect(subject.can_remove_source_branch?(user)).to be_truthy
 | 
						|
    end
 | 
						|
 | 
						|
    it "cannot be removed if the last commit is not also the head of the source branch" do
 | 
						|
      subject.clear_memoized_shas
 | 
						|
      subject.source_branch = "lfs"
 | 
						|
 | 
						|
      expect(subject.can_remove_source_branch?(user)).to be_falsey
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#default_merge_commit_message' do
 | 
						|
    it 'includes merge information as the title' do
 | 
						|
      request = build(:merge_request, source_branch: 'source', target_branch: 'target')
 | 
						|
 | 
						|
      expect(request.default_merge_commit_message)
 | 
						|
        .to match("Merge branch 'source' into 'target'\n\n")
 | 
						|
    end
 | 
						|
 | 
						|
    it 'includes its title in the body' do
 | 
						|
      request = build(:merge_request, title: 'Remove all technical debt')
 | 
						|
 | 
						|
      expect(request.default_merge_commit_message)
 | 
						|
        .to match("Remove all technical debt\n\n")
 | 
						|
    end
 | 
						|
 | 
						|
    it 'includes its closed issues in the body' do
 | 
						|
      issue = create(:issue, project: subject.project)
 | 
						|
 | 
						|
      subject.project.add_developer(subject.author)
 | 
						|
      subject.description = "This issue Closes #{issue.to_reference}"
 | 
						|
      allow(subject.project).to receive(:default_branch).and_return(subject.target_branch)
 | 
						|
      subject.cache_merge_request_closes_issues!
 | 
						|
 | 
						|
      expect(subject.default_merge_commit_message)
 | 
						|
        .to match("Closes #{issue.to_reference}")
 | 
						|
    end
 | 
						|
 | 
						|
    it 'includes its reference in the body' do
 | 
						|
      request = build_stubbed(:merge_request)
 | 
						|
 | 
						|
      expect(request.default_merge_commit_message)
 | 
						|
        .to match("See merge request #{request.to_reference(full: true)}")
 | 
						|
    end
 | 
						|
 | 
						|
    it 'excludes multiple linebreak runs when description is blank' do
 | 
						|
      request = build(:merge_request, title: 'Title', description: nil)
 | 
						|
 | 
						|
      expect(request.default_merge_commit_message).not_to match("Title\n\n\n\n")
 | 
						|
    end
 | 
						|
 | 
						|
    it 'includes its description in the body' do
 | 
						|
      request = build(:merge_request, description: 'By removing all code')
 | 
						|
 | 
						|
      expect(request.default_merge_commit_message(include_description: true))
 | 
						|
        .to match("By removing all code\n\n")
 | 
						|
    end
 | 
						|
 | 
						|
    it 'does not includes its description in the body' do
 | 
						|
      request = build(:merge_request, description: 'By removing all code')
 | 
						|
 | 
						|
      expect(request.default_merge_commit_message)
 | 
						|
        .not_to match("By removing all code\n\n")
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe "#auto_merge_strategy" do
 | 
						|
    subject { merge_request.auto_merge_strategy }
 | 
						|
 | 
						|
    let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) }
 | 
						|
 | 
						|
    it { is_expected.to eq('merge_when_pipeline_succeeds') }
 | 
						|
 | 
						|
    context 'when auto merge is disabled' do
 | 
						|
      let(:merge_request) { create(:merge_request) }
 | 
						|
 | 
						|
      it { is_expected.to be_nil }
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#committers' do
 | 
						|
    it 'returns all the committers of every commit in the merge request' do
 | 
						|
      users = subject.commits.without_merge_commits.map(&:committer_email).uniq.map do |email|
 | 
						|
        create(:user, email: email)
 | 
						|
      end
 | 
						|
 | 
						|
      expect(subject.committers).to match_array(users)
 | 
						|
    end
 | 
						|
 | 
						|
    it 'returns an empty array if no committer is associated with a user' do
 | 
						|
      expect(subject.committers).to be_empty
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#hook_attrs' do
 | 
						|
    it 'delegates to Gitlab::HookData::MergeRequestBuilder#build' do
 | 
						|
      builder = double
 | 
						|
 | 
						|
      expect(Gitlab::HookData::MergeRequestBuilder)
 | 
						|
        .to receive(:new).with(subject).and_return(builder)
 | 
						|
      expect(builder).to receive(:build)
 | 
						|
 | 
						|
      subject.hook_attrs
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#diverged_commits_count' do
 | 
						|
    let(:project) { create(:project, :repository) }
 | 
						|
    let(:forked_project) { fork_project(project, nil, repository: true) }
 | 
						|
 | 
						|
    context 'when the target branch does not exist anymore' do
 | 
						|
      subject { create(:merge_request, source_project: project, target_project: project) }
 | 
						|
 | 
						|
      before do
 | 
						|
        project.repository.raw_repository.delete_branch(subject.target_branch)
 | 
						|
        subject.clear_memoized_shas
 | 
						|
      end
 | 
						|
 | 
						|
      it 'does not crash' do
 | 
						|
        expect { subject.diverged_commits_count }.not_to raise_error
 | 
						|
      end
 | 
						|
 | 
						|
      it 'returns 0' do
 | 
						|
        expect(subject.diverged_commits_count).to eq(0)
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'diverged on same repository' do
 | 
						|
      subject(:merge_request_with_divergence) { create(:merge_request, :diverged, source_project: project, target_project: project) }
 | 
						|
 | 
						|
      it 'counts commits that are on target branch but not on source branch' do
 | 
						|
        expect(subject.diverged_commits_count).to eq(29)
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'diverged on fork' do
 | 
						|
      subject(:merge_request_fork_with_divergence) { create(:merge_request, :diverged, source_project: forked_project, target_project: project) }
 | 
						|
 | 
						|
      it 'counts commits that are on target branch but not on source branch' do
 | 
						|
        expect(subject.diverged_commits_count).to eq(29)
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'rebased on fork' do
 | 
						|
      subject(:merge_request_rebased) { create(:merge_request, :rebased, source_project: forked_project, target_project: project) }
 | 
						|
 | 
						|
      it 'counts commits that are on target branch but not on source branch' do
 | 
						|
        expect(subject.diverged_commits_count).to eq(0)
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    describe 'caching' do
 | 
						|
      before do
 | 
						|
        allow(Rails).to receive(:cache).and_return(ActiveSupport::Cache::MemoryStore.new)
 | 
						|
      end
 | 
						|
 | 
						|
      it 'caches the output' do
 | 
						|
        expect(subject).to receive(:compute_diverged_commits_count)
 | 
						|
          .once
 | 
						|
          .and_return(2)
 | 
						|
 | 
						|
        subject.diverged_commits_count
 | 
						|
        subject.diverged_commits_count
 | 
						|
      end
 | 
						|
 | 
						|
      it 'invalidates the cache when the source sha changes' do
 | 
						|
        expect(subject).to receive(:compute_diverged_commits_count)
 | 
						|
          .twice
 | 
						|
          .and_return(2)
 | 
						|
 | 
						|
        subject.diverged_commits_count
 | 
						|
        allow(subject).to receive(:source_branch_sha).and_return('123abc')
 | 
						|
        subject.diverged_commits_count
 | 
						|
      end
 | 
						|
 | 
						|
      it 'invalidates the cache when the target sha changes' do
 | 
						|
        expect(subject).to receive(:compute_diverged_commits_count)
 | 
						|
          .twice
 | 
						|
          .and_return(2)
 | 
						|
 | 
						|
        subject.diverged_commits_count
 | 
						|
        allow(subject).to receive(:target_branch_sha).and_return('123abc')
 | 
						|
        subject.diverged_commits_count
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  it_behaves_like 'an editable mentionable' do
 | 
						|
    subject { create(:merge_request, :simple) }
 | 
						|
 | 
						|
    let(:backref_text) { "merge request #{subject.to_reference}" }
 | 
						|
    let(:set_mentionable_text) { ->(txt) { subject.description = txt } }
 | 
						|
  end
 | 
						|
 | 
						|
  it_behaves_like 'a Taskable' do
 | 
						|
    subject { create :merge_request, :simple }
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#commit_shas' do
 | 
						|
    before do
 | 
						|
      allow(subject.merge_request_diff).to receive(:commit_shas)
 | 
						|
        .and_return(['sha1'])
 | 
						|
    end
 | 
						|
 | 
						|
    it 'delegates to merge request diff' do
 | 
						|
      expect(subject.commit_shas).to eq ['sha1']
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  context 'head pipeline' do
 | 
						|
    let(:diff_head_sha) { Digest::SHA1.hexdigest(SecureRandom.hex) }
 | 
						|
 | 
						|
    before do
 | 
						|
      allow(subject).to receive(:diff_head_sha).and_return(diff_head_sha)
 | 
						|
    end
 | 
						|
 | 
						|
    describe '#head_pipeline' do
 | 
						|
      it 'returns nil for MR without head_pipeline_id' do
 | 
						|
        subject.update_attribute(:head_pipeline_id, nil)
 | 
						|
 | 
						|
        expect(subject.head_pipeline).to be_nil
 | 
						|
      end
 | 
						|
 | 
						|
      context 'when the source project does not exist' do
 | 
						|
        it 'returns nil' do
 | 
						|
          allow(subject).to receive(:source_project).and_return(nil)
 | 
						|
 | 
						|
          expect(subject.head_pipeline).to be_nil
 | 
						|
        end
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    describe '#actual_head_pipeline' do
 | 
						|
      it 'returns nil for MR with old pipeline' do
 | 
						|
        pipeline = create(:ci_empty_pipeline, sha: 'notlatestsha')
 | 
						|
        subject.update_attribute(:head_pipeline_id, pipeline.id)
 | 
						|
 | 
						|
        expect(subject.actual_head_pipeline).to be_nil
 | 
						|
      end
 | 
						|
 | 
						|
      it 'returns the pipeline for MR with recent pipeline' do
 | 
						|
        pipeline = create(:ci_empty_pipeline, sha: diff_head_sha)
 | 
						|
        subject.update_attribute(:head_pipeline_id, pipeline.id)
 | 
						|
 | 
						|
        expect(subject.actual_head_pipeline).to eq(subject.head_pipeline)
 | 
						|
        expect(subject.actual_head_pipeline).to eq(pipeline)
 | 
						|
      end
 | 
						|
 | 
						|
      it 'returns the pipeline for MR with recent merge request pipeline' do
 | 
						|
        pipeline = create(:ci_empty_pipeline, sha: 'merge-sha', source_sha: diff_head_sha)
 | 
						|
        subject.update_attribute(:head_pipeline_id, pipeline.id)
 | 
						|
 | 
						|
        expect(subject.actual_head_pipeline).to eq(subject.head_pipeline)
 | 
						|
        expect(subject.actual_head_pipeline).to eq(pipeline)
 | 
						|
      end
 | 
						|
 | 
						|
      it 'returns nil when source project does not exist' do
 | 
						|
        allow(subject).to receive(:source_project).and_return(nil)
 | 
						|
 | 
						|
        expect(subject.actual_head_pipeline).to be_nil
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#merge_pipeline' do
 | 
						|
    it 'returns nil when not merged' do
 | 
						|
      expect(subject.merge_pipeline).to be_nil
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when the MR is merged' do
 | 
						|
      let(:sha)      { subject.target_project.commit.id }
 | 
						|
      let(:pipeline) { create(:ci_empty_pipeline, sha: sha, ref: subject.target_branch, project: subject.target_project) }
 | 
						|
 | 
						|
      before do
 | 
						|
        subject.mark_as_merged!
 | 
						|
        subject.update_attribute(:merge_commit_sha, pipeline.sha)
 | 
						|
      end
 | 
						|
 | 
						|
      it 'returns the post-merge pipeline' do
 | 
						|
        expect(subject.merge_pipeline).to eq(pipeline)
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#has_ci?' do
 | 
						|
    let(:merge_request) { build_stubbed(:merge_request) }
 | 
						|
 | 
						|
    context 'has ci' do
 | 
						|
      it 'returns true if MR has head_pipeline_id and commits' do
 | 
						|
        allow(merge_request).to receive_message_chain(:source_project, :ci_service) { nil }
 | 
						|
        allow(merge_request).to receive(:head_pipeline_id) { double }
 | 
						|
        allow(merge_request).to receive(:has_no_commits?) { false }
 | 
						|
 | 
						|
        expect(merge_request.has_ci?).to be(true)
 | 
						|
      end
 | 
						|
 | 
						|
      it 'returns true if MR has any pipeline and commits' do
 | 
						|
        allow(merge_request).to receive_message_chain(:source_project, :ci_service) { nil }
 | 
						|
        allow(merge_request).to receive(:head_pipeline_id) { nil }
 | 
						|
        allow(merge_request).to receive(:has_no_commits?) { false }
 | 
						|
        allow(merge_request).to receive(:all_pipelines) { [double] }
 | 
						|
 | 
						|
        expect(merge_request.has_ci?).to be(true)
 | 
						|
      end
 | 
						|
 | 
						|
      it 'returns true if MR has CI service and commits' do
 | 
						|
        allow(merge_request).to receive_message_chain(:source_project, :ci_service) { double }
 | 
						|
        allow(merge_request).to receive(:head_pipeline_id) { nil }
 | 
						|
        allow(merge_request).to receive(:has_no_commits?) { false }
 | 
						|
        allow(merge_request).to receive(:all_pipelines) { [] }
 | 
						|
 | 
						|
        expect(merge_request.has_ci?).to be(true)
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'has no ci' do
 | 
						|
      it 'returns false if MR has no CI service nor pipeline, and no commits' do
 | 
						|
        allow(merge_request).to receive_message_chain(:source_project, :ci_service) { nil }
 | 
						|
        allow(merge_request).to receive(:head_pipeline_id) { nil }
 | 
						|
        allow(merge_request).to receive(:all_pipelines) { [] }
 | 
						|
        allow(merge_request).to receive(:has_no_commits?) { true }
 | 
						|
 | 
						|
        expect(merge_request.has_ci?).to be(false)
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#all_pipelines' do
 | 
						|
    shared_examples 'returning pipelines with proper ordering' do
 | 
						|
      let!(:all_pipelines) do
 | 
						|
        subject.all_commit_shas.map do |sha|
 | 
						|
          create(:ci_empty_pipeline,
 | 
						|
                 project: subject.source_project,
 | 
						|
                 sha: sha,
 | 
						|
                 ref: subject.source_branch)
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      it 'returns all pipelines' do
 | 
						|
        expect(subject.all_pipelines).not_to be_empty
 | 
						|
        expect(subject.all_pipelines).to eq(all_pipelines.reverse)
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'with single merge_request_diffs' do
 | 
						|
      it_behaves_like 'returning pipelines with proper ordering'
 | 
						|
    end
 | 
						|
 | 
						|
    context 'with multiple irrelevant merge_request_diffs' do
 | 
						|
      before do
 | 
						|
        subject.update(target_branch: 'v1.0.0')
 | 
						|
      end
 | 
						|
 | 
						|
      it_behaves_like 'returning pipelines with proper ordering'
 | 
						|
    end
 | 
						|
 | 
						|
    context 'with unsaved merge request' do
 | 
						|
      subject { build(:merge_request) }
 | 
						|
 | 
						|
      let!(:pipeline) do
 | 
						|
        create(:ci_empty_pipeline,
 | 
						|
               project: subject.project,
 | 
						|
               sha: subject.diff_head_sha,
 | 
						|
               ref: subject.source_branch)
 | 
						|
      end
 | 
						|
 | 
						|
      it 'returns pipelines from diff_head_sha' do
 | 
						|
        expect(subject.all_pipelines).to contain_exactly(pipeline)
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when pipelines exist for the branch and merge request' do
 | 
						|
      let(:source_ref) { 'feature' }
 | 
						|
      let(:target_ref) { 'master' }
 | 
						|
 | 
						|
      let!(:branch_pipeline) do
 | 
						|
        create(:ci_pipeline,
 | 
						|
               source: :push,
 | 
						|
               project: project,
 | 
						|
               ref: source_ref,
 | 
						|
               sha: shas.second)
 | 
						|
      end
 | 
						|
 | 
						|
      let!(:detached_merge_request_pipeline) do
 | 
						|
        create(:ci_pipeline,
 | 
						|
               source: :merge_request_event,
 | 
						|
               project: project,
 | 
						|
               ref: source_ref,
 | 
						|
               sha: shas.second,
 | 
						|
               merge_request: merge_request)
 | 
						|
      end
 | 
						|
 | 
						|
      let(:merge_request) do
 | 
						|
        create(:merge_request,
 | 
						|
               source_project: project,
 | 
						|
               source_branch: source_ref,
 | 
						|
               target_project: project,
 | 
						|
               target_branch: target_ref)
 | 
						|
      end
 | 
						|
 | 
						|
      let(:project) { create(:project, :repository) }
 | 
						|
      let(:shas) { project.repository.commits(source_ref, limit: 2).map(&:id) }
 | 
						|
 | 
						|
      before do
 | 
						|
        allow(merge_request).to receive(:all_commit_shas) { shas }
 | 
						|
      end
 | 
						|
 | 
						|
      it 'returns merge request pipeline first' do
 | 
						|
        expect(merge_request.all_pipelines)
 | 
						|
          .to eq([detached_merge_request_pipeline,
 | 
						|
                  branch_pipeline])
 | 
						|
      end
 | 
						|
 | 
						|
      context 'when there are a branch pipeline and a merge request pipeline' do
 | 
						|
        let!(:branch_pipeline_2) do
 | 
						|
          create(:ci_pipeline,
 | 
						|
                 source: :push,
 | 
						|
                 project: project,
 | 
						|
                 ref: source_ref,
 | 
						|
                 sha: shas.first)
 | 
						|
        end
 | 
						|
 | 
						|
        let!(:detached_merge_request_pipeline_2) do
 | 
						|
          create(:ci_pipeline,
 | 
						|
                 source: :merge_request_event,
 | 
						|
                 project: project,
 | 
						|
                 ref: source_ref,
 | 
						|
                 sha: shas.first,
 | 
						|
                 merge_request: merge_request)
 | 
						|
        end
 | 
						|
 | 
						|
        it 'returns merge request pipelines first' do
 | 
						|
          expect(merge_request.all_pipelines)
 | 
						|
            .to eq([detached_merge_request_pipeline_2,
 | 
						|
                    detached_merge_request_pipeline,
 | 
						|
                    branch_pipeline_2,
 | 
						|
                    branch_pipeline])
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      context 'when there are multiple merge request pipelines from the same branch' do
 | 
						|
        let!(:branch_pipeline_2) do
 | 
						|
          create(:ci_pipeline,
 | 
						|
                 source: :push,
 | 
						|
                 project: project,
 | 
						|
                 ref: source_ref,
 | 
						|
                 sha: shas.first)
 | 
						|
        end
 | 
						|
 | 
						|
        let!(:detached_merge_request_pipeline_2) do
 | 
						|
          create(:ci_pipeline,
 | 
						|
                 source: :merge_request_event,
 | 
						|
                 project: project,
 | 
						|
                 ref: source_ref,
 | 
						|
                 sha: shas.first,
 | 
						|
                 merge_request: merge_request_2)
 | 
						|
        end
 | 
						|
 | 
						|
        let(:merge_request_2) do
 | 
						|
          create(:merge_request,
 | 
						|
                 source_project: project,
 | 
						|
                 source_branch: source_ref,
 | 
						|
                 target_project: project,
 | 
						|
                 target_branch: 'stable')
 | 
						|
        end
 | 
						|
 | 
						|
        before do
 | 
						|
          allow(merge_request_2).to receive(:all_commit_shas) { shas }
 | 
						|
        end
 | 
						|
 | 
						|
        it 'returns only related merge request pipelines' do
 | 
						|
          expect(merge_request.all_pipelines)
 | 
						|
            .to eq([detached_merge_request_pipeline,
 | 
						|
                    branch_pipeline_2,
 | 
						|
                    branch_pipeline])
 | 
						|
 | 
						|
          expect(merge_request_2.all_pipelines)
 | 
						|
            .to eq([detached_merge_request_pipeline_2,
 | 
						|
                    branch_pipeline_2,
 | 
						|
                    branch_pipeline])
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      context 'when detached merge request pipeline is run on head ref of the merge request' do
 | 
						|
        let!(:detached_merge_request_pipeline) do
 | 
						|
          create(:ci_pipeline,
 | 
						|
                 source: :merge_request_event,
 | 
						|
                 project: project,
 | 
						|
                 ref: merge_request.ref_path,
 | 
						|
                 sha: shas.second,
 | 
						|
                 merge_request: merge_request)
 | 
						|
        end
 | 
						|
 | 
						|
        it 'sets the head ref of the merge request to the pipeline ref' do
 | 
						|
          expect(detached_merge_request_pipeline.ref).to match(%r{refs/merge-requests/\d+/head})
 | 
						|
        end
 | 
						|
 | 
						|
        it 'includes the detached merge request pipeline even though the ref is custom path' do
 | 
						|
          expect(merge_request.all_pipelines).to include(detached_merge_request_pipeline)
 | 
						|
        end
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#update_head_pipeline' do
 | 
						|
    subject { merge_request.update_head_pipeline }
 | 
						|
 | 
						|
    let(:merge_request) { create(:merge_request) }
 | 
						|
 | 
						|
    context 'when there is a pipeline with the diff head sha' do
 | 
						|
      let!(:pipeline) do
 | 
						|
        create(:ci_empty_pipeline,
 | 
						|
               project: merge_request.project,
 | 
						|
               sha: merge_request.diff_head_sha,
 | 
						|
               ref: merge_request.source_branch)
 | 
						|
      end
 | 
						|
 | 
						|
      it 'updates the head pipeline' do
 | 
						|
        expect { subject }
 | 
						|
          .to change { merge_request.reload.head_pipeline }
 | 
						|
          .from(nil).to(pipeline)
 | 
						|
      end
 | 
						|
 | 
						|
      context 'when merge request has already had head pipeline' do
 | 
						|
        before do
 | 
						|
          merge_request.update!(head_pipeline: pipeline)
 | 
						|
        end
 | 
						|
 | 
						|
        context 'when failed to find an actual head pipeline' do
 | 
						|
          before do
 | 
						|
            allow(merge_request).to receive(:find_actual_head_pipeline) { }
 | 
						|
          end
 | 
						|
 | 
						|
          it 'does not update the current head pipeline' do
 | 
						|
            expect { subject }
 | 
						|
              .not_to change { merge_request.reload.head_pipeline }
 | 
						|
          end
 | 
						|
        end
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when detached merge request pipeline is run on head ref of the merge request' do
 | 
						|
      let!(:pipeline) do
 | 
						|
        create(:ci_pipeline,
 | 
						|
               source: :merge_request_event,
 | 
						|
               project: merge_request.source_project,
 | 
						|
               ref: merge_request.ref_path,
 | 
						|
               sha: sha,
 | 
						|
               merge_request: merge_request)
 | 
						|
      end
 | 
						|
 | 
						|
      let(:sha) { merge_request.diff_head_sha }
 | 
						|
 | 
						|
      it 'sets the head ref of the merge request to the pipeline ref' do
 | 
						|
        expect(pipeline.ref).to match(%r{refs/merge-requests/\d+/head})
 | 
						|
      end
 | 
						|
 | 
						|
      it 'updates correctly even though the target branch name of the merge request is different from the pipeline ref' do
 | 
						|
        expect { subject }
 | 
						|
          .to change { merge_request.reload.head_pipeline }
 | 
						|
          .from(nil).to(pipeline)
 | 
						|
      end
 | 
						|
 | 
						|
      context 'when sha is not HEAD of the source branch' do
 | 
						|
        let(:sha) { merge_request.diff_base_sha }
 | 
						|
 | 
						|
        it 'does not update head pipeline' do
 | 
						|
          expect { subject }.not_to change { merge_request.reload.head_pipeline }
 | 
						|
        end
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when there are no pipelines with the diff head sha' do
 | 
						|
      it 'does not update the head pipeline' do
 | 
						|
        expect { subject }
 | 
						|
          .not_to change { merge_request.reload.head_pipeline }
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#has_test_reports?' do
 | 
						|
    subject { merge_request.has_test_reports? }
 | 
						|
 | 
						|
    let(:project) { create(:project, :repository) }
 | 
						|
 | 
						|
    context 'when head pipeline has test reports' do
 | 
						|
      let(:merge_request) { create(:merge_request, :with_test_reports, source_project: project) }
 | 
						|
 | 
						|
      it { is_expected.to be_truthy }
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when head pipeline does not have test reports' do
 | 
						|
      let(:merge_request) { create(:merge_request, source_project: project) }
 | 
						|
 | 
						|
      it { is_expected.to be_falsey }
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#calculate_reactive_cache' do
 | 
						|
    let(:project) { create(:project, :repository) }
 | 
						|
    let(:merge_request) { create(:merge_request, source_project: project) }
 | 
						|
    subject { merge_request.calculate_reactive_cache(service_class_name) }
 | 
						|
 | 
						|
    context 'when given an unknown service class name' do
 | 
						|
      let(:service_class_name) { 'Integer' }
 | 
						|
 | 
						|
      it 'raises a NameError exception' do
 | 
						|
        expect { subject }.to raise_error(NameError, service_class_name)
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when given a known service class name' do
 | 
						|
      let(:service_class_name) { 'Ci::CompareTestReportsService' }
 | 
						|
 | 
						|
      it 'does not raises a NameError exception' do
 | 
						|
        allow_any_instance_of(service_class_name.constantize).to receive(:execute).and_return(nil)
 | 
						|
 | 
						|
        expect { subject }.not_to raise_error
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#compare_test_reports' do
 | 
						|
    subject { merge_request.compare_test_reports }
 | 
						|
 | 
						|
    let(:project) { create(:project, :repository) }
 | 
						|
    let(:merge_request) { create(:merge_request, source_project: project) }
 | 
						|
 | 
						|
    let!(:base_pipeline) do
 | 
						|
      create(:ci_pipeline,
 | 
						|
             :with_test_reports,
 | 
						|
             project: project,
 | 
						|
             ref: merge_request.target_branch,
 | 
						|
             sha: merge_request.diff_base_sha)
 | 
						|
    end
 | 
						|
 | 
						|
    before do
 | 
						|
      merge_request.update!(head_pipeline_id: head_pipeline.id)
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when head pipeline has test reports' do
 | 
						|
      let!(:head_pipeline) do
 | 
						|
        create(:ci_pipeline,
 | 
						|
               :with_test_reports,
 | 
						|
               project: project,
 | 
						|
               ref: merge_request.source_branch,
 | 
						|
               sha: merge_request.diff_head_sha)
 | 
						|
      end
 | 
						|
 | 
						|
      context 'when reactive cache worker is parsing asynchronously' do
 | 
						|
        it 'returns status' do
 | 
						|
          expect(subject[:status]).to eq(:parsing)
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      context 'when reactive cache worker is inline' do
 | 
						|
        before do
 | 
						|
          synchronous_reactive_cache(merge_request)
 | 
						|
        end
 | 
						|
 | 
						|
        it 'returns status and data' do
 | 
						|
          expect_any_instance_of(Ci::CompareTestReportsService)
 | 
						|
            .to receive(:execute).with(base_pipeline, head_pipeline).and_call_original
 | 
						|
 | 
						|
          subject
 | 
						|
        end
 | 
						|
 | 
						|
        context 'when cached results is not latest' do
 | 
						|
          before do
 | 
						|
            allow_any_instance_of(Ci::CompareTestReportsService)
 | 
						|
              .to receive(:latest?).and_return(false)
 | 
						|
          end
 | 
						|
 | 
						|
          it 'raises and InvalidateReactiveCache error' do
 | 
						|
            expect { subject }.to raise_error(ReactiveCaching::InvalidateReactiveCache)
 | 
						|
          end
 | 
						|
        end
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when head pipeline does not have test reports' do
 | 
						|
      let!(:head_pipeline) do
 | 
						|
        create(:ci_pipeline,
 | 
						|
               project: project,
 | 
						|
               ref: merge_request.source_branch,
 | 
						|
               sha: merge_request.diff_head_sha)
 | 
						|
      end
 | 
						|
 | 
						|
      it 'returns status and error message' do
 | 
						|
        expect(subject[:status]).to eq(:error)
 | 
						|
        expect(subject[:status_reason]).to eq('This merge request does not have test reports')
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#all_commit_shas' do
 | 
						|
    context 'when merge request is persisted' do
 | 
						|
      let(:all_commit_shas) do
 | 
						|
        subject.merge_request_diffs.flat_map(&:commits).map(&:sha).uniq
 | 
						|
      end
 | 
						|
 | 
						|
      shared_examples 'returning all SHA' do
 | 
						|
        it 'returns all SHAs from all merge_request_diffs' do
 | 
						|
          expect(subject.merge_request_diffs.size).to eq(2)
 | 
						|
          expect(subject.all_commit_shas).to match_array(all_commit_shas)
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      context 'with a completely different branch' do
 | 
						|
        before do
 | 
						|
          subject.update(target_branch: 'csv')
 | 
						|
        end
 | 
						|
 | 
						|
        it_behaves_like 'returning all SHA'
 | 
						|
      end
 | 
						|
 | 
						|
      context 'with a branch having no difference' do
 | 
						|
        before do
 | 
						|
          subject.update(target_branch: 'branch-merged')
 | 
						|
          subject.reload # make sure commits were not cached
 | 
						|
        end
 | 
						|
 | 
						|
        it_behaves_like 'returning all SHA'
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when merge request is not persisted' do
 | 
						|
      context 'when compare commits are set in the service' do
 | 
						|
        let(:commit) { spy('commit') }
 | 
						|
 | 
						|
        subject do
 | 
						|
          build(:merge_request, compare_commits: [commit, commit])
 | 
						|
        end
 | 
						|
 | 
						|
        it 'returns commits from compare commits temporary data' do
 | 
						|
          expect(subject.all_commit_shas).to eq [commit, commit]
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      context 'when compare commits are not set in the service' do
 | 
						|
        subject { build(:merge_request) }
 | 
						|
 | 
						|
        it 'returns array with diff head sha element only' do
 | 
						|
          expect(subject.all_commit_shas).to eq [subject.diff_head_sha]
 | 
						|
        end
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#short_merge_commit_sha' do
 | 
						|
    let(:merge_request) { build_stubbed(:merge_request) }
 | 
						|
 | 
						|
    it 'returns short id when there is a merge_commit_sha' do
 | 
						|
      merge_request.merge_commit_sha = 'f7ce827c314c9340b075657fd61c789fb01cf74d'
 | 
						|
 | 
						|
      expect(merge_request.short_merge_commit_sha).to eq('f7ce827c')
 | 
						|
    end
 | 
						|
 | 
						|
    it 'returns nil when there is no merge_commit_sha' do
 | 
						|
      merge_request.merge_commit_sha = nil
 | 
						|
 | 
						|
      expect(merge_request.short_merge_commit_sha).to be_nil
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#can_be_reverted?' do
 | 
						|
    context 'when there is no merge_commit for the MR' do
 | 
						|
      before do
 | 
						|
        subject.metrics.update!(merged_at: Time.now.utc)
 | 
						|
      end
 | 
						|
 | 
						|
      it 'returns false' do
 | 
						|
        expect(subject.can_be_reverted?(nil)).to be_falsey
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when the MR has been merged' do
 | 
						|
      before do
 | 
						|
        MergeRequests::MergeService
 | 
						|
          .new(subject.target_project, subject.author)
 | 
						|
          .execute(subject)
 | 
						|
      end
 | 
						|
 | 
						|
      context 'when there is no revert commit' do
 | 
						|
        it 'returns true' do
 | 
						|
          expect(subject.can_be_reverted?(nil)).to be_truthy
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      context 'when there is no merged_at for the MR' do
 | 
						|
        before do
 | 
						|
          subject.metrics.update!(merged_at: nil)
 | 
						|
        end
 | 
						|
 | 
						|
        it 'returns true' do
 | 
						|
          expect(subject.can_be_reverted?(nil)).to be_truthy
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      context 'when there is a revert commit' do
 | 
						|
        let(:current_user) { subject.author }
 | 
						|
        let(:branch) { subject.target_branch }
 | 
						|
        let(:project) { subject.target_project }
 | 
						|
 | 
						|
        let(:revert_commit_id) do
 | 
						|
          params = {
 | 
						|
            commit: subject.merge_commit,
 | 
						|
            branch_name: branch,
 | 
						|
            start_branch: branch
 | 
						|
          }
 | 
						|
 | 
						|
          Commits::RevertService.new(project, current_user, params).execute[:result]
 | 
						|
        end
 | 
						|
 | 
						|
        before do
 | 
						|
          project.add_maintainer(current_user)
 | 
						|
 | 
						|
          ProcessCommitWorker.new.perform(project.id,
 | 
						|
                                          current_user.id,
 | 
						|
                                          project.commit(revert_commit_id).to_hash,
 | 
						|
                                          project.default_branch == branch)
 | 
						|
        end
 | 
						|
 | 
						|
        context 'but merged at timestamp cannot be found' do
 | 
						|
          before do
 | 
						|
            allow(subject).to receive(:merged_at) { nil }
 | 
						|
          end
 | 
						|
 | 
						|
          it 'returns false' do
 | 
						|
            expect(subject.can_be_reverted?(current_user)).to be_falsey
 | 
						|
          end
 | 
						|
        end
 | 
						|
 | 
						|
        context 'when the revert commit is mentioned in a note after the MR was merged' do
 | 
						|
          it 'returns false' do
 | 
						|
            expect(subject.can_be_reverted?(current_user)).to be_falsey
 | 
						|
          end
 | 
						|
        end
 | 
						|
 | 
						|
        context 'when there is no merged_at for the MR' do
 | 
						|
          before do
 | 
						|
            subject.metrics.update!(merged_at: nil)
 | 
						|
          end
 | 
						|
 | 
						|
          it 'returns false' do
 | 
						|
            expect(subject.can_be_reverted?(current_user)).to be_falsey
 | 
						|
          end
 | 
						|
        end
 | 
						|
 | 
						|
        context 'when the revert commit is mentioned in a note just before the MR was merged' do
 | 
						|
          before do
 | 
						|
            subject.notes.last.update!(created_at: subject.metrics.merged_at - 30.seconds)
 | 
						|
          end
 | 
						|
 | 
						|
          it 'returns false' do
 | 
						|
            expect(subject.can_be_reverted?(current_user)).to be_falsey
 | 
						|
          end
 | 
						|
        end
 | 
						|
 | 
						|
        context 'when the revert commit is mentioned in a note long before the MR was merged' do
 | 
						|
          before do
 | 
						|
            subject.notes.last.update!(created_at: subject.metrics.merged_at - 2.minutes)
 | 
						|
          end
 | 
						|
 | 
						|
          it 'returns true' do
 | 
						|
            expect(subject.can_be_reverted?(current_user)).to be_truthy
 | 
						|
          end
 | 
						|
        end
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#merged_at' do
 | 
						|
    context 'when MR is not merged' do
 | 
						|
      let(:merge_request) { create(:merge_request, :closed) }
 | 
						|
 | 
						|
      it 'returns nil' do
 | 
						|
        expect(merge_request.merged_at).to be_nil
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when metrics has merged_at data' do
 | 
						|
      let(:merge_request) { create(:merge_request, :merged) }
 | 
						|
 | 
						|
      before do
 | 
						|
        merge_request.metrics.update!(merged_at: 1.day.ago)
 | 
						|
      end
 | 
						|
 | 
						|
      it 'returns metrics merged_at' do
 | 
						|
        expect(merge_request.merged_at).to eq(merge_request.metrics.merged_at)
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when merged event is persisted, but no metrics merged_at is persisted' do
 | 
						|
      let(:user) { create(:user) }
 | 
						|
      let(:merge_request) { create(:merge_request, :merged) }
 | 
						|
 | 
						|
      before do
 | 
						|
        EventCreateService.new.merge_mr(merge_request, user)
 | 
						|
      end
 | 
						|
 | 
						|
      it 'returns merged event creation date' do
 | 
						|
        expect(merge_request.merge_event).to be_persisted
 | 
						|
        expect(merge_request.merged_at).to eq(merge_request.merge_event.created_at)
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when merging note is persisted, but no metrics or merge event exists' do
 | 
						|
      let(:user) { create(:user) }
 | 
						|
      let(:merge_request) { create(:merge_request, :merged) }
 | 
						|
 | 
						|
      before do
 | 
						|
        merge_request.metrics.destroy!
 | 
						|
 | 
						|
        SystemNoteService.change_status(merge_request,
 | 
						|
                                        merge_request.target_project,
 | 
						|
                                        user,
 | 
						|
                                        merge_request.state, nil)
 | 
						|
      end
 | 
						|
 | 
						|
      it 'returns merging note creation date' do
 | 
						|
        expect(merge_request.reload.metrics).to be_nil
 | 
						|
        expect(merge_request.merge_event).to be_nil
 | 
						|
        expect(merge_request.notes.count).to eq(1)
 | 
						|
        expect(merge_request.merged_at).to eq(merge_request.notes.first.created_at)
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#participants' do
 | 
						|
    let(:project) { create(:project, :public) }
 | 
						|
 | 
						|
    let(:mr) do
 | 
						|
      create(:merge_request, source_project: project, target_project: project)
 | 
						|
    end
 | 
						|
 | 
						|
    let!(:note1) do
 | 
						|
      create(:note_on_merge_request, noteable: mr, project: project, note: 'a')
 | 
						|
    end
 | 
						|
 | 
						|
    let!(:note2) do
 | 
						|
      create(:note_on_merge_request, noteable: mr, project: project, note: 'b')
 | 
						|
    end
 | 
						|
 | 
						|
    it 'includes the merge request author' do
 | 
						|
      expect(mr.participants).to include(mr.author)
 | 
						|
    end
 | 
						|
 | 
						|
    it 'includes the authors of the notes' do
 | 
						|
      expect(mr.participants).to include(note1.author, note2.author)
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe 'cached counts' do
 | 
						|
    it 'updates when assignees change' do
 | 
						|
      user1 = create(:user)
 | 
						|
      user2 = create(:user)
 | 
						|
      mr = create(:merge_request, assignees: [user1])
 | 
						|
      mr.project.add_developer(user1)
 | 
						|
      mr.project.add_developer(user2)
 | 
						|
 | 
						|
      expect(user1.assigned_open_merge_requests_count).to eq(1)
 | 
						|
      expect(user2.assigned_open_merge_requests_count).to eq(0)
 | 
						|
 | 
						|
      mr.assignees = [user2]
 | 
						|
 | 
						|
      expect(user1.assigned_open_merge_requests_count).to eq(0)
 | 
						|
      expect(user2.assigned_open_merge_requests_count).to eq(1)
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#merge_async' do
 | 
						|
    it 'enqueues MergeWorker job and updates merge_jid' do
 | 
						|
      merge_request = create(:merge_request)
 | 
						|
      user_id = double(:user_id)
 | 
						|
      params = {}
 | 
						|
      merge_jid = 'hash-123'
 | 
						|
 | 
						|
      expect(MergeWorker).to receive(:perform_async).with(merge_request.id, user_id, params) do
 | 
						|
        merge_jid
 | 
						|
      end
 | 
						|
 | 
						|
      merge_request.merge_async(user_id, params)
 | 
						|
 | 
						|
      expect(merge_request.reload.merge_jid).to eq(merge_jid)
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#rebase_async' do
 | 
						|
    let(:merge_request) { create(:merge_request) }
 | 
						|
    let(:user_id) { double(:user_id) }
 | 
						|
    let(:rebase_jid) { 'rebase-jid' }
 | 
						|
 | 
						|
    subject(:execute) { merge_request.rebase_async(user_id) }
 | 
						|
 | 
						|
    it 'atomically enqueues a RebaseWorker job and updates rebase_jid' do
 | 
						|
      expect(RebaseWorker)
 | 
						|
        .to receive(:perform_async)
 | 
						|
        .with(merge_request.id, user_id)
 | 
						|
        .and_return(rebase_jid)
 | 
						|
 | 
						|
      expect(merge_request).to receive(:lock!).and_call_original
 | 
						|
 | 
						|
      execute
 | 
						|
 | 
						|
      expect(merge_request.rebase_jid).to eq(rebase_jid)
 | 
						|
    end
 | 
						|
 | 
						|
    it 'refuses to enqueue a job if a rebase is in progress' do
 | 
						|
      merge_request.update_column(:rebase_jid, rebase_jid)
 | 
						|
 | 
						|
      expect(RebaseWorker).not_to receive(:perform_async)
 | 
						|
      expect(Gitlab::SidekiqStatus)
 | 
						|
        .to receive(:running?)
 | 
						|
        .with(rebase_jid)
 | 
						|
        .and_return(true)
 | 
						|
 | 
						|
      expect { execute }.to raise_error(ActiveRecord::StaleObjectError)
 | 
						|
    end
 | 
						|
 | 
						|
    it 'refuses to enqueue a job if the MR is not open' do
 | 
						|
      merge_request.update_column(:state, 'foo')
 | 
						|
 | 
						|
      expect(RebaseWorker).not_to receive(:perform_async)
 | 
						|
 | 
						|
      expect { execute }.to raise_error(ActiveRecord::StaleObjectError)
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#mergeable?' do
 | 
						|
    let(:project) { create(:project) }
 | 
						|
 | 
						|
    subject { create(:merge_request, source_project: project) }
 | 
						|
 | 
						|
    it 'returns false if #mergeable_state? is false' do
 | 
						|
      expect(subject).to receive(:mergeable_state?) { false }
 | 
						|
 | 
						|
      expect(subject.mergeable?).to be_falsey
 | 
						|
    end
 | 
						|
 | 
						|
    it 'return true if #mergeable_state? is true and the MR #can_be_merged? is true' do
 | 
						|
      allow(subject).to receive(:mergeable_state?) { true }
 | 
						|
      expect(subject).to receive(:check_mergeability)
 | 
						|
      expect(subject).to receive(:can_be_merged?) { true }
 | 
						|
 | 
						|
      expect(subject.mergeable?).to be_truthy
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#mergeable_state?' do
 | 
						|
    let(:project) { create(:project, :repository) }
 | 
						|
 | 
						|
    subject { create(:merge_request, source_project: project) }
 | 
						|
 | 
						|
    it 'checks if merge request can be merged' do
 | 
						|
      allow(subject).to receive(:mergeable_ci_state?) { true }
 | 
						|
      expect(subject).to receive(:check_mergeability)
 | 
						|
 | 
						|
      subject.mergeable?
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when not open' do
 | 
						|
      before do
 | 
						|
        subject.close
 | 
						|
      end
 | 
						|
 | 
						|
      it 'returns false' do
 | 
						|
        expect(subject.mergeable_state?).to be_falsey
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when working in progress' do
 | 
						|
      before do
 | 
						|
        subject.title = 'WIP MR'
 | 
						|
      end
 | 
						|
 | 
						|
      it 'returns false' do
 | 
						|
        expect(subject.mergeable_state?).to be_falsey
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when broken' do
 | 
						|
      before do
 | 
						|
        allow(subject).to receive(:broken?) { true }
 | 
						|
      end
 | 
						|
 | 
						|
      it 'returns false' do
 | 
						|
        expect(subject.mergeable_state?).to be_falsey
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when failed' do
 | 
						|
      context 'when #mergeable_ci_state? is false' do
 | 
						|
        before do
 | 
						|
          allow(subject).to receive(:mergeable_ci_state?) { false }
 | 
						|
        end
 | 
						|
 | 
						|
        it 'returns false' do
 | 
						|
          expect(subject.mergeable_state?).to be_falsey
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      context 'when #mergeable_discussions_state? is false' do
 | 
						|
        before do
 | 
						|
          allow(subject).to receive(:mergeable_discussions_state?) { false }
 | 
						|
        end
 | 
						|
 | 
						|
        it 'returns false' do
 | 
						|
          expect(subject.mergeable_state?).to be_falsey
 | 
						|
        end
 | 
						|
 | 
						|
        it 'returns true when skipping discussions check' do
 | 
						|
          expect(subject.mergeable_state?(skip_discussions_check: true)).to be(true)
 | 
						|
        end
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#mergeable_ci_state?' do
 | 
						|
    let(:project) { create(:project, only_allow_merge_if_pipeline_succeeds: true) }
 | 
						|
    let(:pipeline) { create(:ci_empty_pipeline) }
 | 
						|
 | 
						|
    subject { build(:merge_request, target_project: project) }
 | 
						|
 | 
						|
    context 'when it is only allowed to merge when build is green' do
 | 
						|
      context 'and a failed pipeline is associated' do
 | 
						|
        before do
 | 
						|
          pipeline.update(status: 'failed', sha: subject.diff_head_sha)
 | 
						|
          allow(subject).to receive(:head_pipeline) { pipeline }
 | 
						|
        end
 | 
						|
 | 
						|
        it { expect(subject.mergeable_ci_state?).to be_falsey }
 | 
						|
      end
 | 
						|
 | 
						|
      context 'and a successful pipeline is associated' do
 | 
						|
        before do
 | 
						|
          pipeline.update(status: 'success', sha: subject.diff_head_sha)
 | 
						|
          allow(subject).to receive(:head_pipeline) { pipeline }
 | 
						|
        end
 | 
						|
 | 
						|
        it { expect(subject.mergeable_ci_state?).to be_truthy }
 | 
						|
      end
 | 
						|
 | 
						|
      context 'and a skipped pipeline is associated' do
 | 
						|
        before do
 | 
						|
          pipeline.update(status: 'skipped', sha: subject.diff_head_sha)
 | 
						|
          allow(subject).to receive(:head_pipeline) { pipeline }
 | 
						|
        end
 | 
						|
 | 
						|
        it { expect(subject.mergeable_ci_state?).to be_truthy }
 | 
						|
      end
 | 
						|
 | 
						|
      context 'when no pipeline is associated' do
 | 
						|
        before do
 | 
						|
          allow(subject).to receive(:head_pipeline) { nil }
 | 
						|
        end
 | 
						|
 | 
						|
        it { expect(subject.mergeable_ci_state?).to be_falsey }
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when merges are not restricted to green builds' do
 | 
						|
      subject { build(:merge_request, target_project: create(:project, only_allow_merge_if_pipeline_succeeds: false)) }
 | 
						|
 | 
						|
      context 'and a failed pipeline is associated' do
 | 
						|
        before do
 | 
						|
          pipeline.statuses << create(:commit_status, status: 'failed', project: project)
 | 
						|
          allow(subject).to receive(:head_pipeline) { pipeline }
 | 
						|
        end
 | 
						|
 | 
						|
        it { expect(subject.mergeable_ci_state?).to be_truthy }
 | 
						|
      end
 | 
						|
 | 
						|
      context 'when no pipeline is associated' do
 | 
						|
        before do
 | 
						|
          allow(subject).to receive(:head_pipeline) { nil }
 | 
						|
        end
 | 
						|
 | 
						|
        it { expect(subject.mergeable_ci_state?).to be_truthy }
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#mergeable_discussions_state?' do
 | 
						|
    let(:merge_request) { create(:merge_request_with_diff_notes, source_project: project) }
 | 
						|
 | 
						|
    context 'when project.only_allow_merge_if_all_discussions_are_resolved == true' do
 | 
						|
      let(:project) { create(:project, :repository, only_allow_merge_if_all_discussions_are_resolved: true) }
 | 
						|
 | 
						|
      context 'with all discussions resolved' do
 | 
						|
        before do
 | 
						|
          merge_request.discussions.each { |d| d.resolve!(merge_request.author) }
 | 
						|
        end
 | 
						|
 | 
						|
        it 'returns true' do
 | 
						|
          expect(merge_request.mergeable_discussions_state?).to be_truthy
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      context 'with unresolved discussions' do
 | 
						|
        before do
 | 
						|
          merge_request.discussions.each(&:unresolve!)
 | 
						|
        end
 | 
						|
 | 
						|
        it 'returns false' do
 | 
						|
          expect(merge_request.mergeable_discussions_state?).to be_falsey
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      context 'with no discussions' do
 | 
						|
        before do
 | 
						|
          merge_request.notes.destroy_all # rubocop: disable DestroyAll
 | 
						|
        end
 | 
						|
 | 
						|
        it 'returns true' do
 | 
						|
          expect(merge_request.mergeable_discussions_state?).to be_truthy
 | 
						|
        end
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when project.only_allow_merge_if_all_discussions_are_resolved == false' do
 | 
						|
      let(:project) { create(:project, :repository, only_allow_merge_if_all_discussions_are_resolved: false) }
 | 
						|
 | 
						|
      context 'with unresolved discussions' do
 | 
						|
        before do
 | 
						|
          merge_request.discussions.each(&:unresolve!)
 | 
						|
        end
 | 
						|
 | 
						|
        it 'returns true' do
 | 
						|
          expect(merge_request.mergeable_discussions_state?).to be_truthy
 | 
						|
        end
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe "#environments_for" do
 | 
						|
    let(:project)       { create(:project, :repository) }
 | 
						|
    let(:user)          { project.creator }
 | 
						|
    let(:merge_request) { create(:merge_request, source_project: project) }
 | 
						|
 | 
						|
    before do
 | 
						|
      merge_request.source_project.add_maintainer(user)
 | 
						|
      merge_request.target_project.add_maintainer(user)
 | 
						|
    end
 | 
						|
 | 
						|
    context 'with multiple environments' do
 | 
						|
      let(:environments) { create_list(:environment, 3, project: project) }
 | 
						|
 | 
						|
      before do
 | 
						|
        create(:deployment, :success, environment: environments.first, ref: 'master', sha: project.commit('master').id)
 | 
						|
        create(:deployment, :success, environment: environments.second, ref: 'feature', sha: project.commit('feature').id)
 | 
						|
      end
 | 
						|
 | 
						|
      it 'selects deployed environments' do
 | 
						|
        expect(merge_request.environments_for(user)).to contain_exactly(environments.first)
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'with environments on source project' do
 | 
						|
      let(:source_project) { fork_project(project, nil, repository: true) }
 | 
						|
 | 
						|
      let(:merge_request) do
 | 
						|
        create(:merge_request,
 | 
						|
               source_project: source_project, source_branch: 'feature',
 | 
						|
               target_project: project)
 | 
						|
      end
 | 
						|
 | 
						|
      let(:source_environment) { create(:environment, project: source_project) }
 | 
						|
 | 
						|
      before do
 | 
						|
        create(:deployment, :success, environment: source_environment, ref: 'feature', sha: merge_request.diff_head_sha)
 | 
						|
      end
 | 
						|
 | 
						|
      it 'selects deployed environments' do
 | 
						|
        expect(merge_request.environments_for(user)).to contain_exactly(source_environment)
 | 
						|
      end
 | 
						|
 | 
						|
      context 'with environments on target project' do
 | 
						|
        let(:target_environment) { create(:environment, project: project) }
 | 
						|
 | 
						|
        before do
 | 
						|
          create(:deployment, :success, environment: target_environment, tag: true, sha: merge_request.diff_head_sha)
 | 
						|
        end
 | 
						|
 | 
						|
        it 'selects deployed environments' do
 | 
						|
          expect(merge_request.environments_for(user)).to contain_exactly(source_environment, target_environment)
 | 
						|
        end
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'without a diff_head_commit' do
 | 
						|
      before do
 | 
						|
        expect(merge_request).to receive(:diff_head_commit).and_return(nil)
 | 
						|
      end
 | 
						|
 | 
						|
      it 'returns an empty array' do
 | 
						|
        expect(merge_request.environments_for(user)).to be_empty
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe "#environments" do
 | 
						|
    subject { merge_request.environments }
 | 
						|
 | 
						|
    let(:merge_request) { create(:merge_request, source_branch: 'feature', target_branch: 'master') }
 | 
						|
    let(:project) { merge_request.project }
 | 
						|
 | 
						|
    let(:pipeline) do
 | 
						|
      create(:ci_pipeline,
 | 
						|
        source: :merge_request_event,
 | 
						|
        merge_request: merge_request, project: project,
 | 
						|
        sha: merge_request.diff_head_sha,
 | 
						|
        merge_requests_as_head_pipeline: [merge_request])
 | 
						|
    end
 | 
						|
 | 
						|
    let!(:job) { create(:ci_build, :start_review_app, pipeline: pipeline, project: project) }
 | 
						|
 | 
						|
    it 'returns environments' do
 | 
						|
      is_expected.to eq(pipeline.environments)
 | 
						|
      expect(subject.count).to be(1)
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when pipeline is not associated with environments' do
 | 
						|
      let!(:job) { create(:ci_build, pipeline: pipeline, project: project) }
 | 
						|
 | 
						|
      it 'returns empty array' do
 | 
						|
        is_expected.to be_empty
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when pipeline is not a pipeline for merge request' do
 | 
						|
      let(:pipeline) do
 | 
						|
        create(:ci_pipeline,
 | 
						|
          project: project,
 | 
						|
          ref: 'feature',
 | 
						|
          sha: merge_request.diff_head_sha,
 | 
						|
          merge_requests_as_head_pipeline: [merge_request])
 | 
						|
      end
 | 
						|
 | 
						|
      it 'returns empty relation' do
 | 
						|
        is_expected.to be_empty
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe "#reload_diff" do
 | 
						|
    it 'calls MergeRequests::ReloadDiffsService#execute with correct params' do
 | 
						|
      user = create(:user)
 | 
						|
      service = instance_double(MergeRequests::ReloadDiffsService, execute: nil)
 | 
						|
 | 
						|
      expect(MergeRequests::ReloadDiffsService)
 | 
						|
        .to receive(:new).with(subject, user)
 | 
						|
        .and_return(service)
 | 
						|
 | 
						|
      subject.reload_diff(user)
 | 
						|
 | 
						|
      expect(service).to have_received(:execute)
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when using the after_update hook to update' do
 | 
						|
      context 'when the branches are updated' do
 | 
						|
        it 'uses the new heads to generate the diff' do
 | 
						|
          expect { subject.update!(source_branch: subject.target_branch, target_branch: subject.source_branch) }
 | 
						|
            .to change { subject.merge_request_diff.start_commit_sha }
 | 
						|
            .and change { subject.merge_request_diff.head_commit_sha }
 | 
						|
        end
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#update_diff_discussion_positions' do
 | 
						|
    let(:discussion) { create(:diff_note_on_merge_request, project: subject.project, noteable: subject).to_discussion }
 | 
						|
    let(:commit) { subject.project.commit(sample_commit.id) }
 | 
						|
    let(:old_diff_refs) { subject.diff_refs }
 | 
						|
 | 
						|
    before do
 | 
						|
      # Update merge_request_diff so that #diff_refs will return commit.diff_refs
 | 
						|
      allow(subject).to receive(:create_merge_request_diff) do
 | 
						|
        subject.merge_request_diffs.create(
 | 
						|
          base_commit_sha: commit.parent_id,
 | 
						|
          start_commit_sha: commit.parent_id,
 | 
						|
          head_commit_sha: commit.sha
 | 
						|
        )
 | 
						|
 | 
						|
        subject.reload_merge_request_diff
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    it "updates diff discussion positions" do
 | 
						|
      expect(Discussions::UpdateDiffPositionService).to receive(:new).with(
 | 
						|
        subject.project,
 | 
						|
        subject.author,
 | 
						|
        old_diff_refs: old_diff_refs,
 | 
						|
        new_diff_refs: commit.diff_refs,
 | 
						|
        paths: discussion.position.paths
 | 
						|
      ).and_call_original
 | 
						|
 | 
						|
      expect_any_instance_of(Discussions::UpdateDiffPositionService).to receive(:execute).with(discussion).and_call_original
 | 
						|
      expect_any_instance_of(DiffNote).to receive(:save).once
 | 
						|
 | 
						|
      subject.update_diff_discussion_positions(old_diff_refs: old_diff_refs,
 | 
						|
                                               new_diff_refs: commit.diff_refs,
 | 
						|
                                               current_user: subject.author)
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when resolve_outdated_diff_discussions is set' do
 | 
						|
      before do
 | 
						|
        discussion
 | 
						|
 | 
						|
        subject.project.update!(resolve_outdated_diff_discussions: true)
 | 
						|
      end
 | 
						|
 | 
						|
      it 'calls MergeRequests::ResolvedDiscussionNotificationService' do
 | 
						|
        expect_any_instance_of(MergeRequests::ResolvedDiscussionNotificationService)
 | 
						|
          .to receive(:execute).with(subject)
 | 
						|
 | 
						|
        subject.update_diff_discussion_positions(old_diff_refs: old_diff_refs,
 | 
						|
                                                 new_diff_refs: commit.diff_refs,
 | 
						|
                                                 current_user: subject.author)
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#branch_merge_base_commit' do
 | 
						|
    context 'source and target branch exist' do
 | 
						|
      it { expect(subject.branch_merge_base_commit.sha).to eq('ae73cb07c9eeaf35924a10f713b364d32b2dd34f') }
 | 
						|
      it { expect(subject.branch_merge_base_commit).to be_a(Commit) }
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when the target branch does not exist' do
 | 
						|
      before do
 | 
						|
        subject.project.repository.rm_branch(subject.author, subject.target_branch)
 | 
						|
        subject.clear_memoized_shas
 | 
						|
      end
 | 
						|
 | 
						|
      it 'returns nil' do
 | 
						|
        expect(subject.branch_merge_base_commit).to be_nil
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe "#diff_refs" do
 | 
						|
    context "with diffs" do
 | 
						|
      subject { create(:merge_request, :with_diffs) }
 | 
						|
      let(:expected_diff_refs) do
 | 
						|
        Gitlab::Diff::DiffRefs.new(
 | 
						|
          base_sha:  subject.merge_request_diff.base_commit_sha,
 | 
						|
          start_sha: subject.merge_request_diff.start_commit_sha,
 | 
						|
          head_sha:  subject.merge_request_diff.head_commit_sha
 | 
						|
        )
 | 
						|
      end
 | 
						|
 | 
						|
      it "does not touch the repository" do
 | 
						|
        subject # Instantiate the object
 | 
						|
 | 
						|
        expect_any_instance_of(Repository).not_to receive(:commit)
 | 
						|
 | 
						|
        subject.diff_refs
 | 
						|
      end
 | 
						|
 | 
						|
      it "returns expected diff_refs" do
 | 
						|
        expect(subject.diff_refs).to eq(expected_diff_refs)
 | 
						|
      end
 | 
						|
 | 
						|
      context 'when importing' do
 | 
						|
        before do
 | 
						|
          subject.importing = true
 | 
						|
        end
 | 
						|
 | 
						|
        it "returns MR diff_refs" do
 | 
						|
          expect(subject.diff_refs).to eq(expected_diff_refs)
 | 
						|
        end
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe "#source_project_missing?" do
 | 
						|
    let(:project) { create(:project) }
 | 
						|
    let(:forked_project) { fork_project(project) }
 | 
						|
    let(:user) { create(:user) }
 | 
						|
    let(:unlink_project) { Projects::UnlinkForkService.new(forked_project, user) }
 | 
						|
 | 
						|
    context "when the fork exists" do
 | 
						|
      let(:merge_request) do
 | 
						|
        create(:merge_request,
 | 
						|
          source_project: forked_project,
 | 
						|
          target_project: project)
 | 
						|
      end
 | 
						|
 | 
						|
      it { expect(merge_request.source_project_missing?).to be_falsey }
 | 
						|
    end
 | 
						|
 | 
						|
    context "when the source project is the same as the target project" do
 | 
						|
      let(:merge_request) { create(:merge_request, source_project: project) }
 | 
						|
 | 
						|
      it { expect(merge_request.source_project_missing?).to be_falsey }
 | 
						|
    end
 | 
						|
 | 
						|
    context "when the fork does not exist" do
 | 
						|
      let!(:merge_request) do
 | 
						|
        create(:merge_request,
 | 
						|
          source_project: forked_project,
 | 
						|
          target_project: project)
 | 
						|
      end
 | 
						|
 | 
						|
      it "returns true" do
 | 
						|
        unlink_project.execute
 | 
						|
        merge_request.reload
 | 
						|
 | 
						|
        expect(merge_request.source_project_missing?).to be_truthy
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#merge_ongoing?' do
 | 
						|
    it 'returns true when the merge request is locked' do
 | 
						|
      merge_request = build_stubbed(:merge_request, state: :locked)
 | 
						|
 | 
						|
      expect(merge_request.merge_ongoing?).to be(true)
 | 
						|
    end
 | 
						|
 | 
						|
    it 'returns true when merge_id, MR is not merged and it has no running job' do
 | 
						|
      merge_request = build_stubbed(:merge_request, state: :open, merge_jid: 'foo')
 | 
						|
      allow(Gitlab::SidekiqStatus).to receive(:running?).with('foo') { true }
 | 
						|
 | 
						|
      expect(merge_request.merge_ongoing?).to be(true)
 | 
						|
    end
 | 
						|
 | 
						|
    it 'returns false when merge_jid is nil' do
 | 
						|
      merge_request = build_stubbed(:merge_request, state: :open, merge_jid: nil)
 | 
						|
 | 
						|
      expect(merge_request.merge_ongoing?).to be(false)
 | 
						|
    end
 | 
						|
 | 
						|
    it 'returns false if MR is merged' do
 | 
						|
      merge_request = build_stubbed(:merge_request, state: :merged, merge_jid: 'foo')
 | 
						|
 | 
						|
      expect(merge_request.merge_ongoing?).to be(false)
 | 
						|
    end
 | 
						|
 | 
						|
    it 'returns false if there is no merge job running' do
 | 
						|
      merge_request = build_stubbed(:merge_request, state: :open, merge_jid: 'foo')
 | 
						|
      allow(Gitlab::SidekiqStatus).to receive(:running?).with('foo') { false }
 | 
						|
 | 
						|
      expect(merge_request.merge_ongoing?).to be(false)
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe "#closed_without_fork?" do
 | 
						|
    let(:project) { create(:project) }
 | 
						|
    let(:forked_project) { fork_project(project) }
 | 
						|
    let(:user) { create(:user) }
 | 
						|
    let(:unlink_project) { Projects::UnlinkForkService.new(forked_project, user) }
 | 
						|
 | 
						|
    context "when the merge request is closed" do
 | 
						|
      let(:closed_merge_request) do
 | 
						|
        create(:closed_merge_request,
 | 
						|
          source_project: forked_project,
 | 
						|
          target_project: project)
 | 
						|
      end
 | 
						|
 | 
						|
      it "returns false if the fork exist" do
 | 
						|
        expect(closed_merge_request.closed_without_fork?).to be_falsey
 | 
						|
      end
 | 
						|
 | 
						|
      it "returns true if the fork does not exist" do
 | 
						|
        unlink_project.execute
 | 
						|
        closed_merge_request.reload
 | 
						|
 | 
						|
        expect(closed_merge_request.closed_without_fork?).to be_truthy
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context "when the merge request is open" do
 | 
						|
      let(:open_merge_request) do
 | 
						|
        create(:merge_request,
 | 
						|
          source_project: forked_project,
 | 
						|
          target_project: project)
 | 
						|
      end
 | 
						|
 | 
						|
      it "returns false" do
 | 
						|
        expect(open_merge_request.closed_without_fork?).to be_falsey
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#reopenable?' do
 | 
						|
    context 'when the merge request is closed' do
 | 
						|
      it 'returns true' do
 | 
						|
        subject.close
 | 
						|
 | 
						|
        expect(subject.reopenable?).to be_truthy
 | 
						|
      end
 | 
						|
 | 
						|
      context 'forked project' do
 | 
						|
        let(:project)      { create(:project, :public) }
 | 
						|
        let(:user)         { create(:user) }
 | 
						|
        let(:forked_project) { fork_project(project, user) }
 | 
						|
 | 
						|
        let!(:merge_request) do
 | 
						|
          create(:closed_merge_request,
 | 
						|
            source_project: forked_project,
 | 
						|
            target_project: project)
 | 
						|
        end
 | 
						|
 | 
						|
        it 'returns false if unforked' do
 | 
						|
          Projects::UnlinkForkService.new(forked_project, user).execute
 | 
						|
 | 
						|
          expect(merge_request.reload.reopenable?).to be_falsey
 | 
						|
        end
 | 
						|
 | 
						|
        it 'returns false if the source project is deleted' do
 | 
						|
          Projects::DestroyService.new(forked_project, user).execute
 | 
						|
 | 
						|
          expect(merge_request.reload.reopenable?).to be_falsey
 | 
						|
        end
 | 
						|
 | 
						|
        it 'returns false if the merge request is merged' do
 | 
						|
          merge_request.update(state: 'merged')
 | 
						|
 | 
						|
          expect(merge_request.reload.reopenable?).to be_falsey
 | 
						|
        end
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when the merge request is opened' do
 | 
						|
      it 'returns false' do
 | 
						|
        expect(subject.reopenable?).to be_falsey
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#mergeable_with_quick_action?' do
 | 
						|
    def create_pipeline(status)
 | 
						|
      pipeline = create(:ci_pipeline_with_one_job,
 | 
						|
        project: project,
 | 
						|
        ref:     merge_request.source_branch,
 | 
						|
        sha:     merge_request.diff_head_sha,
 | 
						|
        status:  status,
 | 
						|
        head_pipeline_of: merge_request)
 | 
						|
 | 
						|
      pipeline
 | 
						|
    end
 | 
						|
 | 
						|
    let(:project)       { create(:project, :public, :repository, only_allow_merge_if_pipeline_succeeds: true) }
 | 
						|
    let(:developer)     { create(:user) }
 | 
						|
    let(:user)          { create(:user) }
 | 
						|
    let(:merge_request) { create(:merge_request, source_project: project) }
 | 
						|
    let(:mr_sha)        { merge_request.diff_head_sha }
 | 
						|
 | 
						|
    before do
 | 
						|
      project.add_developer(developer)
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when autocomplete_precheck is set to true' do
 | 
						|
      it 'is mergeable by developer' do
 | 
						|
        expect(merge_request.mergeable_with_quick_action?(developer, autocomplete_precheck: true)).to be_truthy
 | 
						|
      end
 | 
						|
 | 
						|
      it 'is not mergeable by normal user' do
 | 
						|
        expect(merge_request.mergeable_with_quick_action?(user, autocomplete_precheck: true)).to be_falsey
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when autocomplete_precheck is set to false' do
 | 
						|
      it 'is mergeable by developer' do
 | 
						|
        expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_truthy
 | 
						|
      end
 | 
						|
 | 
						|
      it 'is not mergeable by normal user' do
 | 
						|
        expect(merge_request.mergeable_with_quick_action?(user, last_diff_sha: mr_sha)).to be_falsey
 | 
						|
      end
 | 
						|
 | 
						|
      context 'closed MR' do
 | 
						|
        before do
 | 
						|
          merge_request.update_attribute(:state, :closed)
 | 
						|
        end
 | 
						|
 | 
						|
        it 'is not mergeable' do
 | 
						|
          expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_falsey
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      context 'MR with WIP' do
 | 
						|
        before do
 | 
						|
          merge_request.update_attribute(:title, 'WIP: some MR')
 | 
						|
        end
 | 
						|
 | 
						|
        it 'is not mergeable' do
 | 
						|
          expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_falsey
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      context 'sha differs from the MR diff_head_sha' do
 | 
						|
        it 'is not mergeable' do
 | 
						|
          expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: 'some other sha')).to be_falsey
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      context 'sha is not provided' do
 | 
						|
        it 'is not mergeable' do
 | 
						|
          expect(merge_request.mergeable_with_quick_action?(developer)).to be_falsey
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      context 'with pipeline ok' do
 | 
						|
        before do
 | 
						|
          create_pipeline(:success)
 | 
						|
        end
 | 
						|
 | 
						|
        it 'is mergeable' do
 | 
						|
          expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_truthy
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      context 'with failing pipeline' do
 | 
						|
        before do
 | 
						|
          create_pipeline(:failed)
 | 
						|
        end
 | 
						|
 | 
						|
        it 'is not mergeable' do
 | 
						|
          expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_falsey
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      context 'with running pipeline' do
 | 
						|
        before do
 | 
						|
          create_pipeline(:running)
 | 
						|
        end
 | 
						|
 | 
						|
        it 'is mergeable' do
 | 
						|
          expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_truthy
 | 
						|
        end
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#base_pipeline' do
 | 
						|
    let(:pipeline_arguments) do
 | 
						|
      {
 | 
						|
        project: project,
 | 
						|
        ref: merge_request.target_branch,
 | 
						|
        sha: merge_request.diff_base_sha
 | 
						|
      }
 | 
						|
    end
 | 
						|
 | 
						|
    let(:project) { create(:project, :public, :repository) }
 | 
						|
    let(:merge_request) { create(:merge_request, source_project: project) }
 | 
						|
 | 
						|
    let!(:first_pipeline) { create(:ci_pipeline_without_jobs, pipeline_arguments) }
 | 
						|
    let!(:last_pipeline) { create(:ci_pipeline_without_jobs, pipeline_arguments) }
 | 
						|
    let!(:last_pipeline_with_other_ref) { create(:ci_pipeline_without_jobs, pipeline_arguments.merge(ref: 'other')) }
 | 
						|
 | 
						|
    it 'returns latest pipeline for the target branch' do
 | 
						|
      expect(merge_request.base_pipeline).to eq(last_pipeline)
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#has_commits?' do
 | 
						|
    it 'returns true when merge request diff has commits' do
 | 
						|
      allow(subject.merge_request_diff).to receive(:commits_count)
 | 
						|
        .and_return(2)
 | 
						|
 | 
						|
      expect(subject.has_commits?).to be_truthy
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when commits_count is nil' do
 | 
						|
      it 'returns false' do
 | 
						|
        allow(subject.merge_request_diff).to receive(:commits_count)
 | 
						|
        .and_return(nil)
 | 
						|
 | 
						|
        expect(subject.has_commits?).to be_falsey
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#has_no_commits?' do
 | 
						|
    before do
 | 
						|
      allow(subject.merge_request_diff).to receive(:commits_count)
 | 
						|
        .and_return(0)
 | 
						|
    end
 | 
						|
 | 
						|
    it 'returns true when merge request diff has 0 commits' do
 | 
						|
      expect(subject.has_no_commits?).to be_truthy
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#merge_request_diff_for' do
 | 
						|
    subject { create(:merge_request, importing: true) }
 | 
						|
    let!(:merge_request_diff1) { subject.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') }
 | 
						|
    let!(:merge_request_diff2) { subject.merge_request_diffs.create(head_commit_sha: nil) }
 | 
						|
    let!(:merge_request_diff3) { subject.merge_request_diffs.create(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e') }
 | 
						|
 | 
						|
    context 'with diff refs' do
 | 
						|
      it 'returns the diffs' do
 | 
						|
        expect(subject.merge_request_diff_for(merge_request_diff1.diff_refs)).to eq(merge_request_diff1)
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'with a commit SHA' do
 | 
						|
      it 'returns the diffs' do
 | 
						|
        expect(subject.merge_request_diff_for(merge_request_diff3.head_commit_sha)).to eq(merge_request_diff3)
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    it 'runs a single query on the initial call, and none afterwards' do
 | 
						|
      expect { subject.merge_request_diff_for(merge_request_diff1.diff_refs) }
 | 
						|
        .not_to exceed_query_limit(1)
 | 
						|
 | 
						|
      expect { subject.merge_request_diff_for(merge_request_diff2.diff_refs) }
 | 
						|
        .not_to exceed_query_limit(0)
 | 
						|
 | 
						|
      expect { subject.merge_request_diff_for(merge_request_diff3.head_commit_sha) }
 | 
						|
        .not_to exceed_query_limit(0)
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#version_params_for' do
 | 
						|
    subject { create(:merge_request, importing: true) }
 | 
						|
    let(:project) { subject.project }
 | 
						|
    let!(:merge_request_diff1) { subject.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') }
 | 
						|
    let!(:merge_request_diff2) { subject.merge_request_diffs.create(head_commit_sha: nil) }
 | 
						|
    let!(:merge_request_diff3) { subject.merge_request_diffs.create(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e') }
 | 
						|
 | 
						|
    context 'when the diff refs are for an older merge request version' do
 | 
						|
      let(:diff_refs) { merge_request_diff1.diff_refs }
 | 
						|
 | 
						|
      it 'returns the diff ID for the version to show' do
 | 
						|
        expect(subject.version_params_for(diff_refs)).to eq(diff_id: merge_request_diff1.id)
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when the diff refs are for a comparison between merge request versions' do
 | 
						|
      let(:diff_refs) { merge_request_diff3.compare_with(merge_request_diff1.head_commit_sha).diff_refs }
 | 
						|
 | 
						|
      it 'returns the diff ID and start sha of the versions to compare' do
 | 
						|
        expect(subject.version_params_for(diff_refs)).to eq(diff_id: merge_request_diff3.id, start_sha: merge_request_diff1.head_commit_sha)
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when the diff refs are not for a merge request version' do
 | 
						|
      let(:diff_refs) { project.commit(sample_commit.id).diff_refs }
 | 
						|
 | 
						|
      it 'returns nil' do
 | 
						|
        expect(subject.version_params_for(diff_refs)).to be_nil
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#fetch_ref!' do
 | 
						|
    it 'fetches the ref correctly' do
 | 
						|
      expect { subject.target_project.repository.delete_refs(subject.ref_path) }.not_to raise_error
 | 
						|
 | 
						|
      subject.fetch_ref!
 | 
						|
      expect(subject.target_project.repository.ref_exists?(subject.ref_path)).to be_truthy
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe 'removing a merge request' do
 | 
						|
    it 'refreshes the number of open merge requests of the target project' do
 | 
						|
      project = subject.target_project
 | 
						|
 | 
						|
      expect { subject.destroy }
 | 
						|
        .to change { project.open_merge_requests_count }.from(1).to(0)
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  it_behaves_like 'throttled touch' do
 | 
						|
    subject { create(:merge_request, updated_at: 1.hour.ago) }
 | 
						|
  end
 | 
						|
 | 
						|
  context 'state machine transitions' do
 | 
						|
    describe '#unlock_mr' do
 | 
						|
      subject { create(:merge_request, state: 'locked', merge_jid: 123) }
 | 
						|
 | 
						|
      it 'updates merge request head pipeline and sets merge_jid to nil' do
 | 
						|
        pipeline = create(:ci_empty_pipeline, project: subject.project, ref: subject.source_branch, sha: subject.source_branch_sha)
 | 
						|
 | 
						|
        subject.unlock_mr
 | 
						|
 | 
						|
        subject.reload
 | 
						|
        expect(subject.head_pipeline).to eq(pipeline)
 | 
						|
        expect(subject.merge_jid).to be_nil
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    describe 'transition to cannot_be_merged' do
 | 
						|
      let(:notification_service) { double(:notification_service) }
 | 
						|
      let(:todo_service) { double(:todo_service) }
 | 
						|
      subject { create(:merge_request, state, merge_status: :unchecked) }
 | 
						|
 | 
						|
      before do
 | 
						|
        allow(NotificationService).to receive(:new).and_return(notification_service)
 | 
						|
        allow(TodoService).to receive(:new).and_return(todo_service)
 | 
						|
 | 
						|
        allow(subject.project.repository).to receive(:can_be_merged?).and_return(false)
 | 
						|
      end
 | 
						|
 | 
						|
      [:opened, :locked].each do |state|
 | 
						|
        context state do
 | 
						|
          let(:state) { state }
 | 
						|
 | 
						|
          it 'notifies conflict, but does not notify again if rechecking still results in cannot_be_merged' do
 | 
						|
            expect(notification_service).to receive(:merge_request_unmergeable).with(subject).once
 | 
						|
            expect(todo_service).to receive(:merge_request_became_unmergeable).with(subject).once
 | 
						|
 | 
						|
            subject.mark_as_unmergeable
 | 
						|
            subject.mark_as_unchecked
 | 
						|
            subject.mark_as_unmergeable
 | 
						|
          end
 | 
						|
 | 
						|
          it 'notifies conflict, whenever newly unmergeable' do
 | 
						|
            expect(notification_service).to receive(:merge_request_unmergeable).with(subject).twice
 | 
						|
            expect(todo_service).to receive(:merge_request_became_unmergeable).with(subject).twice
 | 
						|
 | 
						|
            subject.mark_as_unmergeable
 | 
						|
            subject.mark_as_unchecked
 | 
						|
            subject.mark_as_mergeable
 | 
						|
            subject.mark_as_unchecked
 | 
						|
            subject.mark_as_unmergeable
 | 
						|
          end
 | 
						|
 | 
						|
          it 'does not notify whenever merge request is newly unmergeable due to other reasons' do
 | 
						|
            allow(subject.project.repository).to receive(:can_be_merged?).and_return(true)
 | 
						|
 | 
						|
            expect(notification_service).not_to receive(:merge_request_unmergeable)
 | 
						|
            expect(todo_service).not_to receive(:merge_request_became_unmergeable)
 | 
						|
 | 
						|
            subject.mark_as_unmergeable
 | 
						|
          end
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      [:closed, :merged].each do |state|
 | 
						|
        let(:state) { state }
 | 
						|
 | 
						|
        context state do
 | 
						|
          it 'does not notify' do
 | 
						|
            expect(notification_service).not_to receive(:merge_request_unmergeable)
 | 
						|
            expect(todo_service).not_to receive(:merge_request_became_unmergeable)
 | 
						|
 | 
						|
            subject.mark_as_unmergeable
 | 
						|
          end
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      context 'source branch is missing' do
 | 
						|
        subject { create(:merge_request, :invalid, :opened, merge_status: :unchecked, target_branch: 'master') }
 | 
						|
 | 
						|
        before do
 | 
						|
          allow(subject.project.repository).to receive(:can_be_merged?).and_call_original
 | 
						|
        end
 | 
						|
 | 
						|
        it 'does not raise error' do
 | 
						|
          expect(notification_service).not_to receive(:merge_request_unmergeable)
 | 
						|
          expect(todo_service).not_to receive(:merge_request_became_unmergeable)
 | 
						|
 | 
						|
          expect { subject.mark_as_unmergeable }.not_to raise_error
 | 
						|
          expect(subject.cannot_be_merged?).to eq(true)
 | 
						|
        end
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    describe 'check_state?' do
 | 
						|
      it 'indicates whether MR is still checking for mergeability' do
 | 
						|
        state_machine = described_class.state_machines[:merge_status]
 | 
						|
        check_states = [:unchecked, :cannot_be_merged_recheck]
 | 
						|
 | 
						|
        check_states.each do |merge_status|
 | 
						|
          expect(state_machine.check_state?(merge_status)).to be true
 | 
						|
        end
 | 
						|
 | 
						|
        (state_machine.states.map(&:name) - check_states).each do |merge_status|
 | 
						|
          expect(state_machine.check_state?(merge_status)).to be false
 | 
						|
        end
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#should_be_rebased?' do
 | 
						|
    let(:project) { create(:project, :repository) }
 | 
						|
 | 
						|
    it 'returns false for the same source and target branches' do
 | 
						|
      merge_request = create(:merge_request, source_project: project, target_project: project)
 | 
						|
 | 
						|
      expect(merge_request.should_be_rebased?).to be_falsey
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#rebase_in_progress?' do
 | 
						|
    where(:rebase_jid, :jid_valid, :result) do
 | 
						|
      'foo' | true  | true
 | 
						|
      'foo' | false | false
 | 
						|
      ''    | true  | false
 | 
						|
      nil   | true  | false
 | 
						|
    end
 | 
						|
 | 
						|
    with_them do
 | 
						|
      let(:merge_request) { create(:merge_request) }
 | 
						|
 | 
						|
      subject { merge_request.rebase_in_progress? }
 | 
						|
 | 
						|
      it do
 | 
						|
        # Stub out the legacy gitaly implementation
 | 
						|
        allow(merge_request).to receive(:gitaly_rebase_in_progress?) { false }
 | 
						|
 | 
						|
        allow(Gitlab::SidekiqStatus).to receive(:running?).with(rebase_jid) { jid_valid }
 | 
						|
 | 
						|
        merge_request.rebase_jid = rebase_jid
 | 
						|
 | 
						|
        is_expected.to eq(result)
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#gitaly_rebase_in_progress?' do
 | 
						|
    let(:repo_path) do
 | 
						|
      Gitlab::GitalyClient::StorageSettings.allow_disk_access do
 | 
						|
        subject.source_project.repository.path
 | 
						|
      end
 | 
						|
    end
 | 
						|
    let(:rebase_path) { File.join(repo_path, "gitlab-worktree", "rebase-#{subject.id}") }
 | 
						|
 | 
						|
    before do
 | 
						|
      system(*%W(#{Gitlab.config.git.bin_path} -C #{repo_path} worktree add --detach #{rebase_path} master))
 | 
						|
    end
 | 
						|
 | 
						|
    it 'returns true when there is a current rebase directory' do
 | 
						|
      expect(subject.rebase_in_progress?).to be_truthy
 | 
						|
    end
 | 
						|
 | 
						|
    it 'returns false when there is no rebase directory' do
 | 
						|
      FileUtils.rm_rf(rebase_path)
 | 
						|
 | 
						|
      expect(subject.rebase_in_progress?).to be_falsey
 | 
						|
    end
 | 
						|
 | 
						|
    it 'returns false when the rebase directory has expired' do
 | 
						|
      time = 20.minutes.ago.to_time
 | 
						|
      File.utime(time, time, rebase_path)
 | 
						|
 | 
						|
      expect(subject.rebase_in_progress?).to be_falsey
 | 
						|
    end
 | 
						|
 | 
						|
    it 'returns false when the source project has been removed' do
 | 
						|
      allow(subject).to receive(:source_project).and_return(nil)
 | 
						|
 | 
						|
      expect(subject.rebase_in_progress?).to be_falsey
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#allow_collaboration' do
 | 
						|
    let(:merge_request) do
 | 
						|
      build(:merge_request, source_branch: 'fixes', allow_collaboration: true)
 | 
						|
    end
 | 
						|
 | 
						|
    it 'is false when pushing by a maintainer is not possible' do
 | 
						|
      expect(merge_request).to receive(:collaborative_push_possible?) { false }
 | 
						|
 | 
						|
      expect(merge_request.allow_collaboration).to be_falsy
 | 
						|
    end
 | 
						|
 | 
						|
    it 'is true when pushing by a maintainer is possible' do
 | 
						|
      expect(merge_request).to receive(:collaborative_push_possible?) { true }
 | 
						|
 | 
						|
      expect(merge_request.allow_collaboration).to be_truthy
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#collaborative_push_possible?' do
 | 
						|
    let(:merge_request) do
 | 
						|
      build(:merge_request, source_branch: 'fixes')
 | 
						|
    end
 | 
						|
 | 
						|
    before do
 | 
						|
      allow(ProtectedBranch).to receive(:protected?) { false }
 | 
						|
    end
 | 
						|
 | 
						|
    it 'does not allow maintainer to push if the source project is the same as the target' do
 | 
						|
      merge_request.target_project = merge_request.source_project = create(:project, :public)
 | 
						|
 | 
						|
      expect(merge_request.collaborative_push_possible?).to be_falsy
 | 
						|
    end
 | 
						|
 | 
						|
    it 'allows maintainer to push when both source and target are public' do
 | 
						|
      merge_request.target_project = build(:project, :public)
 | 
						|
      merge_request.source_project = build(:project, :public)
 | 
						|
 | 
						|
      expect(merge_request.collaborative_push_possible?).to be_truthy
 | 
						|
    end
 | 
						|
 | 
						|
    it 'is not available for protected branches' do
 | 
						|
      merge_request.target_project = build(:project, :public)
 | 
						|
      merge_request.source_project = build(:project, :public)
 | 
						|
 | 
						|
      expect(ProtectedBranch).to receive(:protected?)
 | 
						|
                                   .with(merge_request.source_project, 'fixes')
 | 
						|
                                   .and_return(true)
 | 
						|
 | 
						|
      expect(merge_request.collaborative_push_possible?).to be_falsy
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#includes_any_commits?' do
 | 
						|
    it 'returns false' do
 | 
						|
      expect(subject.includes_any_commits?([])).to be_falsey
 | 
						|
    end
 | 
						|
 | 
						|
    it 'returns false' do
 | 
						|
      expect(subject.includes_any_commits?([Gitlab::Git::BLANK_SHA])).to be_falsey
 | 
						|
    end
 | 
						|
 | 
						|
    it 'returns true' do
 | 
						|
      expect(subject.includes_any_commits?([subject.merge_request_diff.head_commit_sha])).to be_truthy
 | 
						|
    end
 | 
						|
 | 
						|
    it 'returns true even when there is a non-existent comit' do
 | 
						|
      expect(subject.includes_any_commits?([Gitlab::Git::BLANK_SHA, subject.merge_request_diff.head_commit_sha])).to be_truthy
 | 
						|
    end
 | 
						|
 | 
						|
    context 'unpersisted merge request' do
 | 
						|
      let(:new_mr) { build(:merge_request) }
 | 
						|
 | 
						|
      it 'returns false' do
 | 
						|
        expect(new_mr.includes_any_commits?([Gitlab::Git::BLANK_SHA])).to be_falsey
 | 
						|
      end
 | 
						|
 | 
						|
      it 'returns true' do
 | 
						|
        expect(new_mr.includes_any_commits?([subject.merge_request_diff.head_commit_sha])).to be_truthy
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#can_allow_collaboration?' do
 | 
						|
    let(:target_project) { create(:project, :public) }
 | 
						|
    let(:source_project) { fork_project(target_project) }
 | 
						|
    let(:merge_request) do
 | 
						|
      create(:merge_request,
 | 
						|
             source_project: source_project,
 | 
						|
             source_branch: 'fixes',
 | 
						|
             target_project: target_project)
 | 
						|
    end
 | 
						|
    let(:user) { create(:user) }
 | 
						|
 | 
						|
    before do
 | 
						|
      allow(merge_request).to receive(:collaborative_push_possible?) { true }
 | 
						|
    end
 | 
						|
 | 
						|
    it 'is false if the user does not have push access to the source project' do
 | 
						|
      expect(merge_request.can_allow_collaboration?(user)).to be_falsy
 | 
						|
    end
 | 
						|
 | 
						|
    it 'is true when the user has push access to the source project' do
 | 
						|
      source_project.add_developer(user)
 | 
						|
 | 
						|
      expect(merge_request.can_allow_collaboration?(user)).to be_truthy
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#merge_participants' do
 | 
						|
    it 'contains author' do
 | 
						|
      expect(subject.merge_participants).to eq([subject.author])
 | 
						|
    end
 | 
						|
 | 
						|
    describe 'when merge_when_pipeline_succeeds? is true' do
 | 
						|
      describe 'when merge user is author' do
 | 
						|
        let(:user) { create(:user) }
 | 
						|
        subject do
 | 
						|
          create(:merge_request,
 | 
						|
                 merge_when_pipeline_succeeds: true,
 | 
						|
                 merge_user: user,
 | 
						|
                 author: user)
 | 
						|
        end
 | 
						|
 | 
						|
        it 'contains author only' do
 | 
						|
          expect(subject.merge_participants).to eq([subject.author])
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      describe 'when merge user and author are different users' do
 | 
						|
        let(:merge_user) { create(:user) }
 | 
						|
        subject do
 | 
						|
          create(:merge_request,
 | 
						|
                 merge_when_pipeline_succeeds: true,
 | 
						|
                 merge_user: merge_user)
 | 
						|
        end
 | 
						|
 | 
						|
        it 'contains author and merge user' do
 | 
						|
          expect(subject.merge_participants).to eq([subject.author, merge_user])
 | 
						|
        end
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '.merge_request_ref?' do
 | 
						|
    subject { described_class.merge_request_ref?(ref) }
 | 
						|
 | 
						|
    context 'when ref is ref name of a branch' do
 | 
						|
      let(:ref) { 'feature' }
 | 
						|
 | 
						|
      it { is_expected.to be_falsey }
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when ref is HEAD ref path of a branch' do
 | 
						|
      let(:ref) { 'refs/heads/feature' }
 | 
						|
 | 
						|
      it { is_expected.to be_falsey }
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when ref is HEAD ref path of a merge request' do
 | 
						|
      let(:ref) { 'refs/merge-requests/1/head' }
 | 
						|
 | 
						|
      it { is_expected.to be_truthy }
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when ref is merge ref path of a merge request' do
 | 
						|
      let(:ref) { 'refs/merge-requests/1/merge' }
 | 
						|
 | 
						|
      it { is_expected.to be_truthy }
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  describe '#cleanup_refs' do
 | 
						|
    subject { merge_request.cleanup_refs(only: only) }
 | 
						|
 | 
						|
    let(:merge_request) { build(:merge_request) }
 | 
						|
 | 
						|
    context 'when removing all refs' do
 | 
						|
      let(:only) { :all }
 | 
						|
 | 
						|
      it 'deletes all refs from the target project' do
 | 
						|
        expect(merge_request.target_project.repository)
 | 
						|
          .to receive(:delete_refs)
 | 
						|
          .with(merge_request.ref_path, merge_request.merge_ref_path, merge_request.train_ref_path)
 | 
						|
 | 
						|
        subject
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    context 'when removing only train ref' do
 | 
						|
      let(:only) { :train }
 | 
						|
 | 
						|
      it 'deletes train ref from the target project' do
 | 
						|
        expect(merge_request.target_project.repository)
 | 
						|
          .to receive(:delete_refs)
 | 
						|
          .with(merge_request.train_ref_path)
 | 
						|
 | 
						|
        subject
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
end
 |