merge_request_spec.rb 56.9 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 89 90
      before do
        project.repository.raw_repository.delete_branch(subject.target_branch)
      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
    let(:attrs_hash) { subject.hook_attrs }
665 666 667 668 669 670 671 672 673 674

    [: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

675
    it "has all the required keys" do
676 677 678 679
      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)
680 681 682 683
      expect(attrs_hash).to include(:total_time_spent)
      expect(attrs_hash).to include(:human_time_estimate)
      expect(attrs_hash).to include(:human_total_time_spent)
      expect(attrs_hash).to include('time_estimate')
684
    end
685 686 687
  end

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

691
    context 'when the target branch does not exist anymore' do
692 693 694 695 696 697
      subject { create(:merge_request, source_project: project, target_project: project) }

      before do
        project.repository.raw_repository.delete_branch(subject.target_branch)
        subject.reload
      end
698 699

      it 'does not crash' do
700
        expect { subject.diverged_commits_count }.not_to raise_error
701 702 703 704 705 706 707
      end

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

708 709 710 711
    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
712
        expect(subject.diverged_commits_count).to eq(29)
713 714 715 716
      end
    end

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

      it 'counts commits that are on target branch but not on source branch' do
720
        expect(subject.diverged_commits_count).to eq(29)
721 722 723 724
      end
    end

    context 'rebased on fork' do
B
Bob Van Landuyt 已提交
725
      subject(:merge_request_rebased) { create(:merge_request, :rebased, source_project: forked_project, target_project: project) }
726 727 728 729 730 731 732

      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
733
      before do
734 735 736 737
        allow(Rails).to receive(:cache).and_return(ActiveSupport::Cache::MemoryStore.new)
      end

      it 'caches the output' do
738 739 740
        expect(subject).to receive(:compute_diverged_commits_count)
          .once
          .and_return(2)
741 742 743 744 745 746

        subject.diverged_commits_count
        subject.diverged_commits_count
      end

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

        subject.diverged_commits_count
752
        allow(subject).to receive(:source_branch_sha).and_return('123abc')
753 754 755 756
        subject.diverged_commits_count
      end

      it 'invalidates the cache when the target sha changes' do
757 758 759
        expect(subject).to receive(:compute_diverged_commits_count)
          .twice
          .and_return(2)
760 761

        subject.diverged_commits_count
762
        allow(subject).to receive(:target_branch_sha).and_return('123abc')
763 764 765
        subject.diverged_commits_count
      end
    end
766 767
  end

768
  it_behaves_like 'an editable mentionable' do
769
    subject { create(:merge_request, :simple) }
770

771
    let(:backref_text) { "merge request #{subject.to_reference}" }
772
    let(:set_mentionable_text) { ->(txt) { subject.description = txt } }
773
  end
V
Vinnie Okada 已提交
774 775

  it_behaves_like 'a Taskable' do
776
    subject { create :merge_request, :simple }
V
Vinnie Okada 已提交
777
  end
778

779
  describe '#commit_shas' do
780
    before do
781
      allow(subject.merge_request_diff).to receive(:commit_shas)
782
        .and_return(['sha1'])
783 784
    end

785
    it 'delegates to merge request diff' do
786
      expect(subject.commit_shas).to eq ['sha1']
787 788 789
    end
  end

790
  describe '#head_pipeline' do
791
    describe 'when the source project exists' do
792
      it 'returns the latest pipeline' do
793
        pipeline = create(:ci_empty_pipeline, project: subject.source_project, ref: 'master', status: 'running', sha: "123abc", head_pipeline_of: subject)
794

795
        expect(subject.head_pipeline).to eq(pipeline)
796 797 798 799 800 801 802
      end
    end

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

803
        expect(subject.head_pipeline).to be_nil
804 805 806
      end
    end
  end
Y
Yorick Peterse 已提交
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 839 840 841 842 843 844 845 846 847 848 849 850
  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

851
  describe '#all_pipelines' do
852
    shared_examples 'returning pipelines with proper ordering' do
853
      let!(:all_pipelines) do
854
        subject.all_commit_shas.map do |sha|
855 856 857 858
          create(:ci_empty_pipeline,
                 project: subject.source_project,
                 sha: sha,
                 ref: subject.source_branch)
859 860 861 862 863
        end
      end

      it 'returns all pipelines' do
        expect(subject.all_pipelines).not_to be_empty
864
        expect(subject.all_pipelines).to eq(all_pipelines.reverse)
