merge_request_spec.rb 56.7 KB
Newer Older
D
Dmitriy Zaporozhets 已提交
1 2
require 'spec_helper'

3
describe MergeRequest do
4
  include RepoHelpers
B
Bob Van Landuyt 已提交
5
  include ProjectForksHelper
6

7 8
  subject { create(:merge_request) }

R
Robert Speicher 已提交
9
  describe 'associations' do
10 11
    it { is_expected.to belong_to(:target_project).class_name('Project') }
    it { is_expected.to belong_to(:source_project).class_name('Project') }
12
    it { is_expected.to belong_to(:merge_user).class_name("User") }
13
    it { is_expected.to belong_to(:assignee) }
14
    it { is_expected.to have_many(:merge_request_diffs) }
R
Robert Speicher 已提交
15 16
  end

17 18 19 20 21 22 23 24 25 26
  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

Z
Zeger-Jan van de Weg 已提交
27 28 29 30 31
  describe "act_as_paranoid" do
    it { is_expected.to have_db_column(:deleted_at) }
    it { is_expected.to have_db_index(:deleted_at) }
  end

32
  describe 'validation' do
33 34
    it { is_expected.to validate_presence_of(:target_branch) }
    it { is_expected.to validate_presence_of(:source_branch) }
Z
Zeger-Jan van de Weg 已提交
35

36
    context "Validation of merge user with Merge When Pipeline Succeeds" do
Z
Zeger-Jan van de Weg 已提交
37 38 39 40 41
      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
J
James Lopez 已提交
42
        subject.merge_when_pipeline_succeeds = true
Z
Zeger-Jan van de Weg 已提交
43 44 45 46
        expect(subject).not_to be_valid
      end

      it "is valid with merge user" do
J
James Lopez 已提交
47
        subject.merge_when_pipeline_succeeds = true
Z
Zeger-Jan van de Weg 已提交
48 49 50 51 52
        subject.merge_user = build(:user)

        expect(subject).to be_valid
      end
    end
53 54 55

    context 'for forks' do
      let(:project) { create(:project) }
B
Bob Van Landuyt 已提交
56 57
      let(:fork1) { fork_project(project) }
      let(:fork2) { fork_project(project) }
58 59 60 61 62 63 64 65

      it 'allows merge requests for sibling-forks' do
        subject.source_project = fork1
        subject.target_project = fork2

        expect(subject).to be_valid
      end
    end
D
Dmitriy Zaporozhets 已提交
66 67
  end

R
Robert Speicher 已提交
68
  describe 'respond to' do
69 70 71
    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?) }
72
    it { is_expected.to respond_to(:merge_params) }
J
James Lopez 已提交
73
    it { is_expected.to respond_to(:merge_when_pipeline_succeeds) }
74
  end
A
Andrey Kumanyaev 已提交
75

76 77 78 79 80 81
  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

82
  describe '#target_branch_sha' do
83
    let(:project) { create(:project, :repository) }
84

85
    subject { create(:merge_request, source_project: project, target_project: project) }
86

87
    context 'when the target branch does not exist' do
88
      before do
89
        project.repository.rm_branch(subject.author, subject.target_branch)
90
      end
91 92

      it 'returns nil' do
93
        expect(subject.target_branch_sha).to be_nil
94 95
      end
    end
96 97 98 99 100 101

    it 'returns memoized value' do
      subject.target_branch_sha = '8ffb3c15a5475e59ae909384297fede4badcb4c7'

      expect(subject.target_branch_sha).to eq '8ffb3c15a5475e59ae909384297fede4badcb4c7'
    end
102 103
  end

104 105 106 107 108
  describe '#card_attributes' do
    it 'includes the author name' do
      allow(subject).to receive(:author).and_return(double(name: 'Robert'))
      allow(subject).to receive(:assignee).and_return(nil)

109 110
      expect(subject.card_attributes)
        .to eq({ 'Author' => 'Robert', 'Assignee' => nil })
111 112 113 114 115 116
    end

    it 'includes the assignee name' do
      allow(subject).to receive(:author).and_return(double(name: 'Robert'))
      allow(subject).to receive(:assignee).and_return(double(name: 'Douwe'))

117 118
      expect(subject.card_attributes)
        .to eq({ 'Author' => 'Robert', 'Assignee' => 'Douwe' })
119 120 121
    end
  end

122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
  describe '#assignee_ids' do
    it 'returns an array of the assigned user id' do
      subject.assignee_id = 123

      expect(subject.assignee_ids).to eq([123])
    end
  end

  describe '#assignee_ids=' do
    it 'sets assignee_id to the last id in the array' do
      subject.assignee_ids = [123, 456]

      expect(subject.assignee_id).to eq(456)
    end
  end

138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
  describe '#assignee_or_author?' do
    let(:user) { create(:user) }

    it 'returns true for a user that is assigned to a merge request' do
      subject.assignee = 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

158 159 160 161 162 163 164 165 166 167 168
  describe '#cache_merge_request_closes_issues!' do
    before do
      subject.project.team << [subject.author, :developer]
      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])

169
      expect { subject.cache_merge_request_closes_issues!(subject.author) }.to change(subject.merge_requests_closing_issues, :count).by(1)
170 171
    end

172 173 174 175
    context 'when both internal and external issue trackers are enabled' do
      before do
        subject.project.has_external_issue_tracker = true
        subject.project.save!
176
        create(:jira_service, project: subject.project)
177 178 179 180 181 182
      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])
183

184
        expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to raise_error
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
        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
221 222 223
    end
  end

224
  describe '#source_branch_sha' do
225 226 227 228 229
    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
230
        expect(subject.source_branch_sha).to eq(last_branch_commit.sha)
231 232 233
      end
    end

234 235 236
    context 'without diffs' do
      subject { create(:merge_request, :without_diffs) }
      it 'returns the sha of the source branch last commit' do
237
        expect(subject.source_branch_sha).to eq(last_branch_commit.sha)
238 239 240
      end
    end

241 242 243
    context 'when the merge request is being created' do
      subject { build(:merge_request, source_branch: nil, compare_commits: []) }
      it 'returns nil' do
244
        expect(subject.source_branch_sha).to be_nil
245 246
      end
    end
247 248 249 250 251 252

    it 'returns memoized value' do
      subject.source_branch_sha = '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b'

      expect(subject.source_branch_sha).to eq '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b'
    end
