shipit-engine 0.27.1 → 0.28.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +30 -1
  3. data/app/assets/stylesheets/_pages/_commits.scss +2 -0
  4. data/app/assets/stylesheets/_pages/_deploy.scss +6 -0
  5. data/app/controllers/shipit/api/release_statuses_controller.rb +22 -0
  6. data/app/controllers/shipit/api/stacks_controller.rb +6 -1
  7. data/app/controllers/shipit/commits_controller.rb +12 -1
  8. data/app/controllers/shipit/deploys_controller.rb +11 -0
  9. data/app/controllers/shipit/stacks_controller.rb +29 -1
  10. data/app/controllers/shipit/tasks_controller.rb +13 -1
  11. data/app/helpers/shipit/merge_status_helper.rb +2 -2
  12. data/app/helpers/shipit/stacks_helper.rb +10 -4
  13. data/app/jobs/shipit/perform_task_job.rb +1 -0
  14. data/app/jobs/shipit/update_github_last_deployed_ref_job.rb +41 -0
  15. data/app/models/shipit/command_line_user.rb +58 -0
  16. data/app/models/shipit/commit.rb +42 -2
  17. data/app/models/shipit/deploy.rb +31 -2
  18. data/app/models/shipit/deploy_spec/lerna_discovery.rb +43 -19
  19. data/app/models/shipit/deploy_spec/pypi_discovery.rb +5 -1
  20. data/app/models/shipit/rollback.rb +4 -2
  21. data/app/models/shipit/stack.rb +72 -15
  22. data/app/models/shipit/task.rb +30 -0
  23. data/app/models/shipit/undeployed_commit.rb +10 -1
  24. data/app/serializers/shipit/command_line_user_serializer.rb +4 -0
  25. data/app/views/layouts/shipit.html.erb +5 -1
  26. data/app/views/shipit/commits/_commit.html.erb +7 -2
  27. data/app/views/shipit/merge_status/_commit_count_warning.html.erb +1 -5
  28. data/app/views/shipit/merge_status/backlogged.html.erb +1 -1
  29. data/app/views/shipit/merge_status/failure.html.erb +1 -1
  30. data/app/views/shipit/merge_status/locked.html.erb +1 -1
  31. data/app/views/shipit/merge_status/success.html.erb +1 -1
  32. data/app/views/shipit/stacks/show.html.erb +10 -1
  33. data/app/views/shipit/tasks/_task_output.html.erb +2 -2
  34. data/config/locales/en.yml +2 -1
  35. data/config/routes.rb +8 -1
  36. data/db/migrate/20190502020249_add_lock_author_id_to_commits.rb +5 -0
  37. data/lib/shipit.rb +14 -2
  38. data/lib/shipit/cast_value.rb +9 -0
  39. data/lib/shipit/command.rb +62 -16
  40. data/lib/shipit/line_buffer.rb +42 -0
  41. data/lib/shipit/version.rb +1 -1
  42. data/lib/tasks/shipit.rake +27 -0
  43. data/test/controllers/api/release_statuses_controller_test.rb +66 -0
  44. data/test/controllers/api/stacks_controller_test.rb +19 -0
  45. data/test/controllers/commits_controller_test.rb +30 -6
  46. data/test/controllers/deploys_controller_test.rb +51 -2
  47. data/test/controllers/tasks_controller_test.rb +24 -0
  48. data/test/dummy/db/schema.rb +2 -1
  49. data/test/dummy/db/seeds.rb +2 -0
  50. data/test/fixtures/shipit/check_runs.yml +11 -0
  51. data/test/fixtures/shipit/commits.yml +104 -0
  52. data/test/fixtures/shipit/stacks.yml +98 -3
  53. data/test/fixtures/shipit/tasks.yml +42 -0
  54. data/test/jobs/update_github_last_deployed_ref_job_test.rb +88 -0
  55. data/test/models/commits_test.rb +88 -1
  56. data/test/models/deploy_spec_test.rb +34 -6
  57. data/test/models/deploys_test.rb +308 -6
  58. data/test/models/rollbacks_test.rb +17 -11
  59. data/test/models/stacks_test.rb +217 -4
  60. data/test/models/tasks_test.rb +13 -0
  61. data/test/models/undeployed_commits_test.rb +62 -3
  62. data/test/test_helper.rb +0 -1
  63. data/test/unit/command_test.rb +55 -0
  64. data/test/unit/line_buffer_test.rb +20 -0
  65. metadata +142 -128
