merge_request_spec.rb 13.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 52
  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

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

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

R
Robert Speicher 已提交
75
  describe 'respond to' do
76 77 78
    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?) }
79 80
    it { is_expected.to respond_to(:merge_params) }
    it { is_expected.to respond_to(:merge_when_build_succeeds) }
81
  end
A
Andrey Kumanyaev 已提交
82

83 84 85 86 87 88
  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

89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
  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

    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

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

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

    before do
122
      allow(merge_request).to receive(:commits) { [merge_request.source_project.repository.commit] }
D
Dmitriy Zaporozhets 已提交
123 124
      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)
125 126 127
    end

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

    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
137
  end
138 139 140

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

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

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

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

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

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

    before do
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 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
  describe "#work_in_progress?" do
    it "detects the 'WIP ' prefix" do
      subject.title = "WIP #{subject.title}"
      expect(subject).to be_work_in_progress
    end

    it "detects the 'WIP: ' prefix" do
      subject.title = "WIP: #{subject.title}"
      expect(subject).to be_work_in_progress
    end

    it "detects the '[WIP] ' prefix" do
      subject.title = "[WIP] #{subject.title}"
      expect(subject).to be_work_in_progress
    end

T
Ted Hogan 已提交
216 217 218 219 220
    it "detects the '[WIP]' prefix" do
      subject.title = "[WIP]#{subject.title}"
      expect(subject).to be_work_in_progress
    end

221 222 223 224 225 226 227 228 229 230
    it "doesn't detect WIP for words starting with WIP" do
      subject.title = "Wipwap #{subject.title}"
      expect(subject).not_to be_work_in_progress
    end

    it "doesn't detect WIP by default" do
      expect(subject).not_to be_work_in_progress
    end
  end

Z
Zeger-Jan van de Weg 已提交
231
  describe '#can_remove_source_branch?' do
Z
Zeger-Jan van de Weg 已提交
232 233
    let(:user) { create(:user) }
    let(:user2) { create(:user) }
234 235 236 237

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

Z
Zeger-Jan van de Weg 已提交
238 239 240 241
      subject.source_branch = "feature"
      subject.target_branch = "master"
      subject.save!
    end
242

Z
Zeger-Jan van de Weg 已提交
243 244
    it "can't be removed when its a protected branch" do
      allow(subject.source_project).to receive(:protected_branch?).and_return(true)
245 246 247 248
      expect(subject.can_remove_source_branch?(user)).to be_falsey
    end

    it "cant remove a root ref" do
Z
Zeger-Jan van de Weg 已提交
249 250
      subject.source_branch = "master"
      subject.target_branch = "feature"
251 252 253 254

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

Z
Zeger-Jan van de Weg 已提交
255 256 257 258
    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

259 260 261
    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 已提交
262
      expect(subject.can_remove_source_branch?(user)).to be_truthy
263
    end
264 265 266 267

    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
268 269
  end

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

273 274 275 276 277 278 279 280
    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

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

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

293
    it "has all the required keys" do
294 295 296 297
      expect(attrs_hash).to include(:source)
      expect(attrs_hash).to include(:target)
      expect(attrs_hash).to include(:last_commit)
      expect(attrs_hash).to include(:work_in_progress)
298
    end
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 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
  end

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

    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
363 364
  end

365
  it_behaves_like 'an editable mentionable' do
366
    subject { create(:merge_request) }
367

368 369
    let(:backref_text) { "merge request #{subject.to_reference}" }
    let(:set_mentionable_text) { ->(txt){ subject.description = txt } }
370
  end
V
Vinnie Okada 已提交
371 372

  it_behaves_like 'a Taskable' do
373
    subject { create :merge_request, :simple }
V
Vinnie Okada 已提交
374
  end
375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399

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