refresh_service_spec.rb 17.4 KB
Newer Older
1
2
require 'spec_helper'

3
describe MergeRequests::RefreshService do
4
5
  include ProjectForksHelper

6
  let(:project) { create(:project, :repository) }
7
  let(:user) { create(:user) }
8
  let(:service) { described_class }
9

10
  describe '#execute' do
11
12
13
14
15
    before do
      @user = create(:user)
      group = create(:group)
      group.add_owner(@user)

16
      @project = create(:project, :repository, namespace: group)
17
18
      @fork_project = fork_project(@project, @user, repository: true)

Stan Hu's avatar
Stan Hu committed
19
20
      @merge_request = create(:merge_request,
                              source_project: @project,
21
22
                              source_branch: 'master',
                              target_branch: 'feature',
23
                              target_project: @project,
24
                              merge_when_pipeline_succeeds: true,
Zeger-Jan van de Weg's avatar
Zeger-Jan van de Weg committed
25
                              merge_user: @user)
26

Stan Hu's avatar
Stan Hu committed
27
28
      @fork_merge_request = create(:merge_request,
                                   source_project: @fork_project,
29
30
31
32
                                   source_branch: 'master',
                                   target_branch: 'feature',
                                   target_project: @project)

33
34
35
36
37
38
39
40
41
42
43
44
45
46
      @build_failed_todo = create(:todo,
                                  :build_failed,
                                  user: @user,
                                  project: @project,
                                  target: @merge_request,
                                  author: @user)

      @fork_build_failed_todo = create(:todo,
                                       :build_failed,
                                       user: @user,
                                       project: @project,
                                       target: @merge_request,
                                       author: @user)

47
48
49
50
51
52
53
      @commits = @merge_request.commits

      @oldrev = @commits.last.id
      @newrev = @commits.first.id
    end

    context 'push to origin repo source branch' do
54
      let(:refresh_service) { service.new(@project, @user) }
55

56
      before do
57
        allow(refresh_service).to receive(:execute_hooks)
58
59
      end

60
      it 'executes hooks with update action' do
61
62
63
        refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
        reload_mrs

64
        expect(refresh_service).to have_received(:execute_hooks)
65
          .with(@merge_request, 'update', old_rev: @oldrev)
66
67
68
69
70
71
72
73
74
75

        expect(@merge_request.notes).not_to be_empty
        expect(@merge_request).to be_open
        expect(@merge_request.merge_when_pipeline_succeeds).to be_falsey
        expect(@merge_request.diff_head_sha).to eq(@newrev)
        expect(@fork_merge_request).to be_open
        expect(@fork_merge_request.notes).to be_empty
        expect(@build_failed_todo).to be_done
        expect(@fork_build_failed_todo).to be_done
      end
76

77
78
79
80
81
82
83
84
      it 'reloads source branch MRs memoization' do
        refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')

        expect { refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') }.to change {
          refresh_service.instance_variable_get("@source_merge_requests").first.merge_request_diff
        }
      end