253 254
  end

255
  describe '#to_reference' do
256
    let(:project) { build(:project, name: 'sample-project') }
257 258
    let(:merge_request) { build(:merge_request, target_project: project, iid: 1) }

259
    it 'returns a String reference to the object' do
260
      expect(merge_request.to_reference).to eq "!1"
261 262 263
    end

    it 'supports a cross-project reference' do
264
      another_project = build(:project, name: 'another-project', namespace: project.namespace)
265
      expect(merge_request.to_reference(another_project)).to eq "sample-project!1"
266
    end
267 268

    it 'returns a String reference with the full path' do
269
      expect(merge_request.to_reference(full: true)).to eq(project.full_path + '!1')
270
    end
271
  end
272

273
  describe '#raw_diffs' do
S
Sean McGivern 已提交
274 275 276 277 278 279 280
    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

281
        expect(merge_request.merge_request_diff).to receive(:raw_diffs).with(options)
S
Sean McGivern 已提交
282

283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
        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

S
Sean McGivern 已提交
298 299 300 301 302 303
  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
304
        merge_request.save
S
Sean McGivern 已提交
305

306
        expect(merge_request.merge_request_diff).to receive(:raw_diffs).with(hash_including(options))
S
Sean McGivern 已提交
307 308 309 310 311 312

        merge_request.diffs(options)
      end
    end

    context 'when there are no MR diffs' do
313
      it 'delegates to the compare object, setting expanded: true' do
S
Sean McGivern 已提交
314 315
        merge_request.compare = double(:compare)

316
        expect(merge_request.compare).to receive(:diffs).with(options.merge(expanded: true))
S
Sean McGivern 已提交
317 318 319 320 321 322

        merge_request.diffs(options)
      end
    end
  end

S
Sean McGivern 已提交
323 324 325 326 327 328
  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
329
      it 'returns the correct count' do
S
Sean McGivern 已提交
330
        merge_request.save
331 332

        expect(merge_request.diff_size).to eq('105')
S
Sean McGivern 已提交
333 334
      end

335 336 337 338 339
      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+')
S
Sean McGivern 已提交
340 341 342
      end

      it 'does not perform highlighting' do
343 344
        merge_request.save

S
Sean McGivern 已提交
345 346 347 348 349 350 351
        expect(Gitlab::Diff::Highlight).not_to receive(:new)

        merge_request.diff_size
      end
    end

    context 'when there are no MR diffs' do
352
      def set_compare(merge_request)
S
Sean McGivern 已提交
353 354 355 356 357 358 359 360 361 362
        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
363 364 365 366 367 368 369 370 371 372
        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+')
S
Sean McGivern 已提交
373 374 375
      end

      it 'does not perform highlighting' do
376 377
        set_compare(merge_request)

S
Sean McGivern 已提交
378 379 380 381 382 383 384
        expect(Gitlab::Diff::Highlight).not_to receive(:new)

        merge_request.diff_size
      end
    end
  end

385
  describe "#related_notes" do
386
    let!(:merge_request) { create(:merge_request) }
387 388

    before do
389
      allow(merge_request).to receive(:commits) { [merge_request.source_project.repository.commit] }
390 391
      create(:note_on_commit, commit_id: merge_request.commits.first.id,
                              project: merge_request.project)
D
Dmitriy Zaporozhets 已提交
392
      create(:note, noteable: merge_request, project: merge_request.project)
393 394
    end

395
    it "includes notes for commits" do
396
      expect(merge_request.commits).not_to be_empty
397
      expect(merge_request.related_notes.count).to eq(2)
398
    end
399

400
    it "includes notes for commits from target project as well" do
401 402 403
      create(:note_on_commit, commit_id: merge_request.commits.first.id,
                              project: merge_request.target_project)

404
      expect(merge_request.commits).not_to be_empty
405
      expect(merge_request.related_notes.count).to eq(3)
406
    end
407
  end
408

I
Izaak Alpert 已提交
409 410
  describe '#for_fork?' do
    it 'returns true if the merge request is for a fork' do
411 412
      subject.source_project = build_stubbed(:project, namespace: create(:group))
      subject.target_project = build_stubbed(:project, namespace: create(:group))
I
Izaak Alpert 已提交
413

414
      expect(subject.for_fork?).to be_truthy
I
Izaak Alpert 已提交
415
    end
D
Dmitriy Zaporozhets 已提交
416

I
Izaak Alpert 已提交
417
    it 'returns false if is not for a fork' do
418
      expect(subject.for_fork?).to be_falsey
I
Izaak Alpert 已提交
419 420 421
    end
  end

422
  describe '#closes_issues' do
423 424
    let(:issue0) { create :issue, project: subject.project }
    let(:issue1) { create :issue, project: subject.project }
425 426 427 428

    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}") }
429 430

    before do
431
      subject.project.team << [subject.author, :developer]
432
      allow(subject).to receive(:commits).and_return([commit0, commit1, commit2])
433 434
    end

435
    it 'accesses the set of issues that will be closed on acceptance' do
436 437
      allow(subject.project).to receive(:default_branch)
        .and_return(subject.target_branch)
438

439
      closed = subject.closes_issues
440

441 442
      expect(closed).to include(issue0, issue1)
    end
443

444 445 446
    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'
447

448
      expect(subject.closes_issues).to be_empty
449
    end
450
  end
451

452
  describe '#issues_mentioned_but_not_closing' do
453 454 455 456
    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}") }
457

458
    it 'detects issues mentioned in description but not closed' do
459
      subject.project.team << [subject.author, :developer]
460
      subject.description = "Is related to #{mentioned_issue.to_reference} and #{closing_issue.to_reference}"
461

462
      allow(subject).to receive(:commits).and_return([commit])
463 464
      allow(subject.project).to receive(:default_branch)
        .and_return(subject.target_branch)
465

466
      expect(subject.issues_mentioned_but_not_closing(subject.author)).to match_array([mentioned_issue])
467
    end
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484

    context 'when the project has an external issue tracker' do
      before do
        subject.project.team << [subject.author, :developer]
        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
        expect(subject.issues_mentioned_but_not_closing(subject.author).map(&:to_s)).to match_array(['TEST-2'])
      end
    end
485 486
  end

