git_reflow 0.7.4 → 0.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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