merge_request_spec.rb 14.1 KB
Newer Older
D
Dmitriy Zaporozhets 已提交
1 2 3 4
# == Schema Information
#
# Table name: merge_requests
#
S
Stan Hu 已提交
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
#  id                        :integer          not null, primary key
#  target_branch             :string(255)      not null
#  source_branch             :string(255)      not null
#  source_project_id         :integer          not null
#  author_id                 :integer
#  assignee_id               :integer
#  title                     :string(255)
#  created_at                :datetime
#  updated_at                :datetime
#  milestone_id              :integer
#  state                     :string(255)
#  merge_status              :string(255)
#  target_project_id         :integer          not null
#  iid                       :integer
#  description               :text
#  position                  :integer          default(0)
#  locked_at                 :datetime
#  updated_by_id             :integer
#  merge_error               :string(255)
#  merge_params              :text
#  merge_when_build_succeeds :boolean          default(FALSE), not null
#  merge_user_id             :integer
27
#  merge_commit_sha          :string
D
Dmitriy Zaporozhets 已提交
28 29
#

D
Dmitriy Zaporozhets 已提交
30 31
require 'spec_helper'

D
Douwe Maan 已提交
32
describe MergeRequest, models: true do
33 34
  subject { create(:merge_request) }

R
Robert Speicher 已提交
35 36 37
  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') }
38
    it { is_expected.to belong_to(:merge_user).class_name("User") }
R
Robert Speicher 已提交
39 40 41
    it { is_expected.to have_one(:merge_request_diff).dependent(:destroy) }
  end

42 43 44 45 46 47 48 49 50 51
  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 已提交
52 53 54 55 56
  describe "act_as_paranoid" do
    it { is_expected.to have_db_column(:deleted_at) }
    it { is_expected.to have_db_index(:deleted_at) }
  end

57
  describe 'validation' do
58 59
    it { is_expected.to validate_presence_of(:target_branch) }
    it { is_expected.to validate_presence_of(:source_branch) }
Z
Zeger-Jan van de Weg 已提交
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77

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

R
Robert Speicher 已提交
80
  describe 'respond to' do
81 82 83
    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?) }
84 85
    it { is_expected.to respond_to(:merge_params) }
    it { is_expected.to respond_to(:merge_when_build_succeeds) }
86
  end
A
Andrey Kumanyaev 已提交
87

88 89 90 91 92 93
  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

94 95 96 97 98 99 100 101 102 103
  describe '#target_sha' do
    context 'when the target branch does not exist anymore' do
      subject { create(:merge_request).tap { |mr| mr.update_attribute(:target_branch, 'deleted') } }

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

104 105 106 107 108 109 110 111 112 113
  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

114 115 116 117 118 119 120
    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

121 122 123 124 125 126 127 128
    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

129 130 131 132 133 134 135 136 137
  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
138
  end
139 140

  describe "#mr_and_commit_notes" do
141
    let!(:merge_request) { create(:merge_request) }
142 143

    before do
144
      allow(merge_request).to receive(:commits) { [merge_request.source_project.repository.commit] }
D
Dmitriy Zaporozhets 已提交
145 146
      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)
147 148 149
    end

    it "should include notes for commits" do
150 151
      expect(merge_request.commits).not_to be_empty
      expect(merge_request.mr_and_commit_notes.count).to eq(2)
152
    end
153 154 155 156 157 158

    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
159
  end
160 161 162

  describe '#is_being_reassigned?' do
    it 'returns true if the merge_request assignee has changed' do
163
      subject.assignee = create(:user)
164
      expect(subject.is_being_reassigned?).to be_truthy
165 166
    end
    it 'returns false if the merge request assignee has not changed' do
167
      expect(subject.is_being_reassigned?).to be_falsey
168 169
    end
  end
I
Izaak Alpert 已提交
170 171 172

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

176
      expect(subject.for_fork?).to be_truthy
I
Izaak Alpert 已提交
177
    end
D
Dmitriy Zaporozhets 已提交
178

I
Izaak Alpert 已提交
179
    it 'returns false if is not for a fork' do
180
      expect(subject.for_fork?).to be_falsey
