merge_request_spec.rb 13.4 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
  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

99 100 101 102 103 104 105
    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

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

114 115 116 117 118 119 120 121 122
  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
123
  end
124 125

  describe "#mr_and_commit_notes" do
126
    let!(:merge_request) { create(:merge_request) }
127 128

    before do
129
      allow(merge_request).to receive(:commits) { [merge_request.source_project.repository.commit] }
D
Dmitriy Zaporozhets 已提交
130 131
      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)
132 133 134
    end

    it "should include notes for commits" do
135 136
      expect(merge_request.commits).not_to be_empty
      expect(merge_request.mr_and_commit_notes.count).to eq(2)
137
    end
138 139 140 141 142 143

    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
144
  end
145 146 147

  describe '#is_being_reassigned?' do
    it 'returns true if the merge_request assignee has changed' do
148
      subject.assignee = create(:user)
149
      expect(subject.is_being_reassigned?).to be_truthy
150 151
    end
    it 'returns false if the merge request assignee has not changed' do
152
      expect(subject.is_being_reassigned?).to be_falsey
153 154
    end
  end
I
Izaak Alpert 已提交
155 156 157

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

161
      expect(subject.for_fork?).to be_truthy
I
Izaak Alpert 已提交
162
    end
D
Dmitriy Zaporozhets 已提交
163

I
Izaak Alpert 已提交
164
    it 'returns false if is not for a fork' do
165
      expect(subject.for_fork?).to be_falsey
I
Izaak Alpert 已提交
166 167 168
    end
  end

169 170 171
  describe 'detection of issues to be closed' do
    let(:issue0) { create :issue, project: subject.project }
    let(:issue1) { create :issue, project: subject.project }
172 173 174 175

    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}") }
176 177

    before do
178
      subject.project.team << [subject.author, :developer]
179
      allow(subject).to receive(:commits).and_return([commit0, commit1, commit2])
180 181 182
    end

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

186 187 188
      closed = subject.closes_issues

      expect(closed).to include(issue0, issue1)
189 190 191
    end

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

195
      expect(subject.closes_issues).to be_empty
196
    end
197 198 199

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

204
      expect(subject.closes_issues).to include(issue2)
205
    end
206 207
  end

208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
  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 已提交
224 225 226 227 228
    it "detects the '[WIP]' prefix" do
      subject.title = "[WIP]#{subject.title}"
      expect(subject).to be_work_in_progress
    end

229 230 231 232 233 234 235 236 237 238
    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 已提交
239
  describe '#can_remove_source_branch?' do
Z
Zeger-Jan van de Weg 已提交
240 241
    let(:user) { create(:user) }
    let(:user2) { create(:user) }
242 243 244 245

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

Z
Zeger-Jan van de Weg 已提交
246 247 248 249
      subject.source_branch = "feature"
      subject.target_branch = "master"
      subject.save!
    end
250

Z
Zeger-Jan van de Weg 已提交
251 252
    it "can't be removed when its a protected branch" do
      allow(subject.source_project).to receive(:protected_branch?).and_return(true)
253 254 255 256
      expect(subject.can_remove_source_branch?(user)).to be_falsey
    end

    it "cant remove a root ref" do
Z
Zeger-Jan van de Weg 已提交
257 258
      subject.source_branch = "master"
      subject.target_branch = "feature"
259 260 261 262

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

Z
Zeger-Jan van de Weg 已提交
263 264 265 266
    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

267 268 269
    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 已提交
270
      expect(subject.can_remove_source_branch?(user)).to be_truthy
271
    end
272 273 274 275

    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
276 277
  end

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

281 282 283 284 285 286 287 288
    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

289
  describe "#hook_attrs" do
290 291 292 293 294 295 296 297 298 299 300
    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

301
    it "has all the required keys" do
302 303 304 305
      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)
306
    end
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 363 364 365 366 367 368 369 370
  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
371 372
  end

373
  it_behaves_like 'an editable mentionable' do
374
    subject { create(:merge_request) }
375

376 377
    let(:backref_text) { "merge request #{subject.to_reference}" }
    let(:set_mentionable_text) { ->(txt){ subject.description = txt } }
378
  end
V
Vinnie Okada 已提交
379 380

  it_behaves_like 'a Taskable' do
381
    subject { create :merge_request, :simple }
V
Vinnie Okada 已提交
382
  end
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407

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