487
  describe "#work_in_progress?" do
488 489 490
    ['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}"
491
        expect(subject.work_in_progress?).to eq true
492
      end
T
Ted Hogan 已提交
493 494
    end

495 496
    it "doesn't detect WIP for words starting with WIP" do
      subject.title = "Wipwap #{subject.title}"
497
      expect(subject.work_in_progress?).to eq false
498 499
    end

500 501
    it "doesn't detect WIP for words containing with WIP" do
      subject.title = "WupWipwap #{subject.title}"
502
      expect(subject.work_in_progress?).to eq false
503 504
    end

505
    it "doesn't detect WIP by default" do
506
      expect(subject.work_in_progress?).to eq false
507 508 509
    end
  end

T
Thomas Balthazar 已提交
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532
  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
533
    end
T
Thomas Balthazar 已提交
534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549

    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

Z
Zeger-Jan van de Weg 已提交
550
  describe '#can_remove_source_branch?' do
Z
Zeger-Jan van de Weg 已提交
551 552
    let(:user) { create(:user) }
    let(:user2) { create(:user) }
553 554 555 556

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

Z
Zeger-Jan van de Weg 已提交
557 558 559 560
      subject.source_branch = "feature"
      subject.target_branch = "master"
      subject.save!
    end
561

Z
Zeger-Jan van de Weg 已提交
562
    it "can't be removed when its a protected branch" do
563
      allow(ProtectedBranch).to receive(:protected?).and_return(true)
564 565 566
      expect(subject.can_remove_source_branch?(user)).to be_falsey
    end

567
    it "can't remove a root ref" do
Z
Zeger-Jan van de Weg 已提交
568 569
      subject.source_branch = "master"
      subject.target_branch = "feature"
570 571 572 573

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

Z
Zeger-Jan van de Weg 已提交
574 575 576 577
    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

578
    it "can be removed if the last commit is the head of the source branch" do
579
      allow(subject).to receive(:source_branch_head).and_return(subject.diff_head_commit)
580

Z
Zeger-Jan van de Weg 已提交
581
      expect(subject.can_remove_source_branch?(user)).to be_truthy
582
    end
583 584

    it "cannot be removed if the last commit is not also the head of the source branch" do
585 586
      subject.source_branch = "lfs"

587 588
      expect(subject.can_remove_source_branch?(user)).to be_falsey
    end
589 590
  end

591 592 593 594 595 596 597 598 599 600 601 602 603 604 605
  describe '#merge_commit_message' do
    it 'includes merge information as the title' do
      request = build(:merge_request, source_branch: 'source', target_branch: 'target')

      expect(request.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.merge_commit_message)
        .to match("Remove all technical debt\n\n")
    end

606
    it 'includes its closed issues in the body' do
607
      issue = create(:issue, project: subject.project)
608

609
      subject.project.team << [subject.author, :developer]
610
      subject.description = "This issue Closes #{issue.to_reference}"
611

612 613
      allow(subject.project).to receive(:default_branch)
        .and_return(subject.target_branch)
614 615

      expect(subject.merge_commit_message)
616
        .to match("Closes #{issue.to_reference}")
617 618 619 620 621 622
    end

    it 'includes its reference in the body' do
      request = build_stubbed(:merge_request)

      expect(request.merge_commit_message)
623
        .to match("See merge request #{request.to_reference(full: true)}")
624 625 626 627 628 629 630
    end

    it 'excludes multiple linebreak runs when description is blank' do
      request = build(:merge_request, title: 'Title', description: nil)

      expect(request.merge_commit_message).not_to match("Title\n\n\n\n")
    end
631 632 633 634 635 636 637 638 639 640 641 642 643 644

    it 'includes its description in the body' do
      request = build(:merge_request, description: 'By removing all code')

      expect(request.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.merge_commit_message)
        .not_to match("By removing all code\n\n")
    end
645 646
  end

J
James Lopez 已提交
647
  describe "#reset_merge_when_pipeline_succeeds" do
648
    let(:merge_if_green) do
J
James Lopez 已提交
649
      create :merge_request, merge_when_pipeline_succeeds: true, merge_user: create(:user),
650 651
                             merge_params: { "should_remove_source_branch" => "1", "commit_message" => "msg" }
    end
Z
Zeger-Jan van de Weg 已提交
652

653
    it "sets the item to false" do
J
James Lopez 已提交
654
      merge_if_green.reset_merge_when_pipeline_succeeds
655 656
      merge_if_green.reload

J
James Lopez 已提交
657
      expect(merge_if_green.merge_when_pipeline_succeeds).to be_falsey
658 659
      expect(merge_if_green.merge_params["should_remove_source_branch"]).to be_nil
      expect(merge_if_green.merge_params["commit_message"]).to be_nil
660 661 662
    end
  end

663
  describe '#hook_attrs' do
664 665 666 667 668 669 670 671
    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
672
    end
673 674 675
  end

  describe '#diverged_commits_count' do
676
    let(:project)      { create(:project, :repository) }
B
Bob Van Landuyt 已提交
677
    let(:forked_project) { fork_project(project, nil, repository: true) }
678

679
    context 'when the target branch does not exist anymore' do
680 681 682 683 684 685
      subject { create(:merge_request, source_project: project, target_project: project) }

      before do
        project.repository.raw_repository.delete_branch(subject.target_branch)
        subject.reload
      end
686 687

      it 'does not crash' do
688
        expect { subject.diverged_commits_count }.not_to raise_error
689 690 691 692 693 694 695
      end

      it 'returns 0' do
        expect(subject.diverged_commits_count).to eq(0)
      end
    end

696 697 698 699
    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
700
        expect(subject.diverged_commits_count).to eq(29)
701 702 703 704
      end
    end

    context 'diverged on fork' do
B
Bob Van Landuyt 已提交
705
      subject(:merge_request_fork_with_divergence) { create(:merge_request, :diverged, source_project: forked_project, target_project: project) }
706 707

      it 'counts commits that are on target branch but not on source branch' do
708
        expect(subject.diverged_commits_count).to eq(29)
709 710 711 712
      end
    end

    context 'rebased on fork' do
B
Bob Van Landuyt 已提交
713
      subject(:merge_request_rebased) { create(:merge_request, :rebased, source_project: forked_project, target_project: project) }
714 715 716 717 718 719 720

      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
721
      before do
722 723 724 725
        allow(Rails).to receive(:cache).and_return(ActiveSupport::Cache::MemoryStore.new)
      end

      it 'caches the output' do
726 727 728
        expect(subject).to receive(:compute_diverged_commits_count)
          .once
          .and_return(2)
729 730 731 732 733 734

        subject.diverged_commits_count
        subject.diverged_commits_count
      end

      it 'invalidates the cache when the source sha changes' do
735 736 737
        expect(subject).to receive(:compute_diverged_commits_count)
          .twice
          .and_return(2)
738 739

        subject.diverged_commits_count
740
        allow(subject).to receive(:source_branch_sha).and_return('123abc')
741 742 743 744
        subject.diverged_commits_count
      end

      it 'invalidates the cache when the target sha changes' do
745 746 747
        expect(subject).to receive(:compute_diverged_commits_count)
          .twice
          .and_return(2)
748 749

        subject.diverged_commits_count
750
        allow(subject).to receive(:target_branch_sha).and_return('123abc')
751 752 753
        subject.diverged_commits_count
      end
    end
754 755
  end

756
  it_behaves_like 'an editable mentionable' do
757
    subject { create(:merge_request, :simple) }
758

759
    let(:backref_text) { "merge request #{subject.to_reference}" }
760
    let(:set_mentionable_text) { ->(txt) { subject.description = txt } }
761
  end
V
Vinnie Okada 已提交
762 763

  it_behaves_like 'a Taskable' do
764
    subject { create :merge_request, :simple }
V
Vinnie Okada 已提交
765
  end
766

767
  describe '#commit_shas' do
768
    before do
769
      allow(subject.merge_request_diff).to receive(:commit_shas)
770
        .and_return(['sha1'])
771 772
    end

773
    it 'delegates to merge request diff' do
774
      expect(subject.commit_shas).to eq ['sha1']
775 776 777
    end
  end

778
  describe '#head_pipeline' do
779
    describe 'when the source project exists' do
780
      it 'returns the latest pipeline' do
781
        pipeline = create(:ci_empty_pipeline, project: subject.source_project, ref: 'master', status: 'running', sha: "123abc", head_pipeline_of: subject)
782

783
        expect(subject.head_pipeline).to eq(pipeline)
784 785 786 787 788 789 790
      end
    end

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

791
        expect(subject.head_pipeline).to be_nil
792 793 794
      end
    end
  end
Y
Yorick Peterse 已提交
795

796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838
  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

839
  describe '#all_pipelines' do
840
    shared_examples 'returning pipelines with proper ordering' do
841
      let!(:all_pipelines) do
842
        subject.all_commit_shas.map do |sha|
843 844 845 846
          create(:ci_empty_pipeline,
                 project: subject.source_project,
                 sha: sha,
                 ref: subject.source_branch)
847 848 849 850 851
        end
      end

      it 'returns all pipelines' do
        expect(subject.all_pipelines).not_to be_empty
852
        expect(subject.all_pipelines).to eq(all_pipelines.reverse)
853
      end
854 855
    end

856 857 858 859 860 861
    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
862
        subject.update(target_branch: 'v1.0.0')
863 864 865
      end

      it_behaves_like 'returning pipelines with proper ordering'
866
    end
867 868

    context 'with unsaved merge request' do
869
      subject { build(:merge_request) }
870 871 872

      let!(:pipeline) do
        create(:ci_empty_pipeline,
873
               project: subject.project,
874 875
               sha: subject.diff_head_sha,
               ref: subject.source_branch)
876
      end
877 878 879 880 881

      it 'returns pipelines from diff_head_sha' do
        expect(subject.all_pipelines).to contain_exactly(pipeline)
      end
    end
882 883
  end

884
  describe '#all_commit_shas' do
885
    context 'when merge request is persisted' do
886
      let(:all_commit_shas) do
887 888
        subject.merge_request_diffs.flat_map(&:commits).map(&:sha).uniq
      end
889

890 891 892
      shared_examples 'returning all SHA' do
        it 'returns all SHA from all merge_request_diffs' do
          expect(subject.merge_request_diffs.size).to eq(2)
893
          expect(subject.all_commit_shas).to match_array(all_commit_shas)
894
        end
895 896
      end

897 898 899 900 901 902
      context 'with a completely different branch' do
        before do
          subject.update(target_branch: 'v1.0.0')
        end

        it_behaves_like 'returning all SHA'
903 904
      end

905 906 907 908 909 910 911 912
      context 'with a branch having no difference' do
        before do
          subject.update(target_branch: 'v1.1.0')
          subject.reload # make sure commits were not cached
        end

        it_behaves_like 'returning all SHA'
      end
913 914
    end

915 916 917 918 919 920 921 922 923
    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
924
          expect(subject.all_commit_shas).to eq [commit, commit]
925
        end
926 927
      end

928 929 930 931
      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
932
          expect(subject.all_commit_shas).to eq [subject.diff_head_sha]
933 934
        end
      end
935 936 937
    end
  end

Y
Yorick Peterse 已提交
938
  describe '#participants' do
939
    let(:project) { create(:project, :public) }
Y
Yorick Peterse 已提交
940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960

    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
J
Josh Frye 已提交
961 962 963 964 965 966

  describe 'cached counts' do
    it 'updates when assignees change' do
      user1 = create(:user)
      user2 = create(:user)
      mr = create(:merge_request, assignee: user1)
967 968
      mr.project.add_developer(user1)
      mr.project.add_developer(user2)
J
Josh Frye 已提交
969

970 971
      expect(user1.assigned_open_merge_requests_count).to eq(1)
      expect(user2.assigned_open_merge_requests_count).to eq(0)
J
Josh Frye 已提交
972 973 974 975

      mr.assignee = user2
      mr.save

976 977
      expect(user1.assigned_open_merge_requests_count).to eq(0)
      expect(user2.assigned_open_merge_requests_count).to eq(1)
J
Josh Frye 已提交
978 979
    end
  end
980

981
  describe '#merge_async' do
982 983 984 985 986 987 988 989 990 991
    it 'enqueues MergeWorker job and updates merge_jid' do
      merge_request = create(:merge_request)
      user_id = double(:user_id)
      params = double(:params)
      merge_jid = 'hash-123'

      expect(MergeWorker).to receive(:perform_async).with(merge_request.id, user_id, params) do
        merge_jid
      end

992
      merge_request.merge_async(user_id, params)
993 994 995 996 997

      expect(merge_request.reload.merge_jid).to eq(merge_jid)
    end
  end

998
  describe '#check_if_can_be_merged' do
999
    let(:project) { create(:project, only_allow_merge_if_pipeline_succeeds: true) }
1000 1001 1002 1003

    subject { create(:merge_request, source_project: project, merge_status: :unchecked) }

    context 'when it is not broken and has no conflicts' do
1004
      before do
1005
        allow(subject).to receive(:broken?) { false }
1006
        allow(project.repository).to receive(:can_be_merged?).and_return(true)
1007
      end
1008

1009
      it 'is marked as mergeable' do
1010 1011 1012 1013 1014
        expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('can_be_merged')
      end
    end

    context 'when broken' do
1015 1016 1017
      before do
        allow(subject).to receive(:broken?) { true }
      end
1018 1019 1020 1021 1022 1023 1024 1025 1026

      it 'becomes unmergeable' do
        expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('cannot_be_merged')
      end
    end

    context 'when it has conflicts' do
      before do
        allow(subject).to receive(:broken?) { false }
1027
        allow(project.repository).to receive(:can_be_merged?).and_return(false)
1028 1029 1030 1031 1032 1033 1034 1035 1036
      end

      it 'becomes unmergeable' do
        expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('cannot_be_merged')
      end
    end
  end

  describe '#mergeable?' do
1037
    let(:project) { create(:project) }
1038 1039 1040

    subject { create(:merge_request, source_project: project) }

1041 1042
    it 'returns false if #mergeable_state? is false' do
      expect(subject).to receive(:mergeable_state?) { false }
1043

1044
      expect(subject.mergeable?).to be_falsey
1045 1046
    end

1047
    it 'return true if #mergeable_state? is true and the MR #can_be_merged? is true' do
1048 1049
      allow(subject).to receive(:mergeable_state?) { true }
      expect(subject).to receive(:check_if_can_be_merged)
1050
      expect(subject).to receive(:can_be_merged?) { true }
1051 1052 1053 1054 1055 1056

      expect(subject.mergeable?).to be_truthy
    end
  end

  describe '#mergeable_state?' do
1057
    let(:project) { create(:project, :repository) }
1058 1059 1060

    subject { create(:merge_request, source_project: project) }

1061
    it 'checks if merge request can be merged' do
1062
      allow(subject).to receive(:mergeable_ci_state?) { true }
1063 1064 1065 1066 1067 1068
      expect(subject).to receive(:check_if_can_be_merged)

      subject.mergeable?
    end

    context 'when not open' do
1069 1070 1071
      before do
        subject.close
      end
1072 1073

      it 'returns false' do
1074
        expect(subject.mergeable_state?).to be_falsey
1075 1076 1077 1078
      end
    end

    context 'when working in progress' do
1079 1080 1081
      before do
        subject.title = 'WIP MR'
      end
1082 1083

      it 'returns false' do
1084
        expect(subject.mergeable_state?).to be_falsey
1085 1086 1087 1088
      end
    end

    context 'when broken' do
1089 1090 1091
      before do
        allow(subject).to receive(:broken?) { true }
      end
1092 1093

      it 'returns false' do
1094
        expect(subject.mergeable_state?).to be_falsey
1095 1096 1097 1098
      end
    end

    context 'when failed' do
R
Rémy Coutable 已提交
1099
      context 'when #mergeable_ci_state? is false' do
1100
        before do
1101
          allow(subject).to receive(:mergeable_ci_state?) { false }
1102 1103 1104 1105
        end

        it 'returns false' do
          expect(subject.mergeable_state?).to be_falsey
1106 1107
        end
      end
1108

R
Rémy Coutable 已提交
1109
      context 'when #mergeable_discussions_state? is false' do
1110 1111 1112 1113 1114 1115 1116 1117
        before do
          allow(subject).to receive(:mergeable_discussions_state?) { false }
        end

        it 'returns false' do
          expect(subject.mergeable_state?).to be_falsey
        end
      end
1118 1119 1120
    end
  end

1121
  describe '#mergeable_ci_state?' do
1122
    let(:project) { create(:project, only_allow_merge_if_pipeline_succeeds: true) }
R
Rémy Coutable 已提交
1123
    let(:pipeline) { create(:ci_empty_pipeline) }
1124 1125 1126

    subject { build(:merge_request, target_project: project) }

1127
    context 'when it is only allowed to merge when build is green' do
R
Rémy Coutable 已提交
1128
      context 'and a failed pipeline is associated' do
1129
        before do
1130
          pipeline.update(status: 'failed')
1131
          allow(subject).to receive(:head_pipeline) { pipeline }
1132
        end
1133

1134
        it { expect(subject.mergeable_ci_state?).to be_falsey }
1135 1136
      end

1137 1138 1139
      context 'and a successful pipeline is associated' do
        before do
          pipeline.update(status: 'success')
1140
          allow(subject).to receive(:head_pipeline) { pipeline }
1141 1142 1143 1144 1145 1146 1147 1148
        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')
1149
          allow(subject).to receive(:head_pipeline) { pipeline }
1150 1151 1152 1153 1154
        end

        it { expect(subject.mergeable_ci_state?).to be_truthy }
      end

R
Rémy Coutable 已提交
1155
      context 'when no pipeline is associated' do
1156
        before do
1157
          allow(subject).to receive(:head_pipeline) { nil }
1158 1159 1160
        end

        it { expect(subject.mergeable_ci_state?).to be_truthy }
1161 1162 1163
      end
    end

1164
    context 'when merges are not restricted to green builds' do
1165
      subject { build(:merge_request, target_project: build(:project, only_allow_merge_if_pipeline_succeeds: false)) }
1166

R
Rémy Coutable 已提交
1167
      context 'and a failed pipeline is associated' do
1168
        before do
R
Rémy Coutable 已提交
1169
          pipeline.statuses << create(:commit_status, status: 'failed', project: project)
1170
          allow(subject).to receive(:head_pipeline) { pipeline }
1171 1172 1173 1174 1175
        end

        it { expect(subject.mergeable_ci_state?).to be_truthy }
      end

R
Rémy Coutable 已提交
1176
      context 'when no pipeline is associated' do
1177
        before do
1178
          allow(subject).to receive(:head_pipeline) { nil }
1179 1180 1181
        end

        it { expect(subject.mergeable_ci_state?).to be_truthy }
1182 1183 1184
      end
    end
  end
1185

1186
  describe '#mergeable_discussions_state?' do
R
Rémy Coutable 已提交
1187
    let(:merge_request) { create(:merge_request_with_diff_notes, source_project: project) }
1188

R
Rémy Coutable 已提交
1189
    context 'when project.only_allow_merge_if_all_discussions_are_resolved == true' do
1190
      let(:project) { create(:project, :repository, only_allow_merge_if_all_discussions_are_resolved: true) }
1191

R
Rémy Coutable 已提交
1192
      context 'with all discussions resolved' do
1193
        before do
R
Rémy Coutable 已提交
1194
          merge_request.discussions.each { |d| d.resolve!(merge_request.author) }
1195 1196 1197
        end

        it 'returns true' do
R
Rémy Coutable 已提交
1198
          expect(merge_request.mergeable_discussions_state?).to be_truthy
1199 1200 1201
        end
      end

R
Rémy Coutable 已提交
1202
      context 'with unresolved discussions' do
1203
        before do
R
Rémy Coutable 已提交
1204
          merge_request.discussions.each(&:unresolve!)
1205 1206 1207
        end

        it 'returns false' do
R
Rémy Coutable 已提交
1208
          expect(merge_request.mergeable_discussions_state?).to be_falsey
1209 1210
        end
      end
1211 1212 1213 1214 1215 1216 1217 1218 1219 1220

      context 'with no discussions' do
        before do
          merge_request.notes.destroy_all
        end

        it 'returns true' do
          expect(merge_request.mergeable_discussions_state?).to be_truthy
        end
      end
1221 1222
    end

R
Rémy Coutable 已提交
1223
    context 'when project.only_allow_merge_if_all_discussions_are_resolved == false' do
1224
      let(:project) { create(:project, :repository, only_allow_merge_if_all_discussions_are_resolved: false) }
1225

R
Rémy Coutable 已提交
1226
      context 'with unresolved discussions' do
1227
        before do
R
Rémy Coutable 已提交
1228
          merge_request.discussions.each(&:unresolve!)
1229 1230 1231
        end

        it 'returns true' do
R
Rémy Coutable 已提交
1232
          expect(merge_request.mergeable_discussions_state?).to be_truthy
1233 1234 1235 1236 1237
        end
      end
    end
  end

D
Douwe Maan 已提交
1238
  describe "#environments_for" do
1239
    let(:project)       { create(:project, :repository) }
D
Douwe Maan 已提交
1240
    let(:user)          { project.creator }
Z
Z.J. van de Weg 已提交
1241 1242
    let(:merge_request) { create(:merge_request, source_project: project) }

D
Douwe Maan 已提交
1243 1244 1245 1246 1247
    before do
      merge_request.source_project.add_master(user)
      merge_request.target_project.add_master(user)
    end

1248 1249
    context 'with multiple environments' do
      let(:environments) { create_list(:environment, 3, project: project) }
1250

1251 1252 1253 1254 1255 1256
      before do
        create(:deployment, environment: environments.first, ref: 'master', sha: project.commit('master').id)
        create(:deployment, environment: environments.second, ref: 'feature', sha: project.commit('feature').id)
      end

      it 'selects deployed environments' do
D
Douwe Maan 已提交
1257
        expect(merge_request.environments_for(user)).to contain_exactly(environments.first)
1258 1259 1260 1261
      end
    end

    context 'with environments on source project' do
B
Bob Van Landuyt 已提交
1262
      let(:source_project) { fork_project(project, nil, repository: true) }
K
Kamil Trzcinski 已提交
1263

1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276
      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, environment: source_environment, ref: 'feature', sha: merge_request.diff_head_sha)
      end

      it 'selects deployed environments' do