I
Izaak Alpert 已提交
181 182 183
    end
  end

184 185 186
  describe 'detection of issues to be closed' do
    let(:issue0) { create :issue, project: subject.project }
    let(:issue1) { create :issue, project: subject.project }
187 188 189 190

    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}") }
191 192

    before do
193
      subject.project.team << [subject.author, :developer]
194
      allow(subject).to receive(:commits).and_return([commit0, commit1, commit2])
195 196 197
    end

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

201 202 203
      closed = subject.closes_issues

      expect(closed).to include(issue0, issue1)
204 205 206
    end

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

210
      expect(subject.closes_issues).to be_empty
211
    end
212 213 214

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

219
      expect(subject.closes_issues).to include(issue2)
220
    end
221 222
  end

223
  describe "#work_in_progress?" do
224 225 226
    ['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}"
227
        expect(subject.work_in_progress?).to eq true
228
      end
T
Ted Hogan 已提交
229 230
    end

231 232
    it "doesn't detect WIP for words starting with WIP" do
      subject.title = "Wipwap #{subject.title}"
233
      expect(subject.work_in_progress?).to eq false
234 235
    end

236 237
    it "doesn't detect WIP for words containing with WIP" do
      subject.title = "WupWipwap #{subject.title}"
238
      expect(subject.work_in_progress?).to eq false
239 240
    end

241
    it "doesn't detect WIP by default" do
242
      expect(subject.work_in_progress?).to eq false
243 244 245
    end
  end

Z
Zeger-Jan van de Weg 已提交
246
  describe '#can_remove_source_branch?' do
Z
Zeger-Jan van de Weg 已提交
247 248
    let(:user) { create(:user) }
    let(:user2) { create(:user) }
249 250 251 252

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

Z
Zeger-Jan van de Weg 已提交
253 254 255 256
      subject.source_branch = "feature"
      subject.target_branch = "master"
      subject.save!
    end
257

Z
Zeger-Jan van de Weg 已提交
258 259
    it "can't be removed when its a protected branch" do
      allow(subject.source_project).to receive(:protected_branch?).and_return(true)
260 261 262 263
      expect(subject.can_remove_source_branch?(user)).to be_falsey
    end

    it "cant remove a root ref" do
Z
Zeger-Jan van de Weg 已提交
264 265
      subject.source_branch = "master"
      subject.target_branch = "feature"
266 267 268 269

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

Z
Zeger-Jan van de Weg 已提交
270 271 272 273
    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

274 275 276
    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 已提交
277
      expect(subject.can_remove_source_branch?(user)).to be_truthy
278
    end
279 280 281 282

    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
283 284
  end

285
  describe "#reset_merge_when_build_succeeds" do
Z
Zeger-Jan van de Weg 已提交
286 287
    let(:merge_if_green) { create :merge_request, merge_when_build_succeeds: true, merge_user: create(:user) }

288 289 290 291 292 293 294 295
    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
    end
  end

296
  describe "#hook_attrs" do
297 298 299 300 301 302 303 304 305 306 307
    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

308
    it "has all the required keys" do
309 310 311 312
      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)
313
    end
314 315 316 317 318 319
  end

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

320 321 322 323 324 325 326 327 328 329 330 331
    context 'when the target branch does not exist anymore' do
      subject { create(:merge_request).tap { |mr| mr.update_attribute(:target_branch, 'deleted') } }

      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

332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389
    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
390 391
  end

392
  it_behaves_like 'an editable mentionable' do
393
    subject { create(:merge_request) }
394

395 396
    let(:backref_text) { "merge request #{subject.to_reference}" }
    let(:set_mentionable_text) { ->(txt){ subject.description = txt } }
397
  end
V
Vinnie Okada 已提交
398 399

  it_behaves_like 'a Taskable' do
400
    subject { create :merge_request, :simple }
V
Vinnie Okada 已提交
401
  end
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426

  describe '#ci_commit' do
    describe 'when the source project exists' do
      it 'returns the latest commit' do
        commit    = double(:commit, id: '123abc')
        ci_commit = double(:ci_commit)

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

        expect(subject.source_project).to receive(:ci_commit).
          with('123abc').
          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 已提交
427
end