git_reflow 0.7.4 → 0.7.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,496 @@
1
+ require 'spec_helper'
2
+
3
+ describe GitReflow do
4
+ let(:git_server) { GitReflow::GitServer::GitHub.new {} }
5
+ let(:github) { Github.new basic_auth: "#{user}:#{password}" }
6
+ let(:user) { 'reenhanced' }
7
+ let(:password) { 'shazam' }
8
+ let(:oauth_token_hash) { Hashie::Mash.new({ token: 'a1b2c3d4e5f6g7h8i9j0', note: 'hostname.local git-reflow'}) }
9
+ let(:repo) { 'repo' }
10
+ let(:base_branch) { 'master' }
11
+ let(:feature_branch) { 'new-feature' }
12
+ let(:enterprise_site) { 'https://github.reenhanced.com' }
13
+ let(:enterprise_api) { 'https://github.reenhanced.com' }
14
+ let(:hostname) { 'hostname.local' }
15
+
16
+ let(:github_authorizations) { Github::Client::Authorizations.new }
17
+ let(:existing_pull_requests) { Fixture.new('pull_requests/pull_requests.json').to_json_hashie }
18
+ let(:existing_pull_request) { GitReflow::GitServer::GitHub::PullRequest.new existing_pull_requests.first }
19
+
20
+ before do
21
+
22
+ # Stubbing out minimum_approvals value to test 2 LGTM reviewers in gitconfig file
23
+ allow(GitReflow::Config).to receive(:get).with("constants.minimumApprovals").and_return("2")
24
+ allow(GitReflow::Config).to receive(:get).and_call_original
25
+
26
+ HighLine.any_instance.stub(:ask) do |terminal, question|
27
+ values = {
28
+ "Please enter your GitHub username: " => user,
29
+ "Please enter your GitHub password (we do NOT store this): " => password,
30
+ "Please enter your Enterprise site URL (e.g. https://github.company.com):" => enterprise_site,
31
+ "Please enter your Enterprise API endpoint (e.g. https://github.company.com/api/v3):" => enterprise_api,
32
+ "Would you like to push this branch to your remote repo and cleanup your feature branch? " => 'yes',
33
+ "Would you like to open it in your browser?" => 'n'
34
+ }
35
+ return_value = values[question] || values[terminal]
36
+ question = ""
37
+ return_value
38
+ end
39
+ end
40
+
41
+ context :deliver do
42
+ let(:branch) { 'new-feature' }
43
+ let(:inputs) { {} }
44
+ let!(:github) do
45
+ allow_any_instance_of(GitReflow::GitServer::GitHub::PullRequest).to receive(:build).and_return(Struct.new(:state, :description, :url).new)
46
+ stub_github_with({
47
+ :user => user,
48
+ :password => password,
49
+ :repo => repo,
50
+ :branch => branch,
51
+ :pull => existing_pull_request
52
+ })
53
+ end
54
+
55
+
56
+ before do
57
+ GitReflow.stub(:append_to_squashed_commit_message).and_return(true)
58
+
59
+ module Kernel
60
+ def system(cmd)
61
+ "call #{cmd}"
62
+ end
63
+ end
64
+ end
65
+
66
+ subject { GitReflow.deliver inputs }
67
+
68
+ it "fetches the latest changes to the destination branch" do
69
+ GitReflow.should_receive(:fetch_destination).with('master')
70
+ subject
71
+ end
72
+
73
+ it "looks for a pull request matching the feature branch and destination branch" do
74
+ github.should_receive(:find_open_pull_request).with(from: branch, to: 'master')
75
+ subject
76
+ end
77
+
78
+ context "and pull request exists for the feature branch to the destination branch" do
79
+ before do
80
+ github.stub(:build_status).and_return(build_status)
81
+ github.should_receive(:find_open_pull_request).and_return(existing_pull_request)
82
+ existing_pull_request.stub(:has_comments?).and_return(true)
83
+ github.stub(:reviewers).and_return(['codenamev'])
84
+
85
+ existing_pull_request.stub(:approvals).and_return(["Simon", "John"])
86
+ existing_pull_request.stub_chain(:last_comment, :match).and_return(true)
87
+ end
88
+
89
+ context 'and build status is not "success"' do
90
+ let(:build_status) { Hashie::Mash.new({ state: 'failure', description: 'Build resulted in failed test(s)' }) }
91
+
92
+ before do
93
+ existing_pull_request.stub(:build).and_return(build_status)
94
+ existing_pull_request.stub(:has_comments?).and_return(true)
95
+ end
96
+
97
+ it "halts delivery and notifies user of a failed build" do
98
+ expect { subject }.to have_said "#{build_status.description}: #{build_status.target_url}", :deliver_halted
99
+ end
100
+ end
101
+
102
+ context 'and build status is nil' do
103
+ let(:build_status) { nil }
104
+ let(:inputs) {{ 'skip_lgtm' => true }}
105
+
106
+ before do
107
+ # stubbing unrelated results so we can just test that it made it insdide the conditional block
108
+ existing_pull_request.stub(:has_comments?).and_return(true)
109
+ existing_pull_request.stub(:reviewers).and_return([])
110
+ GitReflow.stub(:update_destination).and_return(true)
111
+ GitReflow.stub(:merge_feature_branch).and_return(true)
112
+ GitReflow.stub(:append_to_squashed_commit_message).and_return(true)
113
+ end
114
+
115
+ it "ignores build status when not setup" do
116
+ expect { subject }.to have_said "Merge complete!", :success
117
+ end
118
+ end
119
+
120
+ context 'and build status is "success"' do
121
+ let(:build_status) { Hashie::Mash.new({ state: 'success' }) }
122
+
123
+ context 'and has comments' do
124
+ before do
125
+ existing_pull_request.stub(:has_comments?).and_return(true)
126
+ end
127
+
128
+ context 'but there are 2 LGTMs and irrelevant last comment' do
129
+ let(:lgtm_comment_authors) { ['nhance', 'Simon'] }
130
+ before do
131
+ existing_pull_request.stub(:build).and_return(build_status)
132
+ existing_pull_request.stub(:approvals).and_return(lgtm_comment_authors)
133
+ allow(GitReflow::GitServer::PullRequest).to receive(:minimum_approvals).and_return("2")
134
+ existing_pull_request.stub(:reviewers_pending_response).and_return([])
135
+ existing_pull_request.stub_chain(:last_comment, :match).and_return(nil)
136
+ end
137
+
138
+ it "doesn't include the pull request body in the commit message" do
139
+ squash_message = "#{existing_pull_request.body}\nCloses ##{existing_pull_request.number}\n\nLGTM given by: @nhance, @Simon\n"
140
+ GitReflow.should_receive(:append_to_squashed_commit_message).never.with(squash_message)
141
+ subject
142
+ end
143
+
144
+ context "and the pull request has no body" do
145
+ let(:first_commit_message) { "We'll do it live." }
146
+
147
+ before do
148
+ existing_pull_request.description = ''
149
+ github.stub(:find_open_pull_request).and_return(existing_pull_request)
150
+ GitReflow.stub(:get_first_commit_message).and_return(first_commit_message)
151
+ existing_pull_request.stub(:reviewers).and_return(lgtm_comment_authors)
152
+ end
153
+
154
+ it "doesn't include the first commit message for the new branch in the commit message of the merge" do
155
+ squash_message = "#{first_commit_message}\nCloses ##{existing_pull_request.number}\n\nLGTM given by: @nhance, @Simon\n"
156
+ GitReflow.should_receive(:append_to_squashed_commit_message).never.with(squash_message)
157
+ subject
158
+ end
159
+ end
160
+
161
+ it "doesn't notify user of the merge and performs it" do
162
+ GitReflow.should_receive(:merge_feature_branch).never.with('new-feature', {
163
+ destination_branch: 'master',
164
+ pull_request_number: existing_pull_request.number,
165
+ lgtm_authors: ['nhance', 'Simon'],
166
+ message: existing_pull_request.body
167
+ })
168
+
169
+ expect { subject }.to_not have_output "Merging pull request ##{existing_pull_request.number}: '#{existing_pull_request.title}', from '#{existing_pull_request.head.label}' into '#{existing_pull_request.base.label}'"
170
+ end
171
+
172
+ it "doesn't update the destination branch" do
173
+ GitReflow.should_receive(:update_destination).with('master').never
174
+ subject
175
+ end
176
+
177
+ context "and doesn't clean up feature branch" do
178
+ before do
179
+ HighLine.any_instance.stub(:ask) do |terminal, question|
180
+ values = {
181
+ "Please enter your GitHub username: " => user,
182
+ "Please enter your GitHub password (we do NOT store this): " => password,
183
+ "Please enter your Enterprise site URL (e.g. https://github.company.com):" => enterprise_site,
184
+ "Please enter your Enterprise API endpoint (e.g. https://github.company.com/api/v3):" => enterprise_api,
185
+ "Would you like to push this branch to your remote repo and cleanup your feature branch? " => 'yes',
186
+ "Would you like to open it in your browser?" => 'no'
187
+ }
188
+ return_value = values[question] || values[terminal]
189
+ question = ""
190
+ return_value
191
+ end
192
+ end
193
+
194
+ context "not always" do
195
+ before do
196
+ GitReflow::Config.stub(:get) { "false" }
197
+ end
198
+
199
+ it "doesn't push local squash merged base branch to remote repo" do
200
+ expect { subject }.to_not have_run_command("git push origin master")
201
+ end
202
+
203
+ it "doesn't delete the remote feature branch" do
204
+ expect { subject }.to_not have_run_command("git push origin :new-feature")
205
+ end
206
+
207
+ it "doesn't delete the local feature branch" do
208
+ expect { subject }.to_not have_run_command("git branch -D new-feature")
209
+ end
210
+ end
211
+
212
+ context "always" do
213
+ before do
214
+ GitReflow::Config.stub(:get) { "true" }
215
+ end
216
+
217
+ it "doesn't push local squash merged base branch to remote repo" do
218
+ expect { subject }.to_not have_run_command("git push origin master")
219
+ end
220
+
221
+ it "doesn't delete the remote feature branch" do
222
+ expect { subject }.to_not have_run_command("git push origin :new-feature")
223
+ end
224
+
225
+ it "doesn't delete the local feature branch" do
226
+ expect { subject }.to_not have_run_command("git branch -D new-feature")
227
+ end
228
+ end
229
+
230
+ end
231
+
232
+ context "and not cleaning up feature branch" do
233
+ before do
234
+ HighLine.any_instance.stub(:ask) do |terminal, question|
235
+ values = {
236
+ "Please enter your GitHub username: " => user,
237
+ "Please enter your GitHub password (we do NOT store this): " => password,
238
+ "Please enter your Enterprise site URL (e.g. https://github.company.com):" => enterprise_site,
239
+ "Please enter your Enterprise API endpoint (e.g. https://github.company.com/api/v3):" => enterprise_api,
240
+ "Would you like to push this branch to your remote repo and cleanup your feature branch? " => 'no',
241
+ "Would you like to open it in your browser?" => 'no'
242
+ }
243
+ return_value = values[question] || values[terminal]
244
+ question = ""
245
+ return_value
246
+ end
247
+ end
248
+
249
+ it "doesn't update the remote repo with the new squash merge" do
250
+ expect { subject }.to_not have_run_command('git push origin master')
251
+ end
252
+
253
+ it "doesn't delete the feature branch on the remote repo" do
254
+ expect { subject }.to_not have_run_command('git push origin :new-feature')
255
+ end
256
+
257
+ it "doesn't delete the local feature branch" do
258
+ expect { subject }.to_not have_run_command('git branch -D new-feature')
259
+ end
260
+
261
+ it "doesn't provide instructions to undo the steps taken" do
262
+ expect { subject }.to_not have_output("To reset and go back to your branch run \`git reset --hard origin/master && git checkout new-feature\`")
263
+ end
264
+ end
265
+
266
+ context "and there were issues commiting the squash merge to the base branch" do
267
+ before { stub_with_fallback(GitReflow, :run_command_with_label).with('git commit', {with_system: true}).and_return false }
268
+ it "doesn't notifies user of issues commiting the squash merge of the feature branch" do
269
+ expect { subject }.to_not have_said("There were problems commiting your feature... please check the errors above and try again.", :error)
270
+ end
271
+ end
272
+ end
273
+
274
+ context 'but there are 2 LGTMs and LGTM last comment' do
275
+ let(:lgtm_comment_authors) { ['nhance', 'Simon'] }
276
+ before do
277
+ existing_pull_request.stub(:approvals).and_return(lgtm_comment_authors)
278
+ existing_pull_request.stub(:reviewers_pending_response).and_return([])
279
+ existing_pull_request.stub_chain(:last_comment, :match).and_return(true)
280
+ end
281
+
282
+ it "includes the pull request body in the commit message" do
283
+ squash_message = "#{existing_pull_request.body}\nCloses ##{existing_pull_request.number}\n\nLGTM given by: @nhance, @Simon\n"
284
+ GitReflow.should_receive(:append_to_squashed_commit_message).with(squash_message)
285
+ subject
286
+ end
287
+
288
+ context "build status failure, testing description and target_url" do
289
+ let(:build_status) { Hashie::Mash.new({ state: 'failure', description: 'Build resulted in failed test(s)', target_url: "www.error.com" }) }
290
+
291
+ before do
292
+ existing_pull_request.stub(:build).and_return(build_status)
293
+ existing_pull_request.stub(:reviewers).and_return(lgtm_comment_authors)
294
+ existing_pull_request.stub(:has_comments?).and_return(true)
295
+ end
296
+
297
+ it "halts delivery and notifies user of a failed build" do
298
+ expect { subject }.to have_said "#{build_status.description}: #{build_status.url}", :deliver_halted
299
+ end
300
+ end
301
+
302
+ context "build status nil" do
303
+ let(:build_status) { nil }
304
+
305
+ before do
306
+ github.stub(:build).and_return(build_status)
307
+ existing_pull_request.stub(:reviewers_pending_response).and_return([])
308
+ existing_pull_request.stub(:has_comments_or_approvals).and_return(true)
309
+ end
310
+
311
+ it "commits the changes if the build status is nil but has comments/approvals and no pending response" do
312
+ expect{ subject }.to have_said 'Merge complete!', :success
313
+ end
314
+ end
315
+
316
+ context "and the pull request has no body" do
317
+ let(:first_commit_message) { "We'll do it live." }
318
+
319
+ before do
320
+ existing_pull_request.description = ''
321
+ github.stub(:find_open_pull_request).and_return(existing_pull_request)
322
+ GitReflow.stub(:get_first_commit_message).and_return(first_commit_message)
323
+ existing_pull_request.stub(:reviewers).and_return(lgtm_comment_authors)
324
+ end
325
+
326
+ it "includes the first commit message for the new branch in the commit message of the merge" do
327
+ squash_message = "#{first_commit_message}\nCloses ##{existing_pull_request.number}\n\nLGTM given by: @nhance, @Simon\n"
328
+ GitReflow.should_receive(:append_to_squashed_commit_message).with(squash_message)
329
+ subject
330
+ end
331
+ end
332
+
333
+ it "notifies user of the merge and performs it" do
334
+ GitReflow.should_receive(:merge_feature_branch).with('new-feature', {
335
+ destination_branch: 'master',
336
+ pull_request_number: existing_pull_request.number,
337
+ lgtm_authors: ['nhance', 'Simon'],
338
+ message: existing_pull_request.body
339
+ })
340
+
341
+ expect { subject }.to have_output "Merging pull request ##{existing_pull_request.number}: '#{existing_pull_request.title}', from '#{existing_pull_request.head.label}' into '#{existing_pull_request.base.label}'"
342
+ end
343
+
344
+ it "updates the destination branch" do
345
+ GitReflow.should_receive(:update_destination).with('master')
346
+ subject
347
+ end
348
+
349
+ it "commits the changes for the squash merge" do
350
+ expect{ subject }.to have_said 'Merge complete!', :success
351
+ end
352
+
353
+ context "and cleaning up feature branch" do
354
+ before do
355
+ HighLine.any_instance.stub(:ask) do |terminal, question|
356
+ values = {
357
+ "Please enter your GitHub username: " => user,
358
+ "Please enter your GitHub password (we do NOT store this): " => password,
359
+ "Please enter your Enterprise site URL (e.g. https://github.company.com):" => enterprise_site,
360
+ "Please enter your Enterprise API endpoint (e.g. https://github.company.com/api/v3):" => enterprise_api,
361
+ "Would you like to push this branch to your remote repo and cleanup your feature branch? " => 'yes',
362
+ "Would you like to open it in your browser?" => 'no'
363
+ }
364
+ return_value = values[question] || values[terminal]
365
+ question = ""
366
+ return_value
367
+ end
368
+ end
369
+
370
+ context "not always" do
371
+ before do
372
+ GitReflow::Config.stub(:get) { "false" }
373
+ end
374
+
375
+ it "pushes local squash merged base branch to remote repo" do
376
+ expect { subject }.to have_run_command("git push origin master")
377
+ end
378
+
379
+ it "deletes the remote feature branch" do
380
+ expect { subject }.to have_run_command("git push origin :new-feature")
381
+ end
382
+
383
+ it "deletes the local feature branch" do
384
+ expect { subject }.to have_run_command("git branch -D new-feature")
385
+ end
386
+ end
387
+
388
+ context "always" do
389
+ before do
390
+ GitReflow::Config.stub(:get) { "true" }
391
+ end
392
+
393
+ it "pushes local squash merged base branch to remote repo" do
394
+ expect { subject }.to have_run_command("git push origin master")
395
+ end
396
+
397
+ it "deletes the remote feature branch" do
398
+ expect { subject }.to have_run_command("git push origin :new-feature")
399
+ end
400
+
401
+ it "deletes the local feature branch" do
402
+ expect { subject }.to have_run_command("git branch -D new-feature")
403
+ end
404
+ end
405
+ end
406
+
407
+ context "and not cleaning up feature branch" do
408
+ before do
409
+ HighLine.any_instance.stub(:ask) do |terminal, question|
410
+ values = {
411
+ "Please enter your GitHub username: " => user,
412
+ "Please enter your GitHub password (we do NOT store this): " => password,
413
+ "Please enter your Enterprise site URL (e.g. https://github.company.com):" => enterprise_site,
414
+ "Please enter your Enterprise API endpoint (e.g. https://github.company.com/api/v3):" => enterprise_api,
415
+ "Would you like to push this branch to your remote repo and cleanup your feature branch? " => 'no',
416
+ "Would you like to open it in your browser?" => 'no'
417
+ }
418
+ return_value = values[question] || values[terminal]
419
+ question = ""
420
+ return_value
421
+ end
422
+ end
423
+
424
+ it "doesn't update the remote repo with the new squash merge" do
425
+ expect { subject }.to_not have_run_command('git push origin master')
426
+ end
427
+
428
+ it "doesn't delete the feature branch on the remote repo" do
429
+ expect { subject }.to_not have_run_command('git push origin :new-feature')
430
+ end
431
+
432
+ it "doesn't delete the local feature branch" do
433
+ expect { subject }.to_not have_run_command('git branch -D new-feature')
434
+ end
435
+
436
+ it "provides instructions to undo the steps taken" do
437
+ expect { subject }.to have_output("To reset and go back to your branch run \`git reset --hard origin/master && git checkout new-feature\`")
438
+ end
439
+ end
440
+
441
+ context "and there were issues commiting the squash merge to the base branch" do
442
+ before { stub_with_fallback(GitReflow, :run_command_with_label).with('git commit', {with_system: true}).and_return false }
443
+ it "notifies user of issues commiting the squash merge of the feature branch" do
444
+ expect { subject }.to have_said("There were problems commiting your feature... please check the errors above and try again.", :error)
445
+ end
446
+ end
447
+
448
+ end
449
+
450
+ context 'but there are still unaddressed comments' do
451
+ let(:open_comment_authors) { ['nhance', 'codenamev'] }
452
+ before { existing_pull_request.stub(:reviewers_pending_response).and_return(open_comment_authors) }
453
+ it "notifies the user to get their code reviewed" do
454
+ expect { subject }.to have_said "You still need a LGTM from: #{open_comment_authors.join(', ')}", :deliver_halted
455
+ end
456
+ end
457
+ end
458
+
459
+ context 'but has no comments' do
460
+ before do
461
+ existing_pull_request.stub(:has_comments?).and_return(false)
462
+ existing_pull_request.stub(:approvals).and_return(['John', 'Simon'])
463
+ existing_pull_request.stub(:reviewers_pending_response).and_return([])
464
+ existing_pull_request.stub(:build).and_return(build_status)
465
+ end
466
+
467
+ it "notifies the user to get their code reviewed" do
468
+ expect { subject }.to have_said "Merge complete!", :success
469
+ end
470
+ end
471
+
472
+ it "successfully finds a pull request for the current feature branch" do
473
+ expect { subject }.to have_output "Merging pull request #1: 'new-feature', from 'new-feature' into 'master'"
474
+ end
475
+
476
+ it "checks out the destination branch and updates any remote changes" do
477
+ GitReflow.should_receive(:update_destination)
478
+ subject
479
+ end
480
+
481
+ it "merges and squashes the feature branch into the master branch" do
482
+ GitReflow.should_receive(:merge_feature_branch)
483
+ subject
484
+ end
485
+ end
486
+ end
487
+
488
+ context "and no pull request exists for the feature branch to the destination branch" do
489
+ before { github.stub(:find_open_pull_request).and_return(nil) }
490
+
491
+ it "notifies the user of a missing pull request" do
492
+ expect { subject }.to have_said "No pull request exists for #{user}:#{branch}\nPlease submit your branch for review first with \`git reflow review\`", :deliver_halted
493
+ end
494
+ end
495
+ end
496
+ end