BigW Consortium Gitlab

merge_request_spec.rb 13.6 KB
Newer Older
1 2
require 'spec_helper'

Douwe Maan committed
3
describe MergeRequest, models: true do
4 5
  subject { create(:merge_request) }

6 7 8
  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') }
9
    it { is_expected.to belong_to(:merge_user).class_name("User") }
10 11 12
    it { is_expected.to have_one(:merge_request_diff).dependent(:destroy) }
  end

13 14 15 16 17 18 19 20 21 22
  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

23 24 25 26 27
  describe "act_as_paranoid" do
    it { is_expected.to have_db_column(:deleted_at) }
    it { is_expected.to have_db_index(:deleted_at) }
  end

28
  describe 'validation' do
29 30
    it { is_expected.to validate_presence_of(:target_branch) }
    it { is_expected.to validate_presence_of(:source_branch) }
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48

    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
49 50
  end

51
  describe 'respond to' do
52 53 54
    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?) }
55 56
    it { is_expected.to respond_to(:merge_params) }
    it { is_expected.to respond_to(:merge_when_build_succeeds) }
57
  end
58

59 60 61 62 63 64
  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

65 66
  describe '#target_sha' do
    context 'when the target branch does not exist anymore' do
67 68 69 70 71 72 73
      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
74 75 76 77 78 79 80

      it 'returns nil' do
        expect(subject.target_sha).to be_nil
      end
    end
  end

81 82 83 84 85 86 87 88 89 90
  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

91 92 93 94 95 96 97
    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

98 99 100 101 102 103 104 105
    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

106 107 108 109 110 111 112 113 114
  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
115
  end
116 117

  describe "#mr_and_commit_notes" do
118
    let!(:merge_request) { create(:merge_request) }
119 120

    before do
121
      allow(merge_request).to receive(:commits) { [merge_request.source_project.repository.commit] }
122 123
      create(:note_on_commit, commit_id: merge_request.commits.first.id,
                              project: merge_request.project)
124
      create(:note, noteable: merge_request, project: merge_request.project)
125 126 127
    end

    it "should include notes for commits" do
128 129
      expect(merge_request.commits).not_to be_empty
      expect(merge_request.mr_and_commit_notes.count).to eq(2)
130
    end
131 132

    it "should include notes for commits from target project as well" do
133 134 135
      create(:note_on_commit, commit_id: merge_request.commits.first.id,
                              project: merge_request.target_project)

136 137 138
      expect(merge_request.commits).not_to be_empty
      expect(merge_request.mr_and_commit_notes.count).to eq(3)
    end
139
  end
140 141 142

  describe '#is_being_reassigned?' do
    it 'returns true if the merge_request assignee has changed' do
143
      subject.assignee = create(:user)
144
      expect(subject.is_being_reassigned?).to be_truthy
145 146
    end
    it 'returns false if the merge request assignee has not changed' do
147
      expect(subject.is_being_reassigned?).to be_falsey
148 149
    end
  end
150 151 152

  describe '#for_fork?' do
    it 'returns true if the merge request is for a fork' do
Dmitriy Zaporozhets committed
153 154
      subject.source_project = create(:project, namespace: create(:group))
      subject.target_project = create(:project, namespace: create(:group))
155

156
      expect(subject.for_fork?).to be_truthy
157
    end
Dmitriy Zaporozhets committed
158

159
    it 'returns false if is not for a fork' do
160
      expect(subject.for_fork?).to be_falsey
161 162 163
    end
  end

164 165 166
  describe 'detection of issues to be closed' do
    let(:issue0) { create :issue, project: subject.project }
    let(:issue1) { create :issue, project: subject.project }
167 168 169 170

    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}") }
171 172

    before do
173
      subject.project.team << [subject.author, :developer]
174
      allow(subject).to receive(:commits).and_return([commit0, commit1, commit2])
175 176 177
    end

    it 'accesses the set of issues that will be closed on acceptance' do
178 179
      allow(subject.project).to receive(:default_branch).
        and_return(subject.target_branch)
180

181 182 183
      closed = subject.closes_issues

      expect(closed).to include(issue0, issue1)
184 185 186
    end

    it 'only lists issues as to be closed if it targets the default branch' do
187
      allow(subject.project).to receive(:default_branch).and_return('master')
188 189
      subject.target_branch = 'something-else'

190
      expect(subject.closes_issues).to be_empty
