merge_request_spec.rb 12.3 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 89 90 91
  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
92
  end
93 94

  describe "#mr_and_commit_notes" do
95
    let!(:merge_request) { create(:merge_request) }
96 97

    before do
98
      allow(merge_request).to receive(:commits) { [merge_request.source_project.repository.commit] }
D
Dmitriy Zaporozhets 已提交
99 100
      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)
101 102 103
    end

    it "should include notes for commits" do
104 105
      expect(merge_request.commits).not_to be_empty
      expect(merge_request.mr_and_commit_notes.count).to eq(2)
106
    end
107 108 109 110 111 112

    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
113
  end
114 115 116

  describe '#is_being_reassigned?' do
    it 'returns true if the merge_request assignee has changed' do
117
      subject.assignee = create(:user)
118
      expect(subject.is_being_reassigned?).to be_truthy
119 120
    end
    it 'returns false if the merge request assignee has not changed' do
121
      expect(subject.is_being_reassigned?).to be_falsey
122 123
    end
  end
I
Izaak Alpert 已提交
124 125 126

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

130
      expect(subject.for_fork?).to be_truthy
I
Izaak Alpert 已提交
131
    end
D
Dmitriy Zaporozhets 已提交
132

I
Izaak Alpert 已提交
133
    it 'returns false if is not for a fork' do
134
      expect(subject.for_fork?).to be_falsey
I
Izaak Alpert 已提交
135 136 137
    end
  end

138 139 140
  describe 'detection of issues to be closed' do
    let(:issue0) { create :issue, project: subject.project }
    let(:issue1) { create :issue, project: subject.project }
141 142 143 144

    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}") }
145 146

    before do
147
      allow(subject).to receive(:commits).and_return([commit0, commit1, commit2])
148 149 150
    end

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

154 155 156
      closed = subject.closes_issues

      expect(closed).to include(issue0, issue1)
157 158 159
    end

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

163
      expect(subject.closes_issues).to be_empty
164
    end
165 166 167

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

172
      expect(subject.closes_issues).to include(issue2)
173
    end
174 175
  end

176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
  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 已提交
192 193 194 195 196
    it "detects the '[WIP]' prefix" do
      subject.title = "[WIP]#{subject.title}"
      expect(subject).to be_work_in_progress
    end

197 198 199 200 201 202 203 204 205 206
    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 已提交
207
  describe '#can_remove_source_branch?' do
Z
Zeger-Jan van de Weg 已提交
208 209
    let(:user) { create(:user) }
    let(:user2) { create(:user) }
210 211 212 213

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

Z
Zeger-Jan van de Weg 已提交
214 215 216 217
      subject.source_branch = "feature"
      subject.target_branch = "master"
      subject.save!
    end
218

Z
Zeger-Jan van de Weg 已提交
219 220
    it "can't be removed when its a protected branch" do
      allow(subject.source_project).to receive(:protected_branch?).and_return(true)
221 222 223 224
      expect(subject.can_remove_source_branch?(user)).to be_falsey
    end

    it "cant remove a root ref" do
Z
Zeger-Jan van de Weg 已提交
225 226
      subject.source_branch = "master"
      subject.target_branch = "feature"
227 228 229 230

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

Z
Zeger-Jan van de Weg 已提交
231 232 233 234
    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

235 236 237
    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 已提交
238
      expect(subject.can_remove_source_branch?(user)).to be_truthy
239
    end
240 241 242 243

    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
244 245
  end

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

249 250 251 252 253 254 255 256
    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

257
  describe "#hook_attrs" do
258 259 260 261 262 263 264 265 266 267 268
    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

269
    it "has all the required keys" do
270 271 272 273
      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)
274
    end
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 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
  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
339 340
  end

341
  it_behaves_like 'an editable mentionable' do
342
    subject { create(:merge_request) }
343

344 345
    let(:backref_text) { "merge request #{subject.to_reference}" }
    let(:set_mentionable_text) { ->(txt){ subject.description = txt } }
346
  end
V
Vinnie Okada 已提交
347 348

  it_behaves_like 'a Taskable' do
349
    subject { create :merge_request, :simple }
V
Vinnie Okada 已提交
350
  end
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

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