865
      end
866 867
    end

868 869 870 871 872 873
    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
874
        subject.update(target_branch: 'v1.0.0')
875 876 877
      end

      it_behaves_like 'returning pipelines with proper ordering'
878
    end
879 880

    context 'with unsaved merge request' do
881
      subject { build(:merge_request) }
882 883 884

      let!(:pipeline) do
        create(:ci_empty_pipeline,
885
               project: subject.project,
886 887
               sha: subject.diff_head_sha,
               ref: subject.source_branch)
888
      end
889 890 891 892 893

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

896
  describe '#all_commit_shas' do
897
    context 'when merge request is persisted' do
898
      let(:all_commit_shas) do
899 900
        subject.merge_request_diffs.flat_map(&:commits).map(&:sha).uniq
      end
901

902 903 904
      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)
905
          expect(subject.all_commit_shas).to match_array(all_commit_shas)
906
        end
907 908
      end

909 910 911 912 913 914
      context 'with a completely different branch' do
        before do
          subject.update(target_branch: 'v1.0.0')
        end

        it_behaves_like 'returning all SHA'
915 916
      end

917 918 919 920 921 922 923 924
      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
925 926
    end

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

940 941 942 943
      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
944
          expect(subject.all_commit_shas).to eq [subject.diff_head_sha]
945 946
        end
      end
947 948 949
    end
  end

Y
Yorick Peterse 已提交
950
  describe '#participants' do
951
    let(:project) { create(:project, :public) }
Y
Yorick Peterse 已提交
952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972

    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 已提交
973 974 975 976 977 978

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

982 983
      expect(user1.assigned_open_merge_requests_count).to eq(1)
      expect(user2.assigned_open_merge_requests_count).to eq(0)
J
Josh Frye 已提交
984 985 986 987

      mr.assignee = user2
      mr.save

988 989
      expect(user1.assigned_open_merge_requests_count).to eq(0)
      expect(user2.assigned_open_merge_requests_count).to eq(1)
J
Josh Frye 已提交
990 991
    end
  end
992

993
  describe '#merge_async' do
994 995 996 997 998 999 1000 1001 1002 1003
    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

1004
      merge_request.merge_async(user_id, params)
1005 1006 1007 1008 1009

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

1010
  describe '#check_if_can_be_merged' do
1011
    let(:project) { create(:project, only_allow_merge_if_pipeline_succeeds: true) }
1012 1013 1014 1015

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

    context 'when it is not broken and has no conflicts' do
1016
      before do
1017
        allow(subject).to receive(:broken?) { false }
1018
        allow(project.repository).to receive(:can_be_merged?).and_return(true)
1019
      end
1020

1021
      it 'is marked as mergeable' do
1022 1023 1024 1025 1026
        expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('can_be_merged')
      end
    end

    context 'when broken' do
1027 1028 1029
      before do
        allow(subject).to receive(:broken?) { true }
      end
1030 1031 1032 1033 1034 1035 1036 1037 1038

      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 }
1039
        allow(project.repository).to receive(:can_be_merged?).and_return(false)
1040 1041 1042 1043 1044 1045 1046 1047 1048
      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
1049
    let(:project) { create(:project) }
1050 1051 1052

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

1053 1054
    it 'returns false if #mergeable_state? is false' do
      expect(subject).to receive(:mergeable_state?) { false }
1055

1056
      expect(subject.mergeable?).to be_falsey
1057 1058
    end

1059
    it 'return true if #mergeable_state? is true and the MR #can_be_merged? is true' do
1060 1061
      allow(subject).to receive(:mergeable_state?) { true }
      expect(subject).to receive(:check_if_can_be_merged)
1062
      expect(subject).to receive(:can_be_merged?) { true }
1063 1064 1065 1066 1067 1068

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

  describe '#mergeable_state?' do
1069
    let(:project) { create(:project, :repository) }
1070 1071 1072

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

1073
    it 'checks if merge request can be merged' do
1074
      allow(subject).to receive(:mergeable_ci_state?) { true }
1075 1076 1077 1078 1079 1080
      expect(subject).to receive(:check_if_can_be_merged)

      subject.mergeable?
    end

    context 'when not open' do
1081 1082 1083
      before do
        subject.close
      end
1084 1085

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

    context 'when working in progress' do
1091 1092 1093
      before do
        subject.title = 'WIP MR'
      end
1094 1095

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

    context 'when broken' do
