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

D
Douwe Maan 已提交
3
describe MergeRequest, models: true do
4 5
  subject { create(:merge_request) }

R
Robert Speicher 已提交
6 7 8
  describe 'associations' do
    it { is_expected.to belong_to(:target_project).with_foreign_key(:target_project_id).class_name('Project') }
    it { is_expected.to belong_to(:source_project).with_foreign_key(:source_project_id).class_name('Project') }
9
    it { is_expected.to belong_to(:merge_user).class_name("User") }
R
Robert Speicher 已提交
10 11 12
    it { is_expected.to have_one(:merge_request_diff).dependent(:destroy) }
  end

13 14 15 16 17 18 19 20 21 22
  describe 'modules' do
    subject { described_class }

    it { is_expected.to include_module(InternalId) }
    it { is_expected.to include_module(Issuable) }
    it { is_expected.to include_module(Referable) }
    it { is_expected.to include_module(Sortable) }
    it { is_expected.to include_module(Taskable) }
  end

Z
Zeger-Jan van de Weg 已提交
23 24 25 26 27
  describe "act_as_paranoid" do
    it { is_expected.to have_db_column(:deleted_at) }
    it { is_expected.to have_db_index(:deleted_at) }
  end

28
  describe 'validation' do
29 30
    it { is_expected.to validate_presence_of(:target_branch) }
    it { is_expected.to validate_presence_of(:source_branch) }
Z
Zeger-Jan van de Weg 已提交
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48

    context "Validation of merge user with Merge When Build succeeds" do
      it "allows user to be nil when the feature is disabled" do
        expect(subject).to be_valid
      end

      it "is invalid without merge user" do
        subject.merge_when_build_succeeds = true
        expect(subject).not_to be_valid
      end

      it "is valid with merge user" do
        subject.merge_when_build_succeeds = true
        subject.merge_user = build(:user)

        expect(subject).to be_valid
      end
    end
D
Dmitriy Zaporozhets 已提交
49 50
  end

R
Robert Speicher 已提交
51
  describe 'respond to' do
52 53 54
    it { is_expected.to respond_to(:unchecked?) }
    it { is_expected.to respond_to(:can_be_merged?) }
    it { is_expected.to respond_to(:cannot_be_merged?) }
55 56
    it { is_expected.to respond_to(:merge_params) }
    it { is_expected.to respond_to(:merge_when_build_succeeds) }
57
  end
A
Andrey Kumanyaev 已提交
58

59 60 61 62 63 64
  describe '.in_projects' do
    it 'returns the merge requests for a set of projects' do
      expect(described_class.in_projects(Project.all)).to eq([subject])
    end
  end

65 66
  describe '#target_sha' do
    context 'when the target branch does not exist anymore' do
67 68 69 70 71 72 73
      let(:project) { create(:project) }

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

      before do
        project.repository.raw_repository.delete_branch(subject.target_branch)
      end
74 75 76 77 78 79 80

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

81 82 83 84 85 86 87 88 89 90
  describe '#source_sha' do
    let(:last_branch_commit) { subject.source_project.repository.commit(subject.source_branch) }

    context 'with diffs' do
      subject { create(:merge_request, :with_diffs) }
      it 'returns the sha of the source branch last commit' do
        expect(subject.source_sha).to eq(last_branch_commit.sha)
      end
    end

91 92 93 94 95 96 97
    context 'without diffs' do
      subject { create(:merge_request, :without_diffs) }
      it 'returns the sha of the source branch last commit' do
        expect(subject.source_sha).to eq(last_branch_commit.sha)
      end
    end

98 99 100 101 102 103 104 105
    context 'when the merge request is being created' do
      subject { build(:merge_request, source_branch: nil, compare_commits: []) }
      it 'returns nil' do
        expect(subject.source_sha).to be_nil
      end
    end
  end

106 107 108 109 110 111 112 113 114
  describe '#to_reference' do
    it 'returns a String reference to the object' do
      expect(subject.to_reference).to eq "!#{subject.iid}"
    end

    it 'supports a cross-project reference' do
      cross = double('project')
      expect(subject.to_reference(cross)).to eq "#{subject.source_project.to_reference}!#{subject.iid}"
    end