D
Douwe Maan 已提交
1277
        expect(merge_request.environments_for(user)).to contain_exactly(source_environment)
1278 1279 1280 1281 1282 1283 1284 1285 1286 1287
      end

      context 'with environments on target project' do
        let(:target_environment) { create(:environment, project: project) }

        before do
          create(:deployment, environment: target_environment, tag: true, sha: merge_request.diff_head_sha)
        end

        it 'selects deployed environments' do
D
Douwe Maan 已提交
1288
          expect(merge_request.environments_for(user)).to contain_exactly(source_environment, target_environment)
1289 1290
        end
      end
1291 1292 1293 1294 1295 1296 1297 1298
    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
D
Douwe Maan 已提交
1299
        expect(merge_request.environments_for(user)).to be_empty
1300
      end
Z
Z.J. van de Weg 已提交
1301 1302 1303
    end
  end

1304
  describe "#reload_diff" do
1305
    let(:discussion) { create(:diff_note_on_merge_request, project: subject.project, noteable: subject).to_discussion }
1306 1307
    let(:commit) { subject.project.commit(sample_commit.id) }

1308
    it "does not change existing merge request diff" do
1309
      expect(subject.merge_request_diff).not_to receive(:save_git_content)
1310 1311 1312
      subject.reload_diff
    end