1101 1102 1103
      before do
        allow(subject).to receive(:broken?) { true }
      end
1104 1105

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

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

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

R
Rémy Coutable 已提交
1121
      context 'when #mergeable_discussions_state? is false' do
1122 1123 1124 1125 1126 1127 1128 1129
        before do
          allow(subject).to receive(:mergeable_discussions_state?) { false }
        end

        it 'returns false' do
          expect(subject.mergeable_state?).to be_falsey
        end
      end
1130 1131 1132
    end
  end

1133
  describe '#mergeable_ci_state?' do
1134
    let(:project) { create(:project, only_allow_merge_if_pipeline_succeeds: true) }
R
Rémy Coutable 已提交
1135
    let(:pipeline) { create(:ci_empty_pipeline) }
1136 1137 1138

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

1139
    context 'when it is only allowed to merge when build is green' do
R
Rémy Coutable 已提交
1140
      context 'and a failed pipeline is associated' do
1141
        before do
1142
          pipeline.update(status: 'failed')
1143
          allow(subject).to receive(:head_pipeline) { pipeline }
1144
        end
1145

1146
        it { expect(subject.mergeable_ci_state?).to be_falsey }
1147 1148
      end

1149 1150 1151
      context 'and a successful pipeline is associated' do
        before do
          pipeline.update(status: 'success')
1152
          allow(subject).to receive(:head_pipeline) { pipeline }
1153 1154 1155 1156 1157 1158 1159 1160
        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')
1161
          allow(subject).to receive(:head_pipeline) { pipeline }
1162 1163 1164 1165 1166
        end

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

R
Rémy Coutable 已提交
1167
      context 'when no pipeline is associated' do
1168
        before do
1169
          allow(subject).to receive(:head_pipeline) { nil }
1170 1171 1172
        end

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

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

R
Rémy Coutable 已提交
1179
      context 'and a failed pipeline is associated' do
1180
        before do
R
Rémy Coutable 已提交
1181
          pipeline.statuses << create(:commit_status, status: 'failed', project: project)
1182
          allow(subject).to receive(:head_pipeline) { pipeline }
1183 1184 1185 1186 1187
        end

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

R
Rémy Coutable 已提交
1188
      context 'when no pipeline is associated' do
1189
        before do
1190
          allow(subject).to receive(:head_pipeline) { nil }
1191 1192 1193
        end

        it { expect(subject.mergeable_ci_state?).to be_truthy }
1194 1195 1196
      end
    end
  end
1197

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

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

R
Rémy Coutable 已提交
1204
      context 'with all discussions resolved' do
1205
        before do
R
Rémy Coutable 已提交
1206
          merge_request.discussions.each { |d| d.resolve!(merge_request.author) }
1207 1208 1209
        end

        it 'returns true' do
R
Rémy Coutable 已提交
1210
          expect(merge_request.mergeable_discussions_state?).to be_truthy
1211 1212 1213
        end
      end

R
Rémy Coutable 已提交
1214
      context 'with unresolved discussions' do
1215
        before do
R
Rémy Coutable 已提交
1216
          merge_request.discussions.each(&:unresolve!)
1217 1218 1219
        end

        it 'returns false' do
R
Rémy Coutable 已提交
1220
          expect(merge_request.mergeable_discussions_state?).to be_falsey
1221 1222
        end
      end
1223 1224 1225 1226 1227 1228 1229 1230 1231 1232

      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
1233 1234
    end

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

R
Rémy Coutable 已提交
1238
      context 'with unresolved discussions' do
1239
        before do
R
Rémy Coutable 已提交
1240
          merge_request.discussions.each(&:unresolve!)
1241 1242 1243
        end

        it 'returns true' do
R
Rémy Coutable 已提交
1244
          expect(merge_request.mergeable_discussions_state?).to be_truthy
1245 1246 1247 1248 1249
        end
      end
    end
  end

D
Douwe Maan 已提交
1250
  describe "#environments_for" do
1251
    let(:project)       { create(:project, :repository) }
D
Douwe Maan 已提交
1252
    let(:user)          { project.creator }
Z
Z.J. van de Weg 已提交
1253 1254
    let(:merge_request) { create(:merge_request, source_project: project) }

D
Douwe Maan 已提交
1255 1256 1257 1258 1259
    before do
      merge_request.source_project.add_master(user)
      merge_request.target_project.add_master(user)
    end

1260 1261
    context 'with multiple environments' do
      let(:environments) { create_list(:environment, 3, project: project) }
