Skip to content
Snippets Groups Projects
merge_request_spec.rb 14.7 KiB
Newer Older
  • Learn to ignore specific revisions
  • Dmitriy Zaporozhets's avatar
    Dmitriy Zaporozhets committed
    require 'spec_helper'
    
    
    Douwe Maan's avatar
    Douwe Maan committed
    describe MergeRequest, models: true do
    
      subject { create(:merge_request) }
    
    
      describe 'associations' do
        it { is_expected.to belong_to(:target_project).with_foreign_key(:target_project_id).class_name('Project') }
        it { is_expected.to belong_to(:source_project).with_foreign_key(:source_project_id).class_name('Project') }
    
        it { is_expected.to belong_to(:merge_user).class_name("User") }
    
        it { is_expected.to have_one(:merge_request_diff).dependent(:destroy) }
      end
    
    
      describe 'modules' do
        subject { described_class }
    
        it { is_expected.to include_module(InternalId) }
        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) }
      end
    
    
      describe "act_as_paranoid" do
        it { is_expected.to have_db_column(:deleted_at) }
        it { is_expected.to have_db_index(:deleted_at) }
      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 Build 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_build_succeeds = true
            expect(subject).not_to be_valid
          end
    
          it "is valid with merge user" do
            subject.merge_when_build_succeeds = true
            subject.merge_user = build(:user)
    
            expect(subject).to be_valid
          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_build_succeeds) }
    
      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 '#target_sha' do
        context 'when the target branch does not exist anymore' do
    
          let(:project) { create(:project) }
    
          subject { create(:merge_request, source_project: project, target_project: project) }
    
          before do
            project.repository.raw_repository.delete_branch(subject.target_branch)
          end
    
    
          it 'returns nil' do
            expect(subject.target_sha).to be_nil
          end
        end
      end
    
    
      describe '#source_sha' do
        let(:last_branch_commit) { subject.source_project.repository.commit(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_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_sha).to eq(last_branch_commit.sha)
          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_sha).to be_nil
          end
        end
      end
    
    
      describe '#to_reference' do
        it 'returns a String reference to the object' do
          expect(subject.to_reference).to eq "!#{subject.iid}"
        end
    
        it 'supports a cross-project reference' do
          cross = double('project')
          expect(subject.to_reference(cross)).to eq "#{subject.source_project.to_reference}!#{subject.iid}"
        end
    
    
      describe "#mr_and_commit_notes" do
    
        let!(:merge_request) { create(:merge_request) }
    
          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 "should include notes for commits" do
    
          expect(merge_request.commits).not_to be_empty
          expect(merge_request.mr_and_commit_notes.count).to eq(2)
    
    
        it "should include 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.mr_and_commit_notes.count).to eq(3)
        end
    
    
      describe '#is_being_reassigned?' do
        it 'returns true if the merge_request assignee has changed' do
    
          subject.assignee = create(:user)
    
          expect(subject.is_being_reassigned?).to be_truthy
    
        end
        it 'returns false if the merge request assignee has not changed' do
    
          expect(subject.is_being_reassigned?).to be_falsey
    
    
      describe '#for_fork?' do
        it 'returns true if the merge request is for a fork' do
    
    Dmitriy Zaporozhets's avatar
    Dmitriy Zaporozhets committed
          subject.source_project = create(:project, namespace: create(:group))
          subject.target_project = create(:project, namespace: create(:group))
    
          expect(subject.for_fork?).to be_truthy
    
    Dmitriy Zaporozhets's avatar
    Dmitriy Zaporozhets committed
    
    
        it 'returns false if is not for a fork' do
    
          expect(subject.for_fork?).to be_falsey
    
      describe 'detection of issues to be closed' 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}") }
    
          subject.project.team << [subject.author, :developer]
    
          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
    
    
        it 'detects issues mentioned in the description' do
          issue2 = create(:issue, project: subject.project)
    
          subject.description = "Closes #{issue2.to_reference}"
    
          allow(subject.project).to receive(:default_branch).
            and_return(subject.target_branch)
    
          expect(subject.closes_issues).to include(issue2)
    
        ['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
    
        it "doesn't detect WIP for words starting with WIP" do
          subject.title = "Wipwap #{subject.title}"
    
          expect(subject.work_in_progress?).to eq false
    
        it "doesn't detect WIP for words containing with WIP" do
          subject.title = "WupWipwap #{subject.title}"
    
          expect(subject.work_in_progress?).to eq false
    
        it "doesn't detect WIP by default" do
    
          expect(subject.work_in_progress?).to eq false
    
    Zeger-Jan van de Weg's avatar
    Zeger-Jan van de Weg committed
      describe '#can_remove_source_branch?' do
    
        let(:user) { create(:user) }
        let(:user2) { create(:user) }
    
    
        before do
          subject.source_project.team << [user, :master]
    
    
          subject.source_branch = "feature"
          subject.target_branch = "master"
          subject.save!
        end
    
        it "can't be removed when its a protected branch" do
          allow(subject.source_project).to receive(:protected_branch?).and_return(true)
    
          expect(subject.can_remove_source_branch?(user)).to be_falsey
        end
    
        it "cant remove a root ref" do
    
          subject.source_branch = "master"
          subject.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
          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.source_project).to receive(:commit).and_return(subject.last_commit)
    
    
          expect(subject.can_remove_source_branch?(user)).to be_truthy
    
    
        it "cannot be removed if the last commit is not also the head of the source branch" do
          expect(subject.can_remove_source_branch?(user)).to be_falsey
        end
    
      describe "#reset_merge_when_build_succeeds" do
    
        let(:merge_if_green) do
          create :merge_request, merge_when_build_succeeds: true, merge_user: create(:user),
                                 merge_params: { "should_remove_source_branch" => "1", "commit_message" => "msg" }
        end
    
        it "sets the item to false" do
          merge_if_green.reset_merge_when_build_succeeds
          merge_if_green.reload
    
          expect(merge_if_green.merge_when_build_succeeds).to be_falsey
    
          expect(merge_if_green.merge_params["should_remove_source_branch"]).to be_nil
          expect(merge_if_green.merge_params["commit_message"]).to be_nil
    
      describe "#hook_attrs" do
    
        let(:attrs_hash) { subject.hook_attrs.to_h }
    
        [:source, :target].each do |key|
          describe "#{key} key" do
            include_examples 'project hook data', project_key: key do
              let(:data)    { attrs_hash }
              let(:project) { subject.send("#{key}_project") }
            end
          end
        end
    
    
        it "has all the required keys" do
    
          expect(attrs_hash).to include(:source)
          expect(attrs_hash).to include(:target)
          expect(attrs_hash).to include(:last_commit)
          expect(attrs_hash).to include(:work_in_progress)
    
      end
    
      describe '#diverged_commits_count' do
        let(:project)      { create(:project) }
        let(:fork_project) { create(:project, forked_from_project: project) }
    
    
        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.reload
          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(5)
          end
        end
    
        context 'diverged on fork' do
          subject(:merge_request_fork_with_divergence) { create(:merge_request, :diverged, source_project: fork_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(5)
          end
        end
    
        context 'rebased on fork' do
          subject(:merge_request_rebased) { create(:merge_request, :rebased, source_project: fork_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(:example) 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_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_sha).and_return('123abc')
            subject.diverged_commits_count
          end
        end
    
      it_behaves_like 'an editable mentionable' do
    
        subject { create(:merge_request) }
    
        let(:backref_text) { "merge request #{subject.to_reference}" }
        let(:set_mentionable_text) { ->(txt){ subject.description = txt } }
    
    Vinnie Okada's avatar
    Vinnie Okada committed
    
      it_behaves_like 'a Taskable' do
    
        subject { create :merge_request, :simple }
    
    Vinnie Okada's avatar
    Vinnie Okada committed
      end
    
        describe 'when the source project exists' do
          it 'returns the latest commit' do
    
            commit   = double(:commit, id: '123abc')
    
            pipeline = double(:ci_pipeline, ref: 'master')
    
    
            allow(subject).to receive(:last_commit).and_return(commit)
    
    
            expect(subject.source_project).to receive(:pipeline).
    
    Kamil Trzcinski's avatar
    Kamil Trzcinski committed
              with('123abc', 'master').
    
            expect(subject.pipeline).to eq(pipeline)
    
          end
        end
    
        describe 'when the source project does not exist' do
          it 'returns nil' do
            allow(subject).to receive(:source_project).and_return(nil)
    
    
    Yorick Peterse's avatar
    Yorick Peterse committed
    
      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, assignee: user1)
    
          expect(user1.assigned_open_merge_request_count).to eq(1)
          expect(user2.assigned_open_merge_request_count).to eq(0)
    
          mr.assignee = user2
          mr.save
    
          expect(user1.assigned_open_merge_request_count).to eq(0)
          expect(user2.assigned_open_merge_request_count).to eq(1)
        end
      end