@@ -0,0 +1,88 @@
1
+ require 'test_helper'
2
+
3
+ module Shipit
4
+ class UpdateGithubLastDeployedRefJobTest < ActiveSupport::TestCase
5
+ setup do
6
+ @stack = shipit_stacks(:shipit)
7
+ @job = UpdateGithubLastDeployedRefJob.new
8
+ @deploy = @stack.deploys.last
9
+ @commit = @deploy.until_commit
10
+ @api_client = Shipit.github.api
11
+ @expected_ref_prefix = "shipit-deploy/#{@stack.environment}"
12
+ @expected_name = @stack.github_repo_name
13
+ @expected_sha = @commit.sha
14
+
15
+ expected_ref = ["refs", @expected_ref_prefix].join('/')
16
+ ref_url = "http://api.github.test.com/shopify/shipit-engine/git/#{expected_ref}"
17
+ commit_url = "https://api.github.test.com/repos/shopify/shipit-engine/git/commits/#{@commit.sha}"
18
+ response_inner_obj = OpenStruct.new(sha: @commit.sha, type: "commit", url: commit_url)
19
+ @response = OpenStruct.new(ref: expected_ref, node_id: "blah", url: ref_url, object: response_inner_obj)
20
+ end
21
+
22
+ test "#perform will create a ref when one is not present" do
23
+ Octokit::UnprocessableEntity.any_instance.stubs(:build_error_message).returns("Reference does not exist")
24
+
25
+ @api_client.expects(:update_ref).with(@expected_name, @expected_ref_prefix, @expected_sha).raises(Octokit::UnprocessableEntity)
26
+ @api_client.expects(:create_ref).with(@expected_name, @expected_ref_prefix, @expected_sha).returns(@response)
27
+
28
+ result = @job.perform(@stack)
29
+
30
+ assert_equal @response, result
31
+ end
32
+
33
+ test "#perform will update a ref when one is present" do
34
+ prior_response = @response.dup
35
+ prior_response.object = prior_response.object.dup
36
+ new_sha = "some_new_sha"
37
+ @response.object.sha = new_sha
38
+ @commit.sha = new_sha
39
+ @commit.save
40
+
41
+ @api_client.expects(:update_ref).with(@expected_name, @expected_ref_prefix, new_sha).returns(@response)
42
+
43
+ result = @job.perform(@stack)
44
+
45
+ assert_equal @response, result
46
+ end
47
+
48
+ test '#perform will raise an exception for non ref existence errors' do
49
+ Octokit::UnprocessableEntity.any_instance.stubs(:build_error_message).returns("Some other error.")
50
+
51
+ @api_client.expects(:update_ref).with(@expected_name, @expected_ref_prefix, @expected_sha).raises(Octokit::UnprocessableEntity)
52
+ @api_client.expects(:create_ref).with(@expected_name, @expected_ref_prefix, @expected_sha).never
53
+
54
+ assert_raises Octokit::UnprocessableEntity do
55
+ @job.perform(@stack)
56
+ end
57
+ end
58
+
59
+ test '#perform skips unsuccessful deploys when finding sha to use' do
60
+ prior_response = @response.dup
61
+ prior_response.object = prior_response.object.dup
62
+ new_sha = "some_new_sha"
63
+ @response.object.sha = new_sha
64
+ @commit.sha = new_sha
65
+ @commit.save
66
+
67
+ new_deploy = @stack.deploys.last.dup
68
+ new_commit = new_deploy.until_commit.dup
69
+ new_commit.sha = "some fake sha"
70
+ new_deploy.until_commit = new_commit
71
+ new_deploy.id = nil
72
+ new_deploy.status = "faulty"
73
+ new_deploy.save
74
+
75
+ @api_client.expects(:update_ref).with(@expected_name, @expected_ref_prefix, new_sha).returns(@response)
76
+ result = @job.perform(@stack)
77
+
78
+ assert_equal @response, result
79
+
80
+ new_deploy.reload
81
+ new_deploy.status = "success"
82
+ new_deploy.save
83
+
84
+ @api_client.expects(:update_ref).with(@expected_name, @expected_ref_prefix, new_commit.sha).returns(@response)
85
+ @job.perform(@stack)
86
+ end
87
+ end
88
+ end
@@ -35,6 +35,33 @@ module Shipit
35
35
  end
36
36
  end
37
37
 