1262

1263 1264 1265 1266 1267 1268
      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 已提交
1269
        expect(merge_request.environments_for(user)).to contain_exactly(environments.first)
1270 1271 1272 1273
      end
    end

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

1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288
      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 已提交
1289
        expect(merge_request.environments_for(user)).to contain_exactly(source_environment)
1290 1291 1292 1293 1294 1295 1296 1297 1298 1299
      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 已提交
1300
          expect(merge_request.environments_for(user)).to contain_exactly(source_environment, target_environment)
1301 1302
        end
      end
1303 1304 1305 1306 1307 1308 1309 1310
    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 已提交
1311
        expect(merge_request.environments_for(user)).to be_empty
1312
      end
Z
Z.J. van de Weg 已提交
1313 1314 1315
    end
  end

1316
  describe "#reload_diff" do
1317
    let(:discussion) { create(:diff_note_on_merge_request, project: subject.project, noteable: subject).to_discussion }
1318 1319
    let(:commit) { subject.project.commit(sample_commit.id) }

1320
    it "does not change existing merge request diff" do
1321
      expect(subject.merge_request_diff).not_to receive(:save_git_content)
1322 1323 1324
      subject.reload_diff
    end

1325 1326 1327 1328
    it "creates new merge request diff" do
      expect { subject.reload_diff }.to change { subject.merge_request_diffs.count }.by(1)
    end

1329 1330 1331 1332 1333 1334
    it "executs diff cache service" do
      expect_any_instance_of(MergeRequests::MergeRequestDiffCacheService).to receive(:execute).with(subject)

      subject.reload_diff
    end

1335 1336 1337 1338 1339 1340
    it "calls update_diff_discussion_positions" do
      expect(subject).to receive(:update_diff_discussion_positions)

      subject.reload_diff
    end
  end
1341

1342 1343 1344 1345 1346 1347
  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
1348
      # Update merge_request_diff so that #diff_refs will return commit.diff_refs
1349 1350 1351 1352 1353 1354
      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
        )
1355 1356

        subject.merge_request_diff(true)
1357
      end
1358
    end
1359

1360
    it "updates diff discussion positions" do
1361
      expect(Discussions::UpdateDiffPositionService).to receive(:new).with(
1362
        subject.project,
1363
        subject.author,
1364 1365
        old_diff_refs: old_diff_refs,
        new_diff_refs: commit.diff_refs,
1366
        paths: discussion.position.paths
1367 1368
      ).and_call_original

1369
      expect_any_instance_of(Discussions::UpdateDiffPositionService).to receive(:execute).with(discussion).and_call_original
1370 1371
      expect_any_instance_of(DiffNote).to receive(:save).once

1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391
      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
1392 1393
    end
  end
1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407

  describe '#branch_merge_base_commit' do
    context 'source and target branch exist' do
      it { expect(subject.branch_merge_base_commit.sha).to eq('ae73cb07c9eeaf35924a10f713b364d32b2dd34f') }
      it { expect(subject.branch_merge_base_commit).to be_a(Commit) }
    end

    context 'when the target branch does not exist' do
      before do
        subject.project.repository.raw_repository.delete_branch(subject.target_branch)
      end

      it 'returns nil' do
        expect(subject.branch_merge_base_commit).to be_nil
1408 1409 1410 1411
      end
    end
  end

1412
  describe "#diff_refs" do
1413 1414 1415 1416 1417 1418 1419 1420
    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)

1421
        subject.diff_refs
1422 1423 1424 1425 1426 1427 1428 1429 1430
      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
        )

1431
        expect(subject.diff_refs).to eq(expected_diff_refs)
1432 1433 1434
      end
    end
  end
1435

1436
  describe "#source_project_missing?" do
1437
    let(:project)      { create(:project) }
B
Bob Van Landuyt 已提交
1438
    let(:forked_project) { fork_project(project) }
1439
    let(:user)         { create(:user) }
B
Bob Van Landuyt 已提交
1440
    let(:unlink_project) { Projects::UnlinkForkService.new(forked_project, user) }
1441

K
Katarzyna Kobierska 已提交
1442
    context "when the fork exists" do
1443 1444
      let(:merge_request) do
        create(:merge_request,
B
Bob Van Landuyt 已提交
1445
          source_project: forked_project,
1446 1447 1448
          target_project: project)
      end

1449
      it { expect(merge_request.source_project_missing?).to be_falsey }