1313 1314 1315 1316
    it "creates new merge request diff" do
      expect { subject.reload_diff }.to change { subject.merge_request_diffs.count }.by(1)
    end

1317 1318 1319 1320 1321 1322
    it "executs diff cache service" do
      expect_any_instance_of(MergeRequests::MergeRequestDiffCacheService).to receive(:execute).with(subject)

      subject.reload_diff
    end

1323 1324 1325 1326 1327 1328
    it "calls update_diff_discussion_positions" do
      expect(subject).to receive(:update_diff_discussion_positions)

      subject.reload_diff
    end
  end
1329

1330 1331 1332 1333 1334 1335
  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
1336
      # Update merge_request_diff so that #diff_refs will return commit.diff_refs
1337 1338 1339 1340 1341 1342
      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
        )
1343 1344

        subject.merge_request_diff(true)
1345
      end
1346
    end
1347

1348
    it "updates diff discussion positions" do
1349
      expect(Discussions::UpdateDiffPositionService).to receive(:new).with(
1350
        subject.project,
1351
        subject.author,
1352 1353
        old_diff_refs: old_diff_refs,
        new_diff_refs: commit.diff_refs,
1354
        paths: discussion.position.paths
1355 1356
      ).and_call_original

1357
      expect_any_instance_of(Discussions::UpdateDiffPositionService).to receive(:execute).with(discussion).and_call_original