115
  end
116 117

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

    before do
121
      allow(merge_request).to receive(:commits) { [merge_request.source_project.repository.commit] }
D
Dmitriy Zaporozhets 已提交
122 123
      create(:note, commit_id: merge_request.commits.first.id, noteable_type: 'Commit', project: merge_request.project)
      create(:note, noteable: merge_request, project: merge_request.project)
124 125 126
    end

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

    it "should include notes for commits from target project as well" do
      create(:note, commit_id: merge_request.commits.first.id, noteable_type: 'Commit', project: merge_request.target_project)
      expect(merge_request.commits).not_to be_empty
      expect(merge_request.mr_and_commit_notes.count).to eq(3)
    end
136
  end
137 138 139

  describe '#is_being_reassigned?' do
    it 'returns true if the merge_request assignee has changed' do
140
      subject.assignee = create(:user)
141
      expect(subject.is_being_reassigned?).to be_truthy
142 143
    end
    it 'returns false if the merge request assignee has not changed' do
144
      expect(subject.is_being_reassigned?).to be_falsey
145 146
    end
  end
I
Izaak Alpert 已提交
147 148 149

  describe '#for_fork?' do
    it 'returns true if the merge request is for a fork' do
D
Dmitriy Zaporozhets 已提交
150 151
      subject.source_project = create(:project, namespace: create(:group))
      subject.target_project = create(:project, namespace: create(:group))
I
Izaak Alpert 已提交
152

153
      expect(subject.for_fork?).to be_truthy
I
Izaak Alpert 已提交
154
    end
D
Dmitriy Zaporozhets 已提交
155

I
Izaak Alpert 已提交
156
    it 'returns false if is not for a fork' do
157
      expect(subject.for_fork?).to be_falsey
I
Izaak Alpert 已提交
158 159 160
    end
  end

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

    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}") }
168 169

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

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

178 179 180
      closed = subject.closes_issues

      expect(closed).to include(issue0, issue1)
181 182 183
    end

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

187
      expect(subject.closes_issues).to be_empty
188
    end
189 190 191

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

196
      expect(subject.closes_issues).to include(issue2)
197
    end
198 199
  end

200
  describe "#work_in_progress?" do