1450 1451
    end

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

1455
      it { expect(merge_request.source_project_missing?).to be_falsey }
1456 1457
    end

K
Katarzyna Kobierska 已提交
1458
    context "when the fork does not exist" do
1459
      let!(:merge_request) do
1460
        create(:merge_request,
B
Bob Van Landuyt 已提交
1461
          source_project: forked_project,
1462 1463 1464
          target_project: project)
      end

K
Katarzyna Kobierska 已提交
1465
      it "returns true" do
1466 1467 1468
        unlink_project.execute
        merge_request.reload

1469
        expect(merge_request.source_project_missing?).to be_truthy
1470 1471 1472 1473
      end
    end
  end

1474
  describe '#merge_ongoing?' do
1475 1476
    it 'returns true when merge_id is present and MR is not merged' do
      merge_request = build_stubbed(:merge_request, state: :open, merge_jid: 'foo')
1477 1478 1479 1480 1481

      expect(merge_request.merge_ongoing?).to be(true)
    end
  end

1482
  describe "#closed_without_fork?" do
1483
    let(:project)      { create(:project) }
B
Bob Van Landuyt 已提交
1484
    let(:forked_project) { fork_project(project) }
1485
    let(:user)         { create(:user) }
B
Bob Van Landuyt 已提交
1486
    let(:unlink_project) { Projects::UnlinkForkService.new(forked_project, user) }
1487

K
Katarzyna Kobierska 已提交
1488
    context "when the merge request is closed" do
1489 1490
      let(:closed_merge_request) do
        create(:closed_merge_request,
B
Bob Van Landuyt 已提交
1491
          source_project: forked_project,
1492 1493 1494
          target_project: project)
      end

K
Katarzyna Kobierska 已提交
1495
      it "returns false if the fork exist" do
1496 1497 1498
        expect(closed_merge_request.closed_without_fork?).to be_falsey
      end

K
Katarzyna Kobierska 已提交
1499
      it "returns true if the fork does not exist" do
1500 1501 1502 1503 1504 1505
        unlink_project.execute
        closed_merge_request.reload

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

K
Katarzyna Kobierska 已提交
1507
    context "when the merge request is open" do
K
Katarzyna Kobierska 已提交
1508 1509
      let(:open_merge_request) do
        create(:merge_request,
B
Bob Van Landuyt 已提交
1510
          source_project: forked_project,
K
Katarzyna Kobierska 已提交
1511 1512 1513 1514 1515 1516 1517
          target_project: project)
      end

      it "returns false" do
        expect(open_merge_request.closed_without_fork?).to be_falsey
      end
    end
1518
  end
1519

1520
  describe '#reopenable?' do
K
Katarzyna Kobierska 已提交
1521 1522 1523
    context 'when the merge request is closed' do
      it 'returns true' do
        subject.close
K
Katarzyna Kobierska 已提交
1524

1525
        expect(subject.reopenable?).to be_truthy
K
Katarzyna Kobierska 已提交
1526 1527 1528
      end

      context 'forked project' do
B
Bob Van Landuyt 已提交
1529
        let(:project)      { create(:project, :public) }
K
Katarzyna Kobierska 已提交
1530
        let(:user)         { create(:user) }
B
Bob Van Landuyt 已提交
1531
        let(:forked_project) { fork_project(project, user) }
1532 1533

        let!(:merge_request) do
K
Katarzyna Kobierska 已提交
1534
          create(:closed_merge_request,
B
Bob Van Landuyt 已提交
1535
            source_project: forked_project,
K
Katarzyna Kobierska 已提交
1536 1537 1538 1539
            target_project: project)
        end

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

1542
          expect(merge_request.reload.reopenable?).to be_falsey
K
Katarzyna Kobierska 已提交
1543 1544 1545
        end

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

1548
          expect(merge_request.reload.reopenable?).to be_falsey
K
Katarzyna Kobierska 已提交
1549 1550
        end

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

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

K
Katarzyna Kobierska 已提交
1559
    context 'when the merge request is opened' do
K
Katarzyna Kobierska 已提交
1560
      it 'returns false' do
1561
        expect(subject.reopenable?).to be_falsey
K
Katarzyna Kobierska 已提交
1562
      end
K
Katarzyna Kobierska 已提交
1563 1564
    end
  end
1565

1566
  describe '#mergeable_with_quick_action?' do
1567
    def create_pipeline(status)