85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
      context 'when source branch ref does not exists' do
        before do
          DeleteBranchService.new(@project, @user).execute(@merge_request.source_branch)
        end

        it 'closes MRs without source branch ref' do
          expect { refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') }
            .to change { @merge_request.reload.state }
            .from('opened')
            .to('closed')

          expect(@fork_merge_request.reload).to be_open
        end

        it 'does not change the merge request diff' do
          expect { refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') }
            .not_to change { @merge_request.reload.merge_request_diff }
        end
      end
104
105
106
107
108
109
110
111
112
113
114
115
116
117
    end

    context 'when pipeline exists for the source branch' do
      let!(:pipeline) { create(:ci_empty_pipeline, ref: @merge_request.source_branch, project: @project, sha: @commits.first.sha)}

      subject { service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/master') }

      it 'updates the head_pipeline_id for @merge_request' do
        expect { subject }.to change { @merge_request.reload.head_pipeline_id }.from(nil).to(pipeline.id)
      end

      it 'does not update the head_pipeline_id for @fork_merge_request' do
        expect { subject }.not_to change { @fork_merge_request.reload.head_pipeline_id }
      end
118
119
120
121
122
123
124
125
126
127
128
129
130
    end

    context 'push to origin repo source branch when an MR was reopened' do
      let(:refresh_service) { service.new(@project, @user) }

      before do
        @merge_request.update(state: :reopened)

        allow(refresh_service).to receive(:execute_hooks)
        refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
        reload_mrs
      end

131
      it 'executes hooks with update action' do
132
        expect(refresh_service).to have_received(:execute_hooks)
133
          .with(@merge_request, 'update', old_rev: @oldrev)
134

135
136
137
138
139
140
141
142
143
        expect(@merge_request.notes).not_to be_empty
        expect(@merge_request).to be_open
        expect(@merge_request.merge_when_pipeline_succeeds).to be_falsey
        expect(@merge_request.diff_head_sha).to eq(@newrev)
        expect(@fork_merge_request).to be_open
        expect(@fork_merge_request.notes).to be_empty
        expect(@build_failed_todo).to be_done
        expect(@fork_build_failed_todo).to be_done
      end
144
145
146
    end

    context 'push to origin repo target branch' do
147
148
149
150
151
152
153
154
155
156
157
158
159
160
      context 'when all MRs to the target branch had diffs' do
        before do
          service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/feature')
          reload_mrs
        end

        it 'updates the merge state' do
          expect(@merge_request.notes.last.note).to include('merged')
          expect(@merge_request).to be_merged
          expect(@fork_merge_request).to be_merged
          expect(@fork_merge_request.notes.last.note).to include('merged')
          expect(@build_failed_todo).to be_done
          expect(@fork_build_failed_todo).to be_done
        end
161
162
      end

163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
      context 'when an MR to be closed was empty already' do
        let!(:empty_fork_merge_request) do
          create(:merge_request,
                 source_project: @fork_project,
                 source_branch: 'master',
                 target_branch: 'master',
                 target_project: @project)
        end

        before do
          # This spec already has a fake push, so pretend that we were targeting
          # feature all along.
          empty_fork_merge_request.update_columns(target_branch: 'feature')

          service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/feature')
          reload_mrs
          empty_fork_merge_request.reload
        end

        it 'only updates the non-empty MRs' do
          expect(@merge_request).to be_merged
          expect(@merge_request.notes.last.note).to include('merged')

          expect(@fork_merge_request).to be_merged
          expect(@fork_merge_request.notes.last.note).to include('merged')

          expect(empty_fork_merge_request).to be_open
          expect(empty_fork_merge_request.merge_request_diff.state).to eq('empty')
          expect(empty_fork_merge_request.notes).to be_empty
        end
193
      end
194
195
    end

196
197
198
    context 'manual merge of source branch' do
      before do
        # Merge master -> feature branch
199
        @project.repository.merge(@user, @merge_request.diff_head_sha, @merge_request, 'Test message')
200
201
202
203
204
        commit = @project.repository.commit('feature')
        service.new(@project, @user).execute(@oldrev, commit.id, 'refs/heads/feature')
        reload_mrs
      end

205
206
207
208
209
210
211
212
213
      it 'updates the merge state' do
        expect(@merge_request.notes.last.note).to include('merged')
        expect(@merge_request).to be_merged
        expect(@merge_request.diffs.size).to be > 0
        expect(@fork_merge_request).to be_merged
        expect(@fork_merge_request.notes.last.note).to include('merged')
        expect(@build_failed_todo).to be_done
        expect(@fork_build_failed_todo).to be_done
      end
214
215
    end

216
    context 'push to fork repo source branch' do
217
      let(:refresh_service) { service.new(@fork_project, @user) }
218

219
220
221
222
223
224
225
226
      context 'open fork merge request' do
        before do
          allow(refresh_service).to receive(:execute_hooks)
          refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
          reload_mrs
        end

        it 'executes hooks with update action' do
227
          expect(refresh_service).to have_received(:execute_hooks)
228
            .with(@fork_merge_request, 'update', old_rev: @oldrev)
229

230
231
232
233
234
235
236
          expect(@merge_request.notes).to be_empty
          expect(@merge_request).to be_open
          expect(@fork_merge_request.notes.last.note).to include('added 28 commits')
          expect(@fork_merge_request).to be_open
          expect(@build_failed_todo).to be_pending
          expect(@fork_build_failed_todo).to be_pending
        end
237
238
      end

239
240
241
242
243
244
245
246
247
248
249
250
      context 'closed fork merge request' do
        before do
          @fork_merge_request.close!
          allow(refresh_service).to receive(:execute_hooks)
          refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
          reload_mrs
        end

        it 'do not execute hooks with update action' do
          expect(refresh_service).not_to have_received(:execute_hooks)
        end

251
252
253
254
255
256
257
258
        it 'updates merge request to closed state' do
          expect(@merge_request.notes).to be_empty
          expect(@merge_request).to be_open
          expect(@fork_merge_request.notes).to be_empty
          expect(@fork_merge_request).to be_closed
          expect(@build_failed_todo).to be_pending
          expect(@fork_build_failed_todo).to be_pending
        end
259
      end
260
261
262
    end

    context 'push to fork repo target branch' do
263
264
265
266
267
268
      describe 'changes to merge requests' do
        before do
          service.new(@fork_project, @user).execute(@oldrev, @newrev, 'refs/heads/feature')
          reload_mrs
        end

269
270
271
272
273
274
275
276
        it 'updates the merge request state' do
          expect(@merge_request.notes).to be_empty
          expect(@merge_request).to be_open
          expect(@fork_merge_request.notes).to be_empty
          expect(@fork_merge_request).to be_open
          expect(@build_failed_todo).to be_pending
          expect(@fork_build_failed_todo).to be_pending
        end
277
278
      end

279
280
281
282
283
284
285
      describe 'merge request diff' do
        it 'does not reload the diff of the merge request made from fork' do
          expect do
            service.new(@fork_project, @user).execute(@oldrev, @newrev, 'refs/heads/feature')
          end.not_to change { @fork_merge_request.reload.merge_request_diff }
        end
      end
286
287
288
289
290
291
292
293
294
    end

    context 'push to origin repo target branch after fork project was removed' do
      before do
        @fork_project.destroy
        service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/feature')
        reload_mrs
      end

295
296
297
298
299
300
301
302
      it 'updates the merge request state' do
        expect(@merge_request.notes.last.note).to include('merged')
        expect(@merge_request).to be_merged
        expect(@fork_merge_request).to be_open
        expect(@fork_merge_request.notes).to be_empty
        expect(@build_failed_todo).to be_done
        expect(@fork_build_failed_todo).to be_done
      end
303
304
    end

305
306
    context 'push new branch that exists in a merge request' do
      let(:refresh_service) { service.new(@fork_project, @user) }
307
308

      it 'refreshes the merge request' do
309
        expect(refresh_service).to receive(:execute_hooks)
310
                                       .with(@fork_merge_request, 'update', old_rev: Gitlab::Git::BLANK_SHA)
311
312
        allow_any_instance_of(Repository).to receive(:merge_base).and_return(@oldrev)

313
314
315
        refresh_service.execute(Gitlab::Git::BLANK_SHA, @newrev, 'refs/heads/master')
        reload_mrs

316
317
        expect(@merge_request.notes).to be_empty
        expect(@merge_request).to be_open
318

319
        notes = @fork_merge_request.notes.reorder(:created_at).map(&:note)
320
321
        expect(notes[0]).to include('restored source branch `master`')
        expect(notes[1]).to include('added 28 commits')
322
323
        expect(@fork_merge_request).to be_open
      end
324
325
    end

326
    context 'merge request metrics' do
327
328
329
330
331
      let(:issue) { create :issue, project: @project }
      let(:commit_author) { create :user }
      let(:commit) { project.commit }

      before do
332
333
        project.add_developer(commit_author)
        project.add_developer(user)
334
335
336
337
338
339
340
341
342
343
344
345

        allow(commit).to receive_messages(
          safe_message: "Closes #{issue.to_reference}",
          references: [issue],
          author_name: commit_author.name,
          author_email: commit_author.email,
          committed_date: Time.now
        )

        allow_any_instance_of(MergeRequest).to receive(:commits).and_return([commit])
      end

346
347
348
349
350
351
352
353
354
355
356
      context 'when the merge request is sourced from the same project' do
        it 'creates a `MergeRequestsClosingIssues` record for each issue closed by a commit' do
          merge_request = create(:merge_request, target_branch: 'master', source_branch: 'feature', source_project: @project)
          refresh_service = service.new(@project, @user)
          allow(refresh_service).to receive(:execute_hooks)
          refresh_service.execute(@oldrev, @newrev, 'refs/heads/feature')

          issue_ids = MergeRequestsClosingIssues.where(merge_request: merge_request).pluck(:issue_id)
          expect(issue_ids).to eq([issue.id])
        end
      end
357

358
359
      context 'when the merge request is sourced from a different project' do
        it 'creates a `MergeRequestsClosingIssues` record for each issue closed by a commit' do
360
          forked_project = fork_project(@project, @user, repository: true)
361
362
363
364
365
366
367
368
369
370
371
372
373

          merge_request = create(:merge_request,
                                 target_branch: 'master',
                                 source_branch: 'feature',
                                 target_project: @project,
                                 source_project: forked_project)
          refresh_service = service.new(@project, @user)
          allow(refresh_service).to receive(:execute_hooks)
          refresh_service.execute(@oldrev, @newrev, 'refs/heads/feature')

          issue_ids = MergeRequestsClosingIssues.where(merge_request: merge_request).pluck(:issue_id)
          expect(issue_ids).to eq([issue.id])
        end
374
375
376
      end
    end

377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
    context 'marking the merge request as work in progress' do
      let(:refresh_service) { service.new(@project, @user) }
      before do
        allow(refresh_service).to receive(:execute_hooks)
      end

      it 'marks the merge request as work in progress from fixup commits' do
        fixup_merge_request = create(:merge_request,
                                     source_project: @project,
                                     source_branch: 'wip',
                                     target_branch: 'master',
                                     target_project: @project)
        commits = fixup_merge_request.commits
        oldrev = commits.last.id
        newrev = commits.first.id

        refresh_service.execute(oldrev, newrev, 'refs/heads/wip')
        fixup_merge_request.reload

        expect(fixup_merge_request.work_in_progress?).to eq(true)
        expect(fixup_merge_request.notes.last.note).to match(
          /marked as a \*\*Work In Progress\*\* from #{Commit.reference_pattern}/
        )
      end

      it 'references the commit that caused the Work in Progress status' do
        refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
        allow(refresh_service).to receive(:find_new_commits)
        refresh_service.instance_variable_set("@commits", [
406
          double(
407
            id: 'aaaaaaa',
408
            sha: '38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e',
409
410
411
412
            short_id: 'aaaaaaa',
            title: 'Fix issue',
            work_in_progress?: false
          ),
413
          double(
414
            id: 'bbbbbbb',
415
            sha: '498214de67004b1da3d820901307bed2a68a8ef6',
416
417
418
419
420
            short_id: 'bbbbbbb',
            title: 'fixup! Fix issue',
            work_in_progress?: true,
            to_reference: 'bbbbbbb'
          ),
421
          double(
422
            id: 'ccccccc',
423
            sha: '1b12f15a11fc6e62177bef08f47bc7b5ce50b141',
424
425
426
427
            short_id: 'ccccccc',
            title: 'fixup! Fix issue',
            work_in_progress?: true,
            to_reference: 'ccccccc'
428
          )
429
430
431
432
433
434
435
        ])
        refresh_service.execute(@oldrev, @newrev, 'refs/heads/wip')
        reload_mrs
        expect(@merge_request.notes.last.note).to eq(
          "marked as a **Work In Progress** from bbbbbbb"
        )
      end
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461

      it 'does not mark as WIP based on commits that do not belong to an MR' do
        allow(refresh_service).to receive(:find_new_commits)
        refresh_service.instance_variable_set("@commits", [
          double(
            id: 'aaaaaaa',
            sha: 'aaaaaaa',
            short_id: 'aaaaaaa',
            title: 'Fix issue',
            work_in_progress?: false
          ),
          double(
            id: 'bbbbbbb',
            sha: 'bbbbbbbb',
            short_id: 'bbbbbbb',
            title: 'fixup! Fix issue',
            work_in_progress?: true,
            to_reference: 'bbbbbbb'
          )
        ])

        refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
        reload_mrs

        expect(@merge_request.work_in_progress?).to be_falsey
      end
462
463
    end

464
465
466
    def reload_mrs
      @merge_request.reload
      @fork_merge_request.reload
467
468
      @build_failed_todo.reload
      @fork_build_failed_todo.reload
469
470
471
    end
  end
end