38
+ test '.create_from_github handle commits with empty message' do
39
+ assert_difference -> { Commit.count }, +1 do
40
+ @stack.commits.create_from_github!(
41
+ resource(
42
+ sha: '2adaad1ad30c235d3a6e7981dfc1742f7ecb1e85',
43
+ author: {},
44
+ committer: {},
45
+ commit: {
46
+ author: {
47
+ name: 'Lando Walrussian',
48
+ email: 'walrus@shopify.com',
49
+ date: Time.now,
50
+ },
51
+ committer: {
52
+ name: 'Lando Walrussian',
53
+ email: 'walrus@shopify.com',
54
+ date: Time.now,
55
+ },
56
+ message: '',
57
+ },
58
+ ),
59
+ )
60
+ end
61
+ commit = Commit.last
62
+ refute_predicate commit, :revert?
63
+ end
64
+
38
65
  test '.create_from_github handle PRs merged by another Shipit stacks' do
39
66
  assert_difference -> { Commit.count }, +1 do
40
67
  @stack.commits.create_from_github!(
@@ -247,7 +274,7 @@ module Shipit
247
274
 
248
275
  test "#creating a commit update the undeployed_commits_count" do
249
276
  walrus = shipit_users(:walrus)
250
- assert_equal 1, @stack.undeployed_commits_count
277
+ assert_equal 2, @stack.undeployed_commits_count
251
278
  @stack.commits.create!(
252
279
  author: walrus,
253
280
  committer: walrus,
@@ -417,6 +444,66 @@ module Shipit
417
444
  assert_predicate commit, :deployable?
418
445
  end
419
446
 
447
+ test "#lock sets the lock author and sets the locked flag" do
448
+ user = shipit_users(:shipit)
449
+
450
+ @commit.lock(user)
451
+
452
+ assert_predicate(@commit, :locked?)
453
+ assert_equal(user, @commit.lock_author)
454
+ end
455
+
456
+ test "#lock does not set the lock author if the user is anonymous" do
457
+ user = Shipit::AnonymousUser.new
458
+
459
+ @commit.lock(user)
460
+
461
+ assert_predicate(@commit, :locked?)
462
+ assert_nil(@commit.lock_author_id)
463
+ end
464
+
465
+ test ".lock_all sets the lock author and sets the locked flag" do
466
+ user = shipit_users(:shipit)
467
+
468
+ Commit.where(id: [@commit.id]).lock_all(user)
469
+
470
+ @commit.reload
471
+ assert_predicate(@commit, :locked?)
472
+ assert_equal(user, @commit.lock_author)
473
+ end
474
+
475
+ test ".lock_all does not set the lock author if the user is anonymous" do
476
+ user = Shipit::AnonymousUser.new
477
+
478
+ Commit.where(id: [@commit.id]).lock_all(user)
479
+
480
+ @commit.reload
481
+ assert_predicate(@commit, :locked?)
482
+ assert_nil(@commit.lock_author_id)
483
+ end
484
+
485
+ test "#lock_author defaults to AnonymousUser" do
486
+ assert_nil(@commit.lock_author_id)
487
+ assert_kind_of(Shipit::AnonymousUser, @commit.lock_author)
488
+
489
+ user = shipit_users(:shipit)
490
+ @commit.lock(user)
491
+
492
+ assert_kind_of(Shipit::User, @commit.lock_author)
493
+ end
494
+
495
+ test "#unlock clears the lock author and resets the locked flag" do
496
+ user = shipit_users(:shipit)
497
+ @commit.lock(user)
498
+ assert_predicate(@commit, :locked?)
499
+ assert_equal(user, @commit.lock_author)
500
+
501
+ @commit.unlock
502
+
503
+ refute_predicate(@commit, :locked?)
504
+ assert_nil(@commit.lock_author_id)
505
+ end
506
+
420
507
  expected_webhook_transitions = { # we expect deployable_status to fire on these transitions, and not on any others
421
508
  'unknown' => %w(pending success failure error),
422
509
  'pending' => %w(success failure error),
@@ -323,7 +323,11 @@ module Shipit
323
323
  file = Pathname.new('/tmp/fake_setup.py')
324
324
  file.write('foo')
325
325
  @spec.stubs(:setup_dot_py).returns(file)
326
- steps = ['assert-egg-version-tag /tmp/fake_setup.py', 'python setup.py register sdist upload']
326
+ steps = [
327
+ 'assert-egg-version-tag /tmp/fake_setup.py',
328
+ 'python setup.py register sdist',
329
+ 'twine upload dist/*',
330
+ ]
327
331
  assert_equal steps, @spec.deploy_steps
328
332
  end
329
333
 
@@ -732,6 +736,7 @@ module Shipit
732
736
 
733
737
  test 'lerna monorepos have a checklist' do
734
738
  @spec.stubs(:lerna?).returns(true).at_least_once
739
+ @spec.stubs(:lerna_config).returns('lerna' => '2.0.0', 'version' => '1.0.0')
735
740
  assert_match(/lerna publish --skip-npm/, @spec.review_checklist[0])
736
741
  end
737
742
 
@@ -744,10 +749,18 @@ module Shipit
744
749
  file = Pathname.new('/tmp/fake_lerna.json')
745
750
  file.write('{"version": "1.0.0-beta.1"}')
746
751
 
747
- @spec.expects(:lerna_json).returns(file)
752
+ @spec.expects(:lerna_json).at_least_once.returns(file)
748
753
  assert_equal '1.0.0-beta.1', @spec.lerna_version
749
754
  end
750
755
 
756
+ test '#lerna_lerna returns the lerna version specified' do
757
+ file = Pathname.new('/tmp/fake_lerna.json')
758
+ file.write('{"version": "1.0.0-beta.1", "lerna": "3.13.3"}')
759
+
760
+ @spec.expects(:lerna_json).at_least_once.returns(file)
761
+ assert_equal Gem::Version.new('3.13.3'), @spec.lerna_lerna
762
+ end
763
+
751
764
  test '#package_version returns the version number' do
752
765
  file = Pathname.new('/tmp/fake_package.json')
753
766
  file.write('{"version": "1.0.0-beta.1"}')
@@ -780,18 +793,32 @@ module Shipit
780
793
 
781
794
  test '#publish_lerna_packages checks if independent version tags exist, and then invokes lerna deploy script' do
782
795
  @spec.stubs(:lerna?).returns(true)
783
- @spec.stubs(:lerna_version).returns('independent')
796
+ @spec.stubs(:lerna_config).returns('lerna' => '2.0.0', 'version' => 'independent')
784
797
  assert_equal 'assert-lerna-independent-version-tags', @spec.deploy_steps[0]
785
798
  assert_equal 'publish-lerna-independent-packages', @spec.deploy_steps[1]
786
799
  end
787
800
 
788
801
  test '#publish_lerna_packages checks if fixed version tag exists, and then invokes lerna deploy script' do
789
802
  @spec.stubs(:lerna?).returns(true)
790
- @spec.stubs(:lerna_version).returns('1.0.0')
803
+ @spec.stubs(:lerna_config).returns('lerna' => '2.0.0', 'version' => '1.0.0')
791
804
  assert_equal 'assert-lerna-fixed-version-tag', @spec.deploy_steps[0]
792
805
  assert_equal 'node_modules/.bin/lerna publish --yes --skip-git --repo-version 1.0.0 --force-publish=* --npm-tag latest --npm-client=npm --skip-npm=false', @spec.deploy_steps[1]
793
806
  end
794
807
 
808
+ test '#publish_lerna_packages checks if a newer version of lerna is used, and will then use the new publish syntax, correctly setting the `latest` dist tag' do
809
+ @spec.stubs(:lerna?).returns(true)
810
+ @spec.stubs(:lerna_config).returns('lerna' => '3.0.0', 'version' => '1.0.0')
811
+ assert_equal 'assert-lerna-fixed-version-tag', @spec.deploy_steps[0]
812
+ assert_equal 'node_modules/.bin/lerna publish from-git --yes --dist-tag latest', @spec.deploy_steps[1]
813
+ end
814
+
815
+ test '#publish_lerna_packages checks if a newer version of lerna is used, and will then use the new publish syntax, correctly setting the `next` dist tag' do
816
+ @spec.stubs(:lerna?).returns(true)
817
+ @spec.stubs(:lerna_config).returns('lerna' => '3.0.0', 'version' => 'v1.3.1-alpha.2')
818
+ assert_equal 'assert-lerna-fixed-version-tag', @spec.deploy_steps[0]
819
+ assert_equal 'node_modules/.bin/lerna publish from-git --yes --dist-tag next', @spec.deploy_steps[1]
820
+ end
821
+
795
822
  test '#enforce_publish_config? is false when Shipit.enforce_publish_config is nil' do
796
823
  Shipit.stubs(:enforce_publish_config).returns(nil)
797
824
  refute @spec.enforce_publish_config?
@@ -829,7 +856,8 @@ module Shipit
829
856
  end
830
857
 
831
858
  test '#valid_publish_config? is true when shipit does not enforce a publishConfig' do
832
- @spec.stubs(:lerna_version).returns('1.0.0')
859
+ @spec.stubs(:lerna?).returns(true)
860
+ @spec.stubs(:lerna_config).returns('lerna' => '2.0.0', 'version' => '1.0.0')
833
861
  assert @spec.valid_publish_config?
834
862
  end
835
863
 
@@ -946,7 +974,7 @@ module Shipit
946
974
 
947
975
  test '#publish_lerna_packages guesses npm tag' do
948
976
  @spec.stubs(:lerna?).returns(true)
949
- @spec.stubs(:lerna_version).returns('1.0.0-alpha.1')
977
+ @spec.stubs(:lerna_config).returns('lerna' => '2.0.0', 'version' => '1.0.0-alpha.1')
950
978
  assert_match(/--npm-tag next/, @spec.deploy_steps.last)
951
979
  end
952
980
 
@@ -23,6 +23,16 @@ module Shipit
23
23
  assert_raise(RuntimeError) { Deploy.new.enqueue }
24
24
  end
25
25
 
26
+ test "run_now! when not persisted" do
27
+ assert_raise(RuntimeError) { Deploy.new.run_now! }
28
+ end
29
+
30
+ test "run_now! runs in foreground" do
31
+ PerformTaskJob.any_instance.expects(:perform).once
32
+
33
+ @deploy.run_now!
34
+ end
35
+
26
36
  test "working_directory" do
27
37
  assert_equal File.join(@deploy.stack.deploys_path, @deploy.id.to_s), @deploy.working_directory
28
38
  end
@@ -235,6 +245,13 @@ module Shipit
235
245
  end
236
246
  end
237
247
 
248
+ test "transitioning to success enqueues a last-deployed git reference update" do
249
+ @deploy = shipit_deploys(:shipit_running)
250
+ assert_enqueued_with(job: UpdateGithubLastDeployedRefJob, args: [@deploy.stack]) do
251
+ @deploy.complete!
252
+ end
253
+ end
254
+
238
255
  test "transitions to any state updates last deploy time to stack record" do
239
256
  @deploy = shipit_deploys(:shipit_running)
240
257
  @deploy.complete!
@@ -321,12 +338,289 @@ module Shipit
321
338
  assert_equal shipit_commits(:second), @stack.last_deployed_commit
322
339
  end
323
340
 
341
+ def create_test_stack
342
+ Shipit::Stack.create(
343
+ repo_owner: "shopify-test",
344
+ repo_name: "shipit-engine-test",
345
+ environment: 'production',
346
+ branch: "master",
347
+ merge_queue_enabled: true,
348
+ created_at: "2019-01-01 00:00:00",
349
+ updated_at: "2019-01-02 10:10:10",
350
+ )
351
+ end
352
+
353
+ def create_test_commit(stack_id:, user_id:)
354
+ Shipit::Commit.new(
355
+ stack_id: stack_id,
356
+ author_id: user_id,
357
+ sha: SecureRandom.hex(20),
358
+ additions: 2,
359
+ deletions: 0,
360
+ committer_id: user_id,
361
+ message: "Some commit message.",
362
+ authored_at: "2019-01-02 10:11:10",
363
+ committed_at: "2019-01-02 10:11:10",
364
+ )
365
+ end
366
+
367
+ def create_test_status(commit_id:, stack_id:, state: "success")
368
+ Shipit::Status.new(
369
+ description: "Description for commit #{commit_id}",
370
+ stack_id: stack_id,
371
+ commit_id: commit_id,
372
+ state: state,
373
+ )
374
+ end
375
+
376
+ def create_test_deploy(stack_id:, user_id:, since_commit_id:, until_commit_id: since_commit_id)
377
+ Shipit::Deploy.new(
378
+ stack_id: stack_id,
379
+ user_id: user_id,
380
+ since_commit_id: since_commit_id,
381
+ until_commit_id: until_commit_id,
382
+ status: "success",
383
+ type: "Shipit::Deploy",
384
+ )
385
+ end
386
+
387
+ # For test purposes, we want to fail fast if a series of records are given sequential ids.
388
+ # Check that the next item in the series is 1 greater than the last.
389
+ def assert_generated_record_ids_are_sequential(record_id_series)
390
+ record_id_series[0..-2].each_with_index do |id_element, index|
391
+ assert_equal id_element + 1, record_id_series[index + 1]
392
+ end
393
+ end
394
+
395
+ def generate_commits(amount:, stack_id:, user_id:, validate:)
396
+ commit_ids = []
397
+ amount.times do
398
+ commit = create_test_commit(stack_id: stack_id, user_id: user_id)
399
+ commit.save
400
+ commit.reload
401
+ commit_ids << commit.id
402
+ end
403
+
404
+ if validate then assert_generated_record_ids_are_sequential(commit_ids) end
405
+ commit_ids
406
+ end
407
+
324
408
  test "#trigger_revert rolls the stack back to before this deploy" do
325
- assert_equal shipit_commits(:fourth), @stack.last_deployed_commit
326
- rollback = @deploy.trigger_revert
409
+ user_id = @user.id
410
+ test_stack = create_test_stack
411
+ test_stack.save
412
+ test_stack.reload
413
+ stack_id = test_stack.id
414
+
415
+ # Create valid commit history for the stack. We need several commits to deploy and roll back through.
416
+ commit_ids = generate_commits(amount: 4, stack_id: stack_id, user_id: user_id, validate: true)
417
+ commit_ids.each { |commit_id| create_test_status(commit_id: commit_id, stack_id: stack_id, state: "success").save }
418
+
419
+ # Three deploys of commits 1-2, 2-3 and 3-4 respectively. Reverting last should result in Deploy 3 (commit 3) being latest.
420
+ 3.times { |index| create_test_deploy(stack_id: stack_id, user_id: user_id, since_commit_id: commit_ids[index], until_commit_id: commit_ids[index + 1]).save }
421
+
422
+ # Get the reference with Rails-mutated field values.
423
+ commit3 = Shipit::Commit.second_to_last
424
+ commit4 = Shipit::Commit.last
425
+ deploy3 = Shipit::Deploy.last
426
+
427
+ assert_equal commit4, test_stack.last_deployed_commit
428
+
429
+ deploy_to_revert = test_stack.deploys.last
430
+
431
+ assert_equal deploy3, deploy_to_revert
432
+
433
+ rollback = deploy_to_revert.trigger_revert
327
434
  rollback.run!
328
435
  rollback.complete!
329
- assert_equal shipit_commits(:first), @stack.last_deployed_commit
436
+
437
+ last_deploy = test_stack.last_completed_deploy
438
+
439
+ assert_equal commit3, test_stack.last_deployed_commit
440
+ assert_equal commit3, last_deploy.until_commit
441
+ assert_equal "Shipit::Rollback", last_deploy.type
442
+ end
443
+
444
+ test "#trigger_revert skips unsuccessful deploys when reverting" do
445
+ user_id = @user.id
446
+ test_stack = create_test_stack
447
+ test_stack.save
448
+ test_stack.reload
449
+ stack_id = test_stack.id
450
+
451
+ # Create valid commit history for the stack. We need several commits to deploy and roll back through.
452
+ commit_ids = generate_commits(amount: 4, stack_id: stack_id, user_id: user_id, validate: true)
453
+ commit_ids.each { |commit_id| create_test_status(commit_id: commit_id, stack_id: stack_id, state: "success").save }
454
+
455
+ # We want the following order of Deploys:
456
+ # 1. Success (commits 1-2)
457
+ # 2. Faulty (commits 2-3)
458
+ # 3. Rollback to Success (-> commits 1-2)
459
+ # 4. Running (commits 3-4)
460
+ # 5. Reversion of the running deploy to the last successful deploy. (-> commits 1-2, i.e. the successful deploy.)
461
+
462
+ deploy1 = create_test_deploy(stack_id: stack_id, user_id: user_id, since_commit_id: commit_ids[0], until_commit_id: commit_ids[1])
463
+ deploy1.save
464
+
465
+ deploy2 = create_test_deploy(stack_id: stack_id, user_id: user_id, since_commit_id: commit_ids[1], until_commit_id: commit_ids[2])
466
+ deploy2.status = "faulty"
467
+ deploy2.save
468
+
469
+ rollback = test_stack.deploys.second_to_last.trigger_rollback(@user)
470
+ rollback.run!
471
+ rollback.complete!
472
+
473
+ assert_equal commit_ids[1], test_stack.last_deployed_commit.id
474
+
475
+ deploy3 = create_test_deploy(stack_id: stack_id, user_id: user_id, since_commit_id: commit_ids[2], until_commit_id: commit_ids[3])
476
+ deploy3.status = "running"
477
+ deploy3.rollback_once_aborted = false
478
+ deploy3.save
479
+
480
+ running_deploy = deploy3.reload
481
+ running_deploy.abort!(aborted_by: @user)
482
+ running_deploy.reload
483
+
484
+ assert_equal "error", running_deploy.status
485
+
486
+ final_rollback = running_deploy.trigger_revert
487
+ final_rollback.run!
488
+ final_rollback.complete!
489
+
490
+ last_deploy = test_stack.last_completed_deploy
491
+
492
+ # The rollback deploy should be from the last commit until the second commit.
493
+ assert_equal "success", last_deploy.status
494
+ assert_equal "Shipit::Rollback", last_deploy.type
495
+ assert_equal commit_ids[-1], last_deploy.since_commit_id
496
+ assert_equal commit_ids[1], last_deploy.until_commit_id
497
+ assert_equal commit_ids[1], test_stack.last_deployed_commit.id
498
+ end
499
+
500
+ test "#trigger_revert skips non-deploy tasks when rolling back" do
501
+ # The revert functionality should only consider Shipit::Deploy and Shipit::Rollback when selecting a target to roll back to for the user.
502
+ # But it is possible for other task types to be defined, so we want to ensure that they are properly skipped, as we can't know whether they are 'valid' to roll back to.
503
+ user_id = @user.id
504
+ test_stack = create_test_stack
505
+ test_stack.save
506
+ test_stack.reload
507
+ stack_id = test_stack.id
508
+
509
+ # Create valid commit history for the stack. We need several commits to deploy and roll back through.
510
+ commit_ids = generate_commits(amount: 4, stack_id: stack_id, user_id: user_id, validate: true)
511
+ commit_ids.each { |commit_id| create_test_status(commit_id: commit_id, stack_id: stack_id, state: "success").save }
512
+
513
+ # We want the following order of Deploys:
514
+ # 1. Success (commits 1-2)
515
+ # 2. Success, but type is not of deploy (commits 2-3)
516
+ # 3. Running (commits 3-4)
517
+ # 4. Reversion of the running deploy to the last successful deploy. (-> commits 1-2, i.e. the successful deploy.)
518
+ # If the revert functionality doesn't restrict to deploys and rollbacks, then commit 3 will be latest deployed when the reversion is done.
519
+
520
+ deploy1 = create_test_deploy(stack_id: stack_id, user_id: user_id, since_commit_id: commit_ids[0], until_commit_id: commit_ids[1])
521
+ deploy1.save
522
+
523
+ deploy2 = create_test_deploy(stack_id: stack_id, user_id: user_id, since_commit_id: commit_ids[1], until_commit_id: commit_ids[2])
524
+ deploy2.type = "Shipit::Fake"
525
+ deploy2.save
526
+
527
+ deploy3 = create_test_deploy(stack_id: stack_id, user_id: user_id, since_commit_id: commit_ids[2], until_commit_id: commit_ids[3])
528
+ deploy3.status = "running"
529
+ deploy3.rollback_once_aborted = false
530
+ deploy3.save
531
+
532
+ running_deploy = deploy3.reload
533
+ running_deploy.abort!(aborted_by: @user)
534
+ running_deploy.reload
535
+
536
+ rollback = running_deploy.trigger_revert
537
+ rollback.run!
538
+ rollback.complete!
539
+
540
+ last_deploy = test_stack.last_completed_deploy
541
+ assert_equal "success", last_deploy.status
542
+ assert_equal "Shipit::Rollback", last_deploy.type
543
+ assert_equal commit_ids[-1], last_deploy.since_commit_id
544
+ assert_equal commit_ids[1], last_deploy.until_commit_id
545
+ assert_equal commit_ids[1], test_stack.last_deployed_commit.id
546
+ end
547
+
548
+ test "#trigger_revert skips deploys from other stacks" do
549
+ # The revert functionality should only consider Shipit::Deploy and Shipit::Rollback's from the same stack when selecting a target to roll back to for the user.
550
+ # But deploys and commits from other stacks can have ids between the current and the previous of this stack, so we want a test to ensure that the deploy
551
+ # from the correct stack is selected.
552
+ user_id = @user.id
553
+ test_stack = create_test_stack
554
+ test_stack.save
555
+ other_stack = create_test_stack
556
+ other_stack.repo_name += "_other"
557
+ other_stack.save
558
+ other_stack.reload
559
+ stack_id = test_stack.id
560
+
561
+ # Create valid commit history for the stack. We need several commits to deploy and roll back through.
562
+ commit_ids = generate_commits(amount: 4, stack_id: stack_id, user_id: user_id, validate: true)
563
+ commit_ids.each { |commit_id| create_test_status(commit_id: commit_id, stack_id: stack_id, state: "success").save }
564
+
565
+ # We want the following order of Deploys:
566
+ # 1. Success (commits 1-2)
567
+ # 2. Success, but belongs to a different stack (commits 2-3)
568
+ # 3. Running (commits 3-4)
569
+ # 4. Reversion of the running deploy to the last successful deploy of the same stack. (-> commits 1-2, i.e. the successful deploy.)
570
+ # If the revert functionality doesn't restrict to the correct stack, then commit 3 will be latest deployed when the reversion is done.
571
+
572
+ deploy1 = create_test_deploy(stack_id: stack_id, user_id: user_id, since_commit_id: commit_ids[0], until_commit_id: commit_ids[1])
573
+ deploy1.save
574
+
575
+ deploy2 = create_test_deploy(stack_id: other_stack.id, user_id: user_id, since_commit_id: commit_ids[1], until_commit_id: commit_ids[2])
576
+ deploy2.save
577
+
578
+ deploy3 = create_test_deploy(stack_id: stack_id, user_id: user_id, since_commit_id: commit_ids[2], until_commit_id: commit_ids[3])
579
+ deploy3.status = "running"
580
+ deploy3.rollback_once_aborted = false
581
+ deploy3.save
582
+
583
+ running_deploy = deploy3.reload
584
+ running_deploy.abort!(aborted_by: @user)
585
+ running_deploy.reload
586
+
587
+ rollback = running_deploy.trigger_revert
588
+ rollback.run!
589
+ rollback.complete!
590
+
591
+ last_deploy = test_stack.last_completed_deploy
592
+ assert_equal "success", last_deploy.status
593
+ assert_equal "Shipit::Rollback", last_deploy.type
594
+ assert_equal commit_ids[-1], last_deploy.since_commit_id
595
+ assert_equal commit_ids[1], last_deploy.until_commit_id
596
+ assert_equal commit_ids[1], test_stack.last_deployed_commit.id
597
+ end
598
+
599
+ test "#trigger_revert no prior deploy to roll back to" do
600
+ user_id = @user.id
601
+ test_stack = create_test_stack
602
+ test_stack.save
603
+ test_stack.reload
604
+ stack_id = test_stack.id
605
+
606
+ commit_ids = generate_commits(amount: 2, stack_id: stack_id, user_id: user_id, validate: true)
607
+ commit_ids.each { |commit_id| create_test_status(commit_id: commit_id, stack_id: stack_id, state: "success").save }
608
+ deploy1 = create_test_deploy(stack_id: stack_id, user_id: user_id, since_commit_id: commit_ids[0], until_commit_id: commit_ids[1])
609
+ deploy1.save
610
+
611
+ rollback = deploy1.trigger_revert
612
+ rollback.run!
613
+ rollback.complete!
614
+
615
+ assert_equal deploy1.until_commit_id, rollback.since_commit_id
616
+ assert_equal deploy1.since_commit_id, rollback.until_commit_id
617
+
618
+ last_deploy = test_stack.last_completed_deploy
619
+ assert_equal "success", last_deploy.status
620
+ assert_equal "Shipit::Rollback", last_deploy.type
621
+ assert_equal deploy1.until_commit_id, last_deploy.since_commit_id
622
+ assert_equal deploy1.since_commit_id, last_deploy.until_commit_id
623
+ assert_equal deploy1.since_commit_id, test_stack.last_deployed_commit.id
330
624
  end
331
625
 
332
626
  test "#trigger_rollback creates a new Rollback" do
@@ -525,7 +819,7 @@ module Shipit
525
819
  @deploy = shipit_deploys(:canaries_running)
526
820
  @deploy.ping
527
821
 
528
- assert_difference -> { ReleaseStatus.count }, +3 do
822
+ assert_difference -> { ReleaseStatus.count }, +2 do
529
823
  assert_equal 'running', @deploy.status
530
824
  assert_not_equal 'failure', @deploy.last_release_status.state
531
825
 
@@ -545,16 +839,24 @@ module Shipit
545
839
 
546
840
  test "manually triggered rollbacks sets the release status as failure" do
547
841
  @deploy = shipit_deploys(:canaries_validating)
842
+ @middle_deploy = shipit_deploys(:canaries_faulty)
843
+ @rollback_to_deploy = shipit_deploys(:canaries_success)
548
844
 
549
- assert_difference -> { ReleaseStatus.count }, +1 do
845
+ assert_difference -> { ReleaseStatus.count }, +2 do
550
846
  assert_equal 'validating', @deploy.status
551
847
  assert_equal 'pending', @deploy.last_release_status.state
552
848
 
553
- @deploy.trigger_rollback(force: true)
849
+ @rollback_to_deploy.trigger_rollback(force: true)
850
+ @rollback_to_deploy.reload
554
851
  @deploy.reload
555
852
 
556
853
  assert_equal 'faulty', @deploy.status
557
854
  assert_equal 'failure', @deploy.last_release_status.state
855
+
856
+ assert_equal 'faulty', @middle_deploy.status
857
+ assert_equal 'failure', @middle_deploy.last_release_status.state
858
+
859
+ assert_equal 'success', @rollback_to_deploy.status
558
860
  end
559
861
  end
560
862