F
Felipe Artur 已提交
1568
      pipeline = create(:ci_pipeline_with_one_job,
1569 1570 1571
        project: project,
        ref:     merge_request.source_branch,
        sha:     merge_request.diff_head_sha,
1572 1573
        status:  status,
        head_pipeline_of: merge_request)
F
Felipe Artur 已提交
1574 1575

      pipeline
1576 1577
    end

J
James Lopez 已提交
1578
    let(:project)       { create(:project, :public, :repository, only_allow_merge_if_pipeline_succeeds: true) }
1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589
    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
1590
        expect(merge_request.mergeable_with_quick_action?(developer, autocomplete_precheck: true)).to be_truthy
1591 1592 1593
      end

      it 'is not mergeable by normal user' do
1594
        expect(merge_request.mergeable_with_quick_action?(user, autocomplete_precheck: true)).to be_falsey
1595 1596 1597 1598 1599
      end
    end

    context 'when autocomplete_precheck is set to false' do
      it 'is mergeable by developer' do
1600
        expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_truthy
1601 1602 1603
      end

      it 'is not mergeable by normal user' do
1604
        expect(merge_request.mergeable_with_quick_action?(user, last_diff_sha: mr_sha)).to be_falsey
1605 1606 1607 1608 1609 1610 1611 1612
      end

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

        it 'is not mergeable' do
1613
          expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_falsey
1614 1615 1616 1617 1618 1619 1620 1621 1622
        end
      end

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

        it 'is not mergeable' do
1623
          expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_falsey
1624 1625 1626 1627 1628
        end
      end

      context 'sha differs from the MR diff_head_sha'  do
        it 'is not mergeable' do
1629
          expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: 'some other sha')).to be_falsey
1630 1631 1632
        end
      end

J
Jarka Kadlecova 已提交
1633 1634
      context 'sha is not provided'  do
        it 'is not mergeable' do
1635
          expect(merge_request.mergeable_with_quick_action?(developer)).to be_falsey
J
Jarka Kadlecova 已提交
1636 1637 1638
        end
      end

1639 1640 1641 1642 1643 1644
      context 'with pipeline ok'  do
        before do
          create_pipeline(:success)
        end

        it 'is mergeable' do
1645
          expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_truthy
1646 1647 1648 1649 1650 1651 1652 1653 1654
        end
      end

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

        it 'is not mergeable' do
1655
          expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_falsey
1656 1657 1658 1659 1660
        end
      end

      context 'with running pipeline'  do
        before do
F
Felipe Artur 已提交
1661
          create_pipeline(:running)
1662 1663 1664
        end

        it 'is mergeable' do
1665
          expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: mr_sha)).to be_truthy
1666 1667 1668 1669 1670
        end
      end
    end
  end

1671 1672
  describe '#has_commits?' do
    before do
1673 1674
      allow(subject.merge_request_diff).to receive(:commits_count)
        .and_return(2)
1675 1676 1677 1678 1679 1680 1681 1682 1683
    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
1684 1685
      allow(subject.merge_request_diff).to receive(:commits_count)
        .and_return(0)
1686 1687 1688 1689 1690 1691
    end

    it 'returns true when merge request diff has 0 commits' do
      expect(subject.has_no_commits?).to be_truthy
    end
  end
1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710

  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
1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742

  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
1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778

  describe '#fetch_ref' do
    it 'sets "ref_fetched" flag to true' do
      subject.update!(ref_fetched: nil)

      subject.fetch_ref

      expect(subject.reload.ref_fetched).to be_truthy
    end
  end

  describe '#ref_fetched?' do
    it 'does not perform git operation when value is cached' do
      subject.ref_fetched = true

      expect_any_instance_of(Repository).not_to receive(:ref_exists?)
      expect(subject.ref_fetched?).to be_truthy
    end

    it 'caches the value when ref exists but value is not cached' do
      subject.update!(ref_fetched: nil)
      allow_any_instance_of(Repository).to receive(:ref_exists?)
        .and_return(true)

      expect(subject.ref_fetched?).to be_truthy
      expect(subject.reload.ref_fetched).to be_truthy
    end

    it 'returns false when ref does not exist' do
      subject.update!(ref_fetched: nil)
      allow_any_instance_of(Repository).to receive(:ref_exists?)
        .and_return(false)

      expect(subject.ref_fetched?).to be_falsey
    end
  end
1779 1780 1781 1782 1783 1784 1785 1786 1787

  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
1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799

  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 已提交
1800
end