1358 1359
      expect_any_instance_of(DiffNote).to receive(:save).once

1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379
      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
1380 1381
    end
  end
1382 1383 1384 1385 1386 1387 1388 1389 1390

  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
1391
        subject.project.repository.rm_branch(subject.author, subject.target_branch)
1392 1393 1394 1395
      end

      it 'returns nil' do
        expect(subject.branch_merge_base_commit).to be_nil
1396 1397 1398 1399
      end
    end
  end

1400
  describe "#diff_refs" do
1401 1402 1403 1404 1405 1406 1407 1408
    context "with diffs" do
      subject { create(:merge_request, :with_diffs) }

      it "does not touch the repository" do
        subject # Instantiate the object

        expect_any_instance_of(Repository).not_to receive(:commit)

1409
        subject.diff_refs
1410 1411 1412 1413 1414 1415 1416 1417 1418
      end

      it "returns expected diff_refs" do
        expected_diff_refs = 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
        )

1419
        expect(subject.diff_refs).to eq(expected_diff_refs)
1420 1421 1422
      end
    end
  end
1423

1424
  describe "#source_project_missing?" do
1425
    let(:project)      { create(:project) }
B
Bob Van Landuyt 已提交
1426
    let(:forked_project) { fork_project(project) }
1427
    let(:user)         { create(:user) }