191
    end
192 193 194

    it 'detects issues mentioned in the description' do
      issue2 = create(:issue, project: subject.project)
195
      subject.description = "Closes #{issue2.to_reference}"
196 197
      allow(subject.project).to receive(:default_branch).
        and_return(subject.target_branch)
198

199
      expect(subject.closes_issues).to include(issue2)
200
    end
201 202
  end

203
  describe "#work_in_progress?" do
204 205 206
    ['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}"
207
        expect(subject.work_in_progress?).to eq true
208
      end
209 210
    end

211 212
    it "doesn't detect WIP for words starting with WIP" do
      subject.title = "Wipwap #{subject.title}"
213
      expect(subject.work_in_progress?).to eq false
214 215
    end

216 217
    it "doesn't detect WIP for words containing with WIP" do
      subject.title = "WupWipwap #{subject.title}"
218
      expect(subject.work_in_progress?).to eq false
219 220
    end

221
    it "doesn't detect WIP by default" do
222
      expect(subject.work_in_progress?).to eq false
223 224 225
    end
  end

Zeger-Jan van de Weg committed
226
  describe '#can_remove_source_branch?' do
227 228
    let(:user) { create(:user) }
    let(:user2) { create(:user) }
229 230 231 232

    before do
      subject.source_project.team << [user, :master]

233 234 235 236
      subject.source_branch = "feature"
      subject.target_branch = "master"
      subject.save!
    end
237

238 239
    it "can't be removed when its a protected branch" do
      allow(subject.source_project).to receive(:protected_branch?).and_return(true)
240 241 242 243
      expect(subject.can_remove_source_branch?(user)).to be_falsey
    end

    it "cant remove a root ref" do
244 245
      subject.source_branch = "master"
      subject.target_branch = "feature"
246 247 248 249

      expect(subject.can_remove_source_branch?(user)).to be_falsey
    end

250 251 252 253
    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

254 255 256
    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)

257
      expect(subject.can_remove_source_branch?(user)).to be_truthy
258
    end
259 260 261 262

    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
263 264
  end

265
  describe "#reset_merge_when_build_succeeds" do
266 267 268 269
    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
270

271 272 273 274 275
    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
276 277
      expect(merge_if_green.merge_params["should_remove_source_branch"]).to be_nil
      expect(merge_if_green.merge_params["commit_message"]).to be_nil
278 279 280
    end
  end

281
  describe "#hook_attrs" do
282 283 284 285 286 287 288 289 290 291 292
    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

293
    it "has all the required keys" do
294 295 296 297
      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)
298
    end
299 300 301 302 303 304
  end

  describe '#diverged_commits_count' do
    let(:project)      { create(:project) }
    let(:fork_project) { create(:project, forked_from_project: project) }

305
    context 'when the target branch does not exist anymore' do
306 307 308 309 310 311
      subject { create(:merge_request, source_project: project, target_project: project) }

      before do
        project.repository.raw_repository.delete_branch(subject.target_branch)
        subject.reload
      end
312 313 314 315 316 317 318 319 320 321

      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

322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379
    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
380 381
  end

382
  it_behaves_like 'an editable mentionable' do
383
    subject { create(:merge_request) }
384

385 386
    let(:backref_text) { "merge request #{subject.to_reference}" }
    let(:set_mentionable_text) { ->(txt){ subject.description = txt } }
387
  end
Vinnie Okada committed
388 389

  it_behaves_like 'a Taskable' do
390
    subject { create :merge_request, :simple }
Vinnie Okada committed
391
  end
392 393 394 395 396

  describe '#ci_commit' do
    describe 'when the source project exists' do
      it 'returns the latest commit' do
        commit    = double(:commit, id: '123abc')
Kamil Trzcinski committed
397
        ci_commit = double(:ci_commit, ref: 'master')
398 399 400 401

        allow(subject).to receive(:last_commit).and_return(commit)

        expect(subject.source_project).to receive(:ci_commit).
Kamil Trzcinski committed
402
          with('123abc', 'master').
403 404 405 406 407 408 409 410 411 412 413 414 415 416
          and_return(ci_commit)

        expect(subject.ci_commit).to eq(ci_commit)
      end
    end

    describe 'when the source project does not exist' do
      it 'returns nil' do
        allow(subject).to receive(:source_project).and_return(nil)

        expect(subject.ci_commit).to be_nil
      end
    end
  end
417
end