201 202 203
    ['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}"
204
        expect(subject.work_in_progress?).to eq true
205
      end
T
Ted Hogan 已提交
206 207
    end

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

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

218
    it "doesn't detect WIP by default" do
219
      expect(subject.work_in_progress?).to eq false
220 221 222
    end
  end

Z
Zeger-Jan van de Weg 已提交
223
  describe '#can_remove_source_branch?' do
Z
Zeger-Jan van de Weg 已提交
224 225
    let(:user) { create(:user) }
    let(:user2) { create(:user) }
226 227 228 229

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

Z
Zeger-Jan van de Weg 已提交
230 231 232 233
      subject.source_branch = "feature"
      subject.target_branch = "master"
      subject.save!
    end
234

Z
Zeger-Jan van de Weg 已提交
235 236
    it "can't be removed when its a protected branch" do
      allow(subject.source_project).to receive(:protected_branch?).and_return(true)
237 238 239 240
      expect(subject.can_remove_source_branch?(user)).to be_falsey
    end

    it "cant remove a root ref" do
Z
Zeger-Jan van de Weg 已提交
241 242
      subject.source_branch = "master"
      subject.target_branch = "feature"
243 244 245 246

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

Z
Zeger-Jan van de Weg 已提交
247 248 249 250
    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

251 252 253
    it "can be removed if the last commit is the head of the source branch" do
      allow(subject.source_project).to receive(:commit).and_return(subject.last_commit)

Z
Zeger-Jan van de Weg 已提交
254
      expect(subject.can_remove_source_branch?(user)).to be_truthy
255
    end
256 257 258 259

    it "cannot be removed if the last commit is not also the head of the source branch" do
      expect(subject.can_remove_source_branch?(user)).to be_falsey
    end
260 261
  end

262
  describe "#reset_merge_when_build_succeeds" do
263 264 265 266
    let(:merge_if_green) do
      create :merge_request, merge_when_build_succeeds: true, merge_user: create(:user),
                             merge_params: { "should_remove_source_branch" => "1", "commit_message" => "msg" }
    end
Z
Zeger-Jan van de Weg 已提交
267

268 269 270 271 272
    it "sets the item to false" do
      merge_if_green.reset_merge_when_build_succeeds
      merge_if_green.reload

      expect(merge_if_green.merge_when_build_succeeds).to be_falsey
273 274
      expect(merge_if_green.merge_params["should_remove_source_branch"]).to be_nil
      expect(merge_if_green.merge_params["commit_message"]).to be_nil
275 276 277
    end
  end

278
  describe "#hook_attrs" do
279 280 281 282 283 284 285 286 287 288 289
    let(:attrs_hash) { subject.hook_attrs.to_h }

    [:source, :target].each do |key|
      describe "#{key} key" do
        include_examples 'project hook data', project_key: key do
          let(:data)    { attrs_hash }
          let(:project) { subject.send("#{key}_project") }
        end
      end
    end

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

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

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

      before do
        project.repository.raw_repository.delete_branch(subject.target_branch)
        subject.reload
      end
309 310 311 312 313 314 315 316 317 318

      it 'does not crash' do
        expect{ subject.diverged_commits_count }.not_to raise_error
      end

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

319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376
    context 'diverged on same repository' do
      subject(:merge_request_with_divergence) { create(:merge_request, :diverged, source_project: project, target_project: project) }

      it 'counts commits that are on target branch but not on source branch' do
        expect(subject.diverged_commits_count).to eq(5)
      end
    end

    context 'diverged on fork' do
      subject(:merge_request_fork_with_divergence) { create(:merge_request, :diverged, source_project: fork_project, target_project: project) }

      it 'counts commits that are on target branch but not on source branch' do
        expect(subject.diverged_commits_count).to eq(5)
      end
    end

    context 'rebased on fork' do
      subject(:merge_request_rebased) { create(:merge_request, :rebased, source_project: fork_project, target_project: project) }

      it 'counts commits that are on target branch but not on source branch' do
        expect(subject.diverged_commits_count).to eq(0)
      end
    end

    describe 'caching' do
      before(:example) do
        allow(Rails).to receive(:cache).and_return(ActiveSupport::Cache::MemoryStore.new)
      end

      it 'caches the output' do
        expect(subject).to receive(:compute_diverged_commits_count).
          once.
          and_return(2)

        subject.diverged_commits_count
        subject.diverged_commits_count
      end

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

        subject.diverged_commits_count
        allow(subject).to receive(:source_sha).and_return('123abc')
        subject.diverged_commits_count
      end

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

        subject.diverged_commits_count
        allow(subject).to receive(:target_sha).and_return('123abc')
        subject.diverged_commits_count
      end
    end
377 378
  end

379
  it_behaves_like 'an editable mentionable' do
380
    subject { create(:merge_request) }
381

382 383
    let(:backref_text) { "merge request #{subject.to_reference}" }
    let(:set_mentionable_text) { ->(txt){ subject.description = txt } }
384
  end
V
Vinnie Okada 已提交
385 386

  it_behaves_like 'a Taskable' do
387
    subject { create :merge_request, :simple }
V
Vinnie Okada 已提交
388
  end
389 390 391 392 393

  describe '#ci_commit' do
    describe 'when the source project exists' do
      it 'returns the latest commit' do
        commit    = double(:commit, id: '123abc')
K
Kamil Trzcinski 已提交
394
        ci_commit = double(:ci_commit, ref: 'master')
395 396 397 398

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

        expect(subject.source_project).to receive(:ci_commit).
K
Kamil Trzcinski 已提交
399
          with('123abc', 'master').
400 401 402 403 404 405 406 407 408 409 410 411 412 413
          and_return(ci_commit)

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

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

        expect(subject.ci_commit).to be_nil
      end
    end
  end
D
Dmitriy Zaporozhets 已提交
414
end