B
Bob Van Landuyt 已提交
1428
    let(:unlink_project) { Projects::UnlinkForkService.new(forked_project, user) }
1429

K
Katarzyna Kobierska 已提交
1430
    context "when the fork exists" do
1431 1432
      let(:merge_request) do
        create(:merge_request,
B
Bob Van Landuyt 已提交
1433
          source_project: forked_project,
1434 1435 1436
          target_project: project)
      end

1437
      it { expect(merge_request.source_project_missing?).to be_falsey }
1438 1439
    end

K
Katarzyna Kobierska 已提交
1440
    context "when the source project is the same as the target project" do
1441 1442
      let(:merge_request) { create(:merge_request, source_project: project) }

1443
      it { expect(merge_request.source_project_missing?).to be_falsey }
1444 1445
    end

K
Katarzyna Kobierska 已提交
1446
    context "when the fork does not exist" do
1447
      let!(:merge_request) do
1448
        create(:merge_request,
B
Bob Van Landuyt 已提交
1449
          source_project: forked_project,
1450 1451 1452
          target_project: project)
      end

K
Katarzyna Kobierska 已提交
1453
      it "returns true" do
1454 1455 1456
        unlink_project.execute
        merge_request.reload

1457
        expect(merge_request.source_project_missing?).to be_truthy
1458 1459 1460 1461
      end
    end
  end

1462
  describe '#merge_ongoing?' do
1463 1464 1465 1466 1467 1468
    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

1469
    it 'returns true when merge_id, MR is not merged and it has no running job' do
1470
      merge_request = build_stubbed(:merge_request, state: :open, merge_jid: 'foo')
1471
      allow(Gitlab::SidekiqStatus).to receive(:running?).with('foo') { true }
1472 1473 1474

      expect(merge_request.merge_ongoing?).to be(true)
    end
1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489

    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')
1490
      allow(Gitlab::SidekiqStatus).to receive(:running?).with('foo') { false }
1491 1492 1493

      expect(merge_request.merge_ongoing?).to be(false)
    end
1494 1495
  end

1496
  describe "#closed_without_fork?" do
1497
    let(:project)      { create(:project) }
B
Bob Van Landuyt 已提交
1498
    let(:forked_project) { fork_project(project) }
1499
    let(:user)         { create(:user) }
B
Bob Van Landuyt 已提交
1500
    let(:unlink_project) { Projects::UnlinkForkService.new(forked_project, user) }
1501

K
Katarzyna Kobierska 已提交
1502
    context "when the merge request is closed" do
1503 1504
      let(:closed_merge_request) do
        create(:closed_merge_request,
B
Bob Van Landuyt 已提交
1505
          source_project: forked_project,
1506 1507 1508
          target_project: project)
      end

K
Katarzyna Kobierska 已提交
1509
      it "returns false if the fork exist" do
1510 1511 1512
        expect(closed_merge_request.closed_without_fork?).to be_falsey
      end

K
Katarzyna Kobierska 已提交
1513
      it "returns true if the fork does not exist" do
1514 1515 1516 1517 1518 1519
        unlink_project.execute
        closed_merge_request.reload

        expect(closed_merge_request.closed_without_fork?).to be_truthy
      end
    end
K
Katarzyna Kobierska 已提交
1520

K
Katarzyna Kobierska 已提交
1521
    context "when the merge request is open" do
K
Katarzyna Kobierska 已提交
1522 1523
      let(:open_merge_request) do
        create(:merge_request,
B
Bob Van Landuyt 已提交
1524
          source_project: forked_project,
K
Katarzyna Kobierska 已提交
1525 1526 1527 1528 1529 1530 1531
          target_project: project)
      end

      it "returns false" do
        expect(open_merge_request.closed_without_fork?).to be_falsey
      end
    end
1532
  end
1533

1534
  describe '#reopenable?' do
K
Katarzyna Kobierska 已提交
1535 1536 1537
    context 'when the merge request is closed' do
      it 'returns true' do
        subject.close
K
Katarzyna Kobierska 已提交
1538

1539
        expect(subject.reopenable?).to be_truthy
K
Katarzyna Kobierska 已提交
1540 1541 1542
      end

      context 'forked project' do
B
Bob Van Landuyt 已提交
1543
        let(:project)      { create(:project, :public) }
K
Katarzyna Kobierska 已提交
1544
        let(:user)         { create(:user) }
B
Bob Van Landuyt 已提交
1545
        let(:forked_project) { fork_project(project, user) }
1546 1547

        let!(:merge_request) do
K
Katarzyna Kobierska 已提交
1548
          create(:closed_merge_request,
B
Bob Van Landuyt 已提交
1549
            source_project: forked_project,
K
Katarzyna Kobierska 已提交
1550 1551 1552 1553
            target_project: project)
        end

        it 'returns false if unforked' do
B
Bob Van Landuyt 已提交
1554
          Projects::UnlinkForkService.new(forked_project, user).execute
K
Katarzyna Kobierska 已提交
1555

1556
          expect(merge_request.reload.reopenable?).to be_falsey
K
Katarzyna Kobierska 已提交
1557 1558 1559
        end

        it 'returns false if the source project is deleted' do
B
Bob Van Landuyt 已提交
1560
          Projects::DestroyService.new(forked_project, user).execute
K
Katarzyna Kobierska 已提交
1561

1562
          expect(merge_request.reload.reopenable?).to be_falsey
K
Katarzyna Kobierska 已提交
1563 1564
        end

K
Katarzyna Kobierska 已提交
1565
        it 'returns false if the merge request is merged' do
K
Katarzyna Kobierska 已提交
1566 1567
          merge_request.update_attributes(state: 'merged')

1568
          expect(merge_request.reload.reopenable?).to be_falsey
K
Katarzyna Kobierska 已提交
1569 1570
        end
      end
K
Katarzyna Kobierska 已提交
1571 1572
    end

K
Katarzyna Kobierska 已提交
1573
    context 'when the merge request is opened' do
K
Katarzyna Kobierska 已提交
1574
      it 'returns false' do
1575
        expect(subject.reopenable?).to be_falsey
K
Katarzyna Kobierska 已提交
1576
      end
K
Katarzyna Kobierska 已提交
1577 1578
    end
  end
1579

1580
  describe '#mergeable_with_quick_action?' do
1581
    def create_pipeline(status)
F
Felipe Artur 已提交
1582
      pipeline = create(:ci_pipeline_with_one_job,
1583 1584 1585
        project: project,
        ref:     merge_request.source_branch,
        sha:     merge_request.diff_head_sha,
1586 1587
        status:  status,
        head_pipeline_of: merge_request)
F
Felipe Artur 已提交
1588 1589

      pipeline
1590 1591
    end

J
James Lopez 已提交
1592
    let(:project)       { create(:project, :public, :repository, only_allow_merge_if_pipeline_succeeds: true) }
1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603
    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.team << [developer, :developer]
    end

    context 'when autocomplete_precheck is set to true' do
      it 'is mergeable by developer' do
1604
        expect(merge_request.mergeable_with_quick_action?(developer, autocomplete_precheck: true)).to be_truthy
1605 1606 1607
      end

      it 'is not mergeable by normal user' do
1608
        expect(merge_request.mergeable_with_quick_action?(user, autocomplete_precheck: true)).to be_falsey
1609 1610 1611 1612 1613
      end
    end

    context 'when autocomplete_precheck is set to false' do
      it 'is mergeable by developer' do
1614
        expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_truthy
1615 1616 1617
      end

      it 'is not mergeable by normal user' do
1618
        expect(merge_request.mergeable_with_quick_action?(user, last_diff_sha: mr_sha)).to be_falsey
1619 1620 1621 1622 1623 1624 1625 1626
      end

      context 'closed MR'  do
        before do
          merge_request.update_attribute(:state, :closed)
        end

        it 'is not mergeable' do
1627
          expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_falsey
1628 1629 1630 1631 1632 1633 1634 1635 1636
        end
      end

      context 'MR with WIP'  do
        before do
          merge_request.update_attribute(:title, 'WIP: some MR')
        end

        it 'is not mergeable' do
1637
          expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_falsey
1638 1639 1640 1641 1642
        end
      end

      context 'sha differs from the MR diff_head_sha'  do
        it 'is not mergeable' do
1643
          expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: 'some other sha')).to be_falsey
1644 1645 1646
        end
      end

J
Jarka Kadlecova 已提交
1647 1648
      context 'sha is not provided'  do
        it 'is not mergeable' do
1649
          expect(merge_request.mergeable_with_quick_action?(developer)).to be_falsey
J
Jarka Kadlecova 已提交
1650 1651 1652
        end
      end

1653 1654 1655 1656 1657 1658
      context 'with pipeline ok'  do
        before do
          create_pipeline(:success)
        end

        it 'is mergeable' do
1659
          expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_truthy
1660 1661 1662 1663 1664 1665 1666 1667 1668
        end
      end

      context 'with failing pipeline'  do
        before do
          create_pipeline(:failed)
        end

        it 'is not mergeable' do
1669
          expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_falsey
1670 1671 1672 1673 1674
        end
      end

      context 'with running pipeline'  do
        before do
F
Felipe Artur 已提交
1675
          create_pipeline(:running)
1676 1677 1678
        end

        it 'is mergeable' do
1679
          expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_truthy
1680 1681 1682 1683 1684
        end
      end
    end
  end

1685 1686
  describe '#has_commits?' do
    before do
1687 1688
      allow(subject.merge_request_diff).to receive(:commits_count)
        .and_return(2)
1689 1690 1691 1692 1693 1694 1695 1696 1697
    end

    it 'returns true when merge request diff has commits' do
      expect(subject.has_commits?).to be_truthy
    end
  end

  describe '#has_no_commits?' do
    before do
1698 1699
      allow(subject.merge_request_diff).to receive(:commits_count)
        .and_return(0)
1700 1701 1702 1703 1704 1705
    end

    it 'returns true when merge request diff has 0 commits' do
      expect(subject.has_no_commits?).to be_truthy
    end
  end
1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724

  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
  end
1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756

  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
1757

1758 1759 1760
  describe '#fetch_ref!' do
    it 'fetch the ref correctly' do
      expect { subject.target_project.repository.delete_refs(subject.ref_path) }.not_to raise_error
1761

1762 1763
      subject.fetch_ref!
      expect(subject.target_project.repository.ref_exists?(subject.ref_path)).to be_truthy
1764 1765
    end
  end
1766 1767 1768 1769 1770 1771 1772 1773 1774

  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
1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786

  describe '#update_project_counter_caches?' do
    it 'returns true when the state changes' do
      subject.state = 'closed'

      expect(subject.update_project_counter_caches?).to eq(true)
    end

    it 'returns false when the state did not change' do
      expect(subject.update_project_counter_caches?).to eq(false)
    end
  end
D
Dmitriy Zaporozhets 已提交
1787
end