shipit-engine 0.20.0 → 0.21.0

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.
Files changed (110) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +43 -6
  3. data/app/assets/stylesheets/_base/_base.scss +4 -0
  4. data/app/assets/stylesheets/_pages/_commits.scss +3 -1
  5. data/app/assets/stylesheets/_pages/_deploy.scss +4 -2
  6. data/app/controllers/concerns/shipit/authentication.rb +1 -1
  7. data/app/controllers/shipit/api/base_controller.rb +6 -1
  8. data/app/controllers/shipit/api/pull_requests_controller.rb +1 -1
  9. data/app/controllers/shipit/commit_checks_controller.rb +1 -1
  10. data/app/controllers/shipit/shipit_controller.rb +1 -5
  11. data/app/controllers/shipit/stacks_controller.rb +2 -0
  12. data/app/controllers/shipit/tasks_controller.rb +1 -1
  13. data/app/controllers/shipit/webhooks_controller.rb +2 -2
  14. data/app/helpers/shipit/deploys_helper.rb +9 -0
  15. data/app/helpers/shipit/shipit_helper.rb +17 -15
  16. data/app/helpers/shipit/stacks_helper.rb +6 -1
  17. data/app/jobs/shipit/destroy_stack_job.rb +4 -2
  18. data/app/jobs/shipit/fetch_deployed_revision_job.rb +1 -1
  19. data/app/jobs/shipit/github_sync_job.rb +1 -1
  20. data/app/jobs/shipit/merge_pull_requests_job.rb +3 -3
  21. data/app/jobs/shipit/perform_task_job.rb +3 -0
  22. data/app/jobs/shipit/purge_old_deliveries_job.rb +1 -0
  23. data/app/models/shipit/api_client.rb +1 -1
  24. data/app/models/shipit/commit.rb +29 -6
  25. data/app/models/shipit/commit_deployment.rb +1 -1
  26. data/app/models/shipit/commit_deployment_status.rb +1 -1
  27. data/app/models/shipit/deploy_spec/bundler_discovery.rb +1 -10
  28. data/app/models/shipit/deploy_spec/file_system.rb +6 -0
  29. data/app/models/shipit/deploy_spec/kubernetes_discovery.rb +1 -1
  30. data/app/models/shipit/deploy_spec/lerna_discovery.rb +85 -0
  31. data/app/models/shipit/deploy_spec/npm_discovery.rb +103 -5
  32. data/app/models/shipit/deploy_spec/pypi_discovery.rb +4 -2
  33. data/app/models/shipit/deploy_spec/rubygems_discovery.rb +4 -2
  34. data/app/models/shipit/deploy_spec.rb +19 -2
  35. data/app/models/shipit/duration.rb +1 -1
  36. data/app/models/shipit/github_status.rb +1 -1
  37. data/app/models/shipit/hook.rb +4 -5
  38. data/app/models/shipit/output_chunk.rb +1 -1
  39. data/app/models/shipit/pull_request.rb +37 -16
  40. data/app/models/shipit/stack.rb +17 -11
  41. data/app/models/shipit/status/common.rb +4 -0
  42. data/app/models/shipit/status/group.rb +4 -0
  43. data/app/models/shipit/task.rb +20 -8
  44. data/app/models/shipit/task_definition.rb +2 -2
  45. data/app/models/shipit/undeployed_commit.rb +13 -2
  46. data/app/models/shipit/user.rb +1 -1
  47. data/app/serializers/shipit/pull_request_serializer.rb +1 -1
  48. data/app/serializers/shipit/tail_task_serializer.rb +1 -1
  49. data/app/views/shipit/ccmenu/project.xml.builder +9 -8
  50. data/app/views/shipit/deploys/_deploy.html.erb +3 -2
  51. data/app/views/shipit/stacks/_banners.html.erb +4 -1
  52. data/app/views/shipit/stacks/settings.html.erb +4 -0
  53. data/app/views/shipit/statuses/_group.html.erb +1 -1
  54. data/app/views/shipit/statuses/_status.html.erb +1 -1
  55. data/app/views/shipit/tasks/_task.html.erb +1 -2
  56. data/config/locales/en.yml +2 -0
  57. data/config/secrets.development.example.yml +0 -4
  58. data/config/secrets.development.shopify.yml +1 -5
  59. data/db/migrate/20170904103242_reindex_deliveries.rb +7 -0
  60. data/db/migrate/20171120161420_add_base_info_to_pull_request.rb +7 -0
  61. data/db/migrate/20180202220850_add_aborted_by_to_tasks.rb +5 -0
  62. data/lib/shipit/command.rb +11 -3
  63. data/lib/shipit/engine.rb +0 -4
  64. data/lib/shipit/stack_commands.rb +3 -1
  65. data/lib/shipit/version.rb +1 -1
  66. data/lib/shipit.rb +15 -23
  67. data/lib/snippets/assert-lerna-fixed-version-tag +21 -0
  68. data/lib/snippets/assert-lerna-independent-version-tags +28 -0
  69. data/lib/snippets/generate-local-npmrc +19 -0
  70. data/lib/snippets/misconfigured-npm-publish-config +8 -0
  71. data/lib/snippets/publish-lerna-independent-packages +39 -0
  72. data/lib/snippets/push-to-heroku +5 -5
  73. data/lib/tasks/cron.rake +1 -1
  74. data/lib/tasks/dev.rake +1 -1
  75. data/test/controllers/api/deploys_controller_test.rb +19 -0
  76. data/test/controllers/api/stacks_controller_test.rb +1 -1
  77. data/test/controllers/github_authentication_controller_test.rb +1 -1
  78. data/test/controllers/stacks_controller_test.rb +10 -0
  79. data/test/controllers/tasks_controller_test.rb +2 -0
  80. data/test/controllers/webhooks_controller_test.rb +0 -7
  81. data/test/dummy/config/secrets.yml +0 -2
  82. data/test/dummy/db/development.sqlite3 +0 -0
  83. data/test/dummy/db/schema.rb +5 -3
  84. data/test/dummy/db/test.sqlite3 +0 -0
  85. data/test/fixtures/shipit/commits.yml +53 -0
  86. data/test/fixtures/shipit/pull_requests.yml +52 -0
  87. data/test/fixtures/shipit/stacks.yml +35 -0
  88. data/test/fixtures/shipit/statuses.yml +27 -0
  89. data/test/fixtures/shipit/tasks.yml +14 -0
  90. data/test/helpers/queries_helper.rb +1 -1
  91. data/test/jobs/merge_pull_requests_job_test.rb +19 -2
  92. data/test/jobs/perform_task_job_test.rb +26 -2
  93. data/test/models/commits_test.rb +55 -6
  94. data/test/models/deploy_spec_test.rb +288 -52
  95. data/test/models/deploys_test.rb +7 -7
  96. data/test/models/hook_test.rb +7 -1
  97. data/test/models/pull_request_test.rb +78 -24
  98. data/test/models/stacks_test.rb +23 -19
  99. data/test/models/status/group_test.rb +6 -0
  100. data/test/models/undeployed_commits_test.rb +9 -0
  101. data/test/models/users_test.rb +2 -2
  102. data/test/test_helper.rb +1 -1
  103. metadata +211 -224
  104. data/app/assets/javascripts/shipit_bs.js.coffee +0 -2
  105. data/app/assets/stylesheets/shipit_bs.scss +0 -22
  106. data/app/views/bootstrap/shipit/missing_settings.html.erb +0 -97
  107. data/app/views/bootstrap/shipit/stacks/new.html.erb +0 -44
  108. data/app/views/layouts/shipit_bootstrap.html.erb +0 -44
  109. data/lib/shipit/template_renderer_extension.rb +0 -16
  110. data/test/dummy/db/test.sqlite3-journal +0 -0
@@ -284,11 +284,16 @@ module Shipit
284
284
  'require' => [],
285
285
  'ignore' => [],
286
286
  'revalidate_after' => nil,
287
+ 'max_divergence' => {
288
+ 'commits' => nil,
289
+ 'age' => nil,
290
+ },
287
291
  },
288
292
  'ci' => {
289
293
  'hide' => [],
290
294
  'allow_failures' => [],
291
295
  'require' => [],
296
+ 'blocking' => [],
292
297
  },
293
298
  'machine' => {'environment' => {}, 'directory' => nil, 'cleanup' => true},
294
299
  'review' => {'checklist' => [], 'monitoring' => [], 'checks' => []},
@@ -318,59 +323,19 @@ module Shipit
318
323
  assert_equal 'SAFETY_DISABLED', variable_definition.name
319
324
  end
320
325
 
321
- test "task definitions prepend bundle exec by default" do
322
- @spec.expects(:load_config).returns('tasks' => {'restart' => {'steps' => %w(foo)}})
323
- @spec.expects(:bundler?).returns(true).at_least_once
324
- assert_deprecated(/Automatically prepending `bundle exec`/) do
325
- definition = @spec.find_task_definition('restart')
326
- assert_equal ['bundle exec foo'], definition.steps
327
- end
328
- end
329
-
330
- test "task definitions prepend bundle exec if enabled" do
331
- Shipit.expects(:automatically_prepend_bundle_exec).returns(true).at_least_once
326
+ test "task definitions don't prepend bundle exec by default" do
332
327
  @spec.expects(:load_config).returns('tasks' => {'restart' => {'steps' => %w(foo)}})
333
- @spec.expects(:bundler?).returns(true).at_least_once
334
328
  definition = @spec.find_task_definition('restart')
335
-
336
- assert_equal ['bundle exec foo'], definition.steps
337
- end
338
-
339
- test "task definitions do not prepend bundle exec if disabled" do
340
- Shipit.expects(:automatically_prepend_bundle_exec).returns(false).at_least_once
341
- @spec.expects(:load_config).returns('tasks' => {'restart' => {'steps' => %w(foo)}})
342
- definition = @spec.find_task_definition('restart')
343
-
344
329
  assert_equal ['foo'], definition.steps
345
330
  end
346
331
 
347
- test "task definitions do not prepend bundle exec if the task already does" do
348
- Shipit.expects(:automatically_prepend_bundle_exec).returns(true).at_least_once
349
- @spec.expects(:load_config).returns('tasks' => {'restart' => {'steps' => ['bundle exec foo']}})
350
- @spec.stubs(:bundler?).returns(true)
351
- definition = @spec.find_task_definition('restart')
352
-
353
- assert_equal ['bundle exec foo'], definition.steps
354
- end
355
-
356
- test "task definitions do not prepend bundle exec if depedency step is overridden" do
357
- @spec.expects(:load_config).returns(
358
- 'dependencies' => {'override' => []},
359
- 'tasks' => {'restart' => {'steps' => %w(foo)}},
360
- )
361
- @spec.expects(:bundler?).returns(true).at_least_once
362
- definition = @spec.find_task_definition('restart')
363
-
364
- assert_equal ['foo'], definition.steps
365
- end
366
-
367
- test "task definitions prepend bundle exec before serialization" do
332
+ test "task definitions don't bundle exec before serialization" do
368
333
  @spec.expects(:discover_task_definitions).returns('restart' => {'steps' => %w(foo)})
369
334
  @spec.expects(:bundler?).returns(true).at_least_once
370
335
 
371
336
  cached_spec = DeploySpec.load(DeploySpec.dump(@spec))
372
337
  definition = cached_spec.find_task_definition('restart')
373
- assert_equal ['bundle exec foo'], definition.steps
338
+ assert_equal ['foo'], definition.steps
374
339
  end
375
340
 
376
341
  test "#task_definitions returns kubernetes commands as well as comands from the config" do
@@ -455,6 +420,16 @@ module Shipit
455
420
  assert_equal %w(ci/circleci ci/jenkins), @spec.hidden_statuses
456
421
  end
457
422
 
423
+ test "#required_statuses automatically includes #blocking_statuses" do
424
+ @spec.expects(:load_config).returns(
425
+ 'ci' => {
426
+ 'require' => %w(ci/circleci),
427
+ 'blocking' => %w(soc/compliance),
428
+ },
429
+ )
430
+ assert_equal %w(ci/circleci soc/compliance), @spec.required_statuses
431
+ end
432
+
458
433
  test "pull_request_ignored_statuses defaults to the union of ci.hide and ci.allow_failures" do
459
434
  @spec.expects(:load_config).returns(
460
435
  'ci' => {
@@ -573,26 +548,43 @@ module Shipit
573
548
  assert_equal ['fake gem task'], @spec.deploy_steps
574
549
  end
575
550
 
551
+ test 'lerna monorepos take priority over solo npm deploys' do
552
+ @spec.expects(:discover_npm_package).never
553
+ @spec.stubs(:discover_lerna_packages).returns(['fake monorepo task']).once
554
+
555
+ assert_equal ['fake monorepo task'], @spec.deploy_steps
556
+ end
557
+
558
+ test '#lerna? is false if there is no lerna.json' do
559
+ @spec.expects(:lerna_json).returns(Shipit::Engine.root.join("tmp-#{SecureRandom.hex}"))
560
+ refute @spec.lerna?
561
+ end
562
+
576
563
  test '#npm? is false if there is no package.json' do
577
564
  @spec.expects(:package_json).returns(Shipit::Engine.root.join("tmp-#{SecureRandom.hex}"))
578
565
  refute @spec.npm?
579
566
  end
580
567
 
581
- test '#npm? is false if npm package is private' do
568
+ test '#npm? is true if npm package is public' do
582
569
  file = Pathname.new('/tmp/fake_package.json')
583
- file.write('{"private": true}')
570
+ file.write('{"private": false}')
584
571
 
585
572
  @spec.expects(:package_json).returns(file)
586
- refute @spec.npm?
573
+
574
+ assert @spec.npm?
587
575
  end
588
576
 
589
- test '#npm? is true if npm package is public' do
577
+ test '#npm? is false if npm package is private' do
590
578
  file = Pathname.new('/tmp/fake_package.json')
591
- file.write('{"private": false}')
579
+ file.write('{"private": true}')
592
580
 
593
581
  @spec.expects(:package_json).returns(file)
582
+ refute @spec.npm?
583
+ end
594
584
 
595
- assert @spec.npm?
585
+ test 'lerna monorepos have a checklist' do
586
+ @spec.stubs(:lerna?).returns(true).at_least_once
587
+ assert_match(/lerna publish --skip-npm/, @spec.review_checklist[0])
596
588
  end
597
589
 
598
590
  test 'npm packages have a checklist' do
@@ -600,14 +592,223 @@ module Shipit
600
592
  assert_match(/npm version/, @spec.review_checklist[0])
601
593
  end
602
594
 
595
+ test '#lerna_version returns the monorepo root version number' do
596
+ file = Pathname.new('/tmp/fake_lerna.json')
597
+ file.write('{"version": "1.0.0-beta.1"}')
598
+
599
+ @spec.expects(:lerna_json).returns(file)
600
+ assert_equal '1.0.0-beta.1', @spec.lerna_version
601
+ end
602
+
603
+ test '#package_version returns the version number' do
604
+ file = Pathname.new('/tmp/fake_package.json')
605
+ file.write('{"version": "1.0.0-beta.1"}')
606
+
607
+ @spec.expects(:package_json).returns(file)
608
+ assert_equal '1.0.0-beta.1', @spec.package_version
609
+ end
610
+
611
+ test '#dist_tag returns "latest" if the version does not contains a standard pre-release tag' do
612
+ assert_equal 'latest', @spec.dist_tag('1.0.0')
613
+ assert_equal 'latest', @spec.dist_tag('1.0.0-shopifyv4')
614
+ end
615
+
616
+ test '#dist_tag returns "next" if the version contains a pre-release tag' do
617
+ assert_equal 'next', @spec.dist_tag('1.0.0-alpha.1')
618
+ assert_equal 'next', @spec.dist_tag('1.0.0-beta')
619
+ assert_equal 'next', @spec.dist_tag('1.0.0-rc.3')
620
+ assert_equal 'next', @spec.dist_tag('1.0.0-next')
621
+ end
622
+
623
+ test '#dependencies_steps returns lerna setup if a `lerna.json` is present' do
624
+ @spec.expects(:lerna?).returns(true).at_least_once
625
+ assert_equal ['npm install --no-progress', 'node_modules/.bin/lerna bootstrap'], @spec.dependencies_steps
626
+ end
627
+
603
628
  test '#dependencies_steps returns `npm install` if a `package.json` is present' do
604
629
  @spec.expects(:npm?).returns(true).at_least_once
605
630
  assert_equal ['npm install --no-progress'], @spec.dependencies_steps
606
631
  end
607
632
 
633
+ test '#publish_lerna_packages checks if independent version tags exist, and then invokes lerna deploy script' do
634
+ @spec.stubs(:lerna?).returns(true)
635
+ @spec.stubs(:lerna_version).returns('independent')
636
+ assert_equal 'assert-lerna-independent-version-tags', @spec.deploy_steps[0]
637
+ assert_equal 'publish-lerna-independent-packages', @spec.deploy_steps[1]
638
+ end
639
+
640
+ test '#publish_lerna_packages checks if fixed version tag exists, and then invokes lerna deploy script' do
641
+ @spec.stubs(:lerna?).returns(true)
642
+ @spec.stubs(:lerna_version).returns('1.0.0')
643
+ assert_equal 'assert-lerna-fixed-version-tag', @spec.deploy_steps[0]
644
+ assert_equal 'node_modules/.bin/lerna publish --yes --skip-git --repo-version 1.0.0 --force-publish=* --npm-tag latest', @spec.deploy_steps[1]
645
+ end
646
+
647
+ test '#enforce_publish_config? is false when Shipit.enforce_publish_config is nil' do
648
+ Shipit.stubs(:enforce_publish_config).returns(nil)
649
+ refute @spec.enforce_publish_config?
650
+ end
651
+
652
+ test '#enforce_publish_config? is false when Shipit.enforce_publish_config is 0' do
653
+ Shipit.stubs(:enforce_publish_config).returns('0')
654
+ refute @spec.enforce_publish_config?
655
+ end
656
+
657
+ test '#enforce_publish_config? is true when Shipit.enforce_publish_config is 1' do
658
+ Shipit.stubs(:enforce_publish_config).returns('1')
659
+ assert @spec.enforce_publish_config?
660
+ end
661
+
662
+ test '#valid_publish_config? is false when enforce_publish_config? is true and publishConfig is missing from package.json' do
663
+ Shipit.stubs(:private_npm_registry).returns('some_private_registry')
664
+ @spec.stubs(:enforce_publish_config?).returns(true)
665
+ @spec.stubs(:publish_config_access).returns('restricted')
666
+
667
+ package_json = Pathname.new('/tmp/fake_package.json')
668
+ package_json.write('{"name": "foo"}')
669
+
670
+ @spec.expects(:package_json).returns(package_json)
671
+ refute @spec.valid_publish_config?
672
+ end
673
+
674
+ test '#valid_publish_config? is true when enforce_publish_config? is true and publishConfig.access is public' do
675
+ Shipit.stubs(:private_npm_registry).returns('some_private_registry')
676
+ @spec.stubs(:enforce_publish_config?).returns(true)
677
+ @spec.stubs(:publish_config_access).returns('public')
678
+ @spec.stubs(:publish_config).returns('something')
679
+
680
+ assert @spec.valid_publish_config?
681
+ end
682
+
683
+ test '#valid_publish_config? is true when shipit does not enforce a publishConfig' do
684
+ @spec.stubs(:lerna_version).returns('1.0.0')
685
+ assert @spec.valid_publish_config?
686
+ end
687
+
688
+ test '#publish_config returns publishConfig from package.json' do
689
+ package_json = Pathname.new('/tmp/fake_package.json')
690
+ package_json.write('{"publishConfig": "foo"}')
691
+
692
+ @spec.expects(:package_json).returns(package_json)
693
+ assert_equal "foo", @spec.publish_config
694
+ end
695
+
696
+ test '#valid_publish_config_access? is false when publishConfig.access is invalid' do
697
+ @spec.stubs(:publish_config_access).returns('foo')
698
+ refute @spec.valid_publish_config_access?
699
+ end
700
+
701
+ test '#valid_publish_config_access? is true when publishConfig.access is public or restricted' do
702
+ @spec.stubs(:publish_config_access).returns('public')
703
+ assert @spec.valid_publish_config_access?
704
+
705
+ @spec.stubs(:publish_config_access).returns('restricted')
706
+ assert @spec.valid_publish_config_access?
707
+ end
708
+
709
+ test '#publish_config_access is restricted when enforce_publish_config? is true and publishConfig is missing' do
710
+ package_json = Pathname.new('/tmp/fake_package.json')
711
+ package_json.write('{"name": "@shopify/foo"}')
712
+
713
+ @spec.stubs(:enforce_publish_config?).returns(true)
714
+ @spec.expects(:package_json).returns(package_json)
715
+ assert_equal 'restricted', @spec.publish_config_access
716
+ end
717
+
718
+ test '#publish_config_access is public when enforce_publish_config? is false and publishConfig is missing' do
719
+ package_json = Pathname.new('/tmp/fake_package.json')
720
+ package_json.write('{"name": "@shopify/foo"}')
721
+
722
+ @spec.stubs(:enforce_publish_config?).returns(false)
723
+ @spec.expects(:package_json).returns(package_json)
724
+ assert_equal 'public', @spec.publish_config_access
725
+ end
726
+
727
+ test '#publish_config_access returns publishConfig.access from package.json when enforce_publish_config? is true' do
728
+ package_json = Pathname.new('/tmp/fake_package.json')
729
+ package_json.write('{
730
+ "name": "@shopify/foo",
731
+ "publishConfig": {
732
+ "access": "foo"
733
+ }
734
+ }')
735
+
736
+ @spec.stubs(:enforce_publish_config?).returns(false)
737
+ @spec.expects(:package_json).returns(package_json)
738
+ assert_equal 'foo', @spec.publish_config_access
739
+ end
740
+
741
+ test "#scoped_package? is false when Shipit.npm_org_scope is not set and the package is private" do
742
+ Shipit.stubs(:npm_org_scope).returns(nil)
743
+ @spec.stubs(:publish_config_access).returns('restricted')
744
+ refute @spec.scoped_package?
745
+ end
746
+
747
+ test "#scoped_package? is true when Shipit.npm_org_scope is set and package_name starts with scope and the package is private" do
748
+ Shipit.stubs(:npm_org_scope).returns('@shopify')
749
+ @spec.stubs(:publish_config_access).returns('restricted')
750
+ @spec.stubs(:package_name).returns('@shopify/polaris')
751
+ assert @spec.scoped_package?
752
+ end
753
+
754
+ test "#private_scoped_package? is false when private packages are not scoped" do
755
+ @spec.stubs(:scoped_package?).returns(false)
756
+ @spec.stubs(:publish_config_access).returns("restricted")
757
+ refute @spec.private_scoped_package?
758
+ end
759
+
760
+ test "#private_scoped_package? is true when private packages are scoped" do
761
+ @spec.stubs(:scoped_package?).returns(true)
762
+ @spec.stubs(:publish_config_access).returns("restricted")
763
+ assert @spec.private_scoped_package?
764
+ end
765
+
608
766
  test '#publish_npm_package checks if version tag exists, and then invokes npm deploy script' do
609
767
  @spec.stubs(:npm?).returns(true)
610
- assert_equal ['assert-npm-version-tag', 'npm publish'], @spec.deploy_steps
768
+ @spec.stubs(:package_version).returns('1.0.0')
769
+ @spec.stubs(:valid_publish_config?).returns(true)
770
+ @spec.stubs(:publish_config_access).returns('restricted')
771
+ @spec.stubs(:registry).returns("@private:registry=some_private_registry")
772
+ assert_equal ['assert-npm-version-tag', 'npm publish --tag latest --access restricted'], @spec.deploy_steps
773
+ end
774
+
775
+ test '#npmrc_contents returns a scoped private package configuration when the package is scoped and private' do
776
+ registry = "@shopify:registry=some_private_registry"
777
+ Shipit.stubs(:npm_org_scope).returns('@shopify')
778
+ Shipit.stubs(:private_npm_registry).returns('some_private_registry')
779
+ @spec.stubs(:scoped_package?).returns(true)
780
+ @spec.stubs(:publish_config_access).returns('restricted')
781
+ assert_equal registry, @spec.registry
782
+ end
783
+
784
+ test '#npmrc_contents returns a public scoped package configuration when the package is scoped and public' do
785
+ registry = "@shopify:registry=https://registry.npmjs.org/"
786
+ Shipit.stubs(:npm_org_scope).returns('@shopify')
787
+ @spec.stubs(:scoped_package?).returns(true)
788
+ @spec.stubs(:publish_config_access).returns('public')
789
+ assert_equal registry, @spec.registry
790
+ end
791
+
792
+ test '#npmrc_contents returns a public non-scoped package configuration when the package is not scoped and public' do
793
+ registry = "registry=https://registry.npmjs.org/"
794
+ @spec.stubs(:scoped_package?).returns(false)
795
+ @spec.stubs(:publish_config_access).returns('public')
796
+ assert_equal registry, @spec.registry
797
+ end
798
+
799
+ test '#publish_lerna_packages guesses npm tag' do
800
+ @spec.stubs(:lerna?).returns(true)
801
+ @spec.stubs(:lerna_version).returns('1.0.0-alpha.1')
802
+ assert_match(/--npm-tag next/, @spec.deploy_steps.last)
803
+ end
804
+
805
+ test '#publish_npm_package checks if version tag and a pre-release flag exist, and then invokes npm deploy script' do
806
+ @spec.stubs(:npm?).returns(true)
807
+ @spec.stubs(:package_version).returns('1.0.0-alpha.1')
808
+ @spec.stubs(:valid_publish_config?).returns(true)
809
+ @spec.stubs(:publish_config_access).returns('restricted')
810
+ @spec.stubs(:registry).returns("@private:registry=some_private_registry")
811
+ assert_equal ['assert-npm-version-tag', 'npm publish --tag next --access restricted'], @spec.deploy_steps
611
812
  end
612
813
 
613
814
  test 'bundler installs take priority over yarn installs' do
@@ -663,14 +864,49 @@ module Shipit
663
864
  assert_equal ['yarn install --no-progress'], @spec.dependencies_steps
664
865
  end
665
866
 
666
- test '#publish_yarn_package checks if version tag exists, and then invokes npm publish script' do
867
+ test '#publish_npm_package checks if version tag exists, and then invokes npm publish script' do
667
868
  @spec.stubs(:yarn?).returns(true).at_least_once
668
- assert_equal ['assert-npm-version-tag', 'npm publish'], @spec.deploy_steps
869
+ @spec.stubs(:package_version).returns('1.0.0')
870
+ @spec.stubs(:valid_publish_config?).returns(true)
871
+ @spec.stubs(:publish_config_access).returns('restricted')
872
+ @spec.stubs(:registry).returns("@private:registry=some_private_registry")
873
+ assert_equal ['assert-npm-version-tag', 'npm publish --tag latest --access restricted'], @spec.deploy_steps
874
+ end
875
+
876
+ test '#publish_npm_package checks if version tag exists, generates npmrc, and then invokes npm publish script when enforce_publish_config? is true' do
877
+ @spec.stubs(:yarn?).returns(true).at_least_once
878
+ @spec.stubs(:package_version).returns('1.0.0')
879
+ @spec.stubs(:valid_publish_config?).returns(true)
880
+
881
+ @spec.stubs(:publish_config_access).returns('restricted')
882
+ @spec.stubs(:enforce_publish_config?).returns(true)
883
+ @spec.stubs(:registry).returns('fake')
884
+
885
+ generate_npmrc = 'generate-local-npmrc "fake"'
886
+ npm_publish = 'npm publish --tag latest --access restricted'
887
+ deploy_steps = ['assert-npm-version-tag', generate_npmrc, npm_publish]
888
+ assert_equal deploy_steps, @spec.deploy_steps
669
889
  end
670
890
 
671
891
  test 'yarn checklist takes precedence over npm checklist' do
672
892
  @spec.stubs(:yarn?).returns(true).at_least_once
673
893
  assert_match(/yarn version/, @spec.review_checklist[0])
674
894
  end
895
+
896
+ test "max_divergence_commits defaults to `nil" do
897
+ @spec.expects(:load_config).returns({})
898
+ assert_nil @spec.max_divergence_commits
899
+ end
900
+
901
+ test "max_divergence_age defaults to `nil` if `merge.max_divergence.age` cannot be parsed" do
902
+ @spec.expects(:load_config).returns(
903
+ 'merge' => {
904
+ 'max_divergence' => {
905
+ 'age' => 'badbadbad',
906
+ },
907
+ },
908
+ )
909
+ assert_nil @spec.max_divergence_age
910
+ end
675
911
  end
676
912
  end
@@ -229,7 +229,7 @@ module Shipit
229
229
  @deploy = shipit_deploys(:shipit_running)
230
230
  @deploy.complete!
231
231
  @stack.reload
232
- assert_in_delta @deploy.ended_at, @stack.last_deployed_at, 1
232
+ assert_in_delta @deploy.ended_at, @stack.last_deployed_at, 2
233
233
  end
234
234
 
235
235
  test "transitioning to success schedule a MergePullRequests job" do
@@ -264,7 +264,7 @@ module Shipit
264
264
  @deploy = shipit_deploys(:shipit_running)
265
265
  @deploy.ping
266
266
  @deploy.pid = 42
267
- @deploy.abort!(rollback_once_aborted: true)
267
+ @deploy.abort!(rollback_once_aborted: true, aborted_by: @user)
268
268
 
269
269
  assert_difference -> { @stack.rollbacks.count }, 1 do
270
270
  assert_enqueued_with(job: PerformTaskJob) do
@@ -346,12 +346,12 @@ module Shipit
346
346
 
347
347
  test "abort! transition to `aborting`" do
348
348
  @deploy.ping
349
- @deploy.abort!
349
+ @deploy.abort!(aborted_by: @user)
350
350
  assert_equal 'aborting', @deploy.status
351
351
  end
352
352
 
353
353
  test "abort! schedule the rollback if `rollback_once_aborted` is true" do
354
- @deploy.abort!(rollback_once_aborted: true)
354
+ @deploy.abort!(rollback_once_aborted: true, aborted_by: @user)
355
355
  assert_predicate @deploy.reload, :rollback_once_aborted?
356
356
  end
357
357
 
@@ -359,11 +359,11 @@ module Shipit
359
359
  @deploy.ping
360
360
  aborts = []
361
361
 
362
- @deploy.abort!
362
+ @deploy.abort!(aborted_by: @user)
363
363
  @deploy.should_abort? { |abort_count| aborts << abort_count }
364
364
  assert_equal [1], aborts
365
365
 
366
- 3.times { @deploy.abort! }
366
+ 3.times { @deploy.abort!(aborted_by: @user) }
367
367
  @deploy.should_abort? { |abort_count| aborts << abort_count }
368
368
  assert_equal [1, 2, 3, 4], aborts
369
369
  end
@@ -373,7 +373,7 @@ module Shipit
373
373
  refute_predicate @deploy, :alive?
374
374
  refute_predicate @deploy, :finished?
375
375
 
376
- @deploy.abort!
376
+ @deploy.abort!(aborted_by: @user)
377
377
  assert_predicate @deploy, :error?
378
378
  end
379
379
 
@@ -58,10 +58,16 @@ module Shipit
58
58
 
59
59
  test "#purge_old_deliveries!" do
60
60
  Hook.deliver(:deploy, @stack, 'foo' => 42)
61
+ @hook.deliveries.update_all(status: 'sent')
61
62
 
62
- assert_difference -> { Delivery.count }, -1 do
63
+ previous_ids = @hook.deliveries.sent.order(id: :desc).pluck(:id)
64
+
65
+ assert_difference -> { @hook.deliveries.sent.count }, -1 do
63
66
  @hook.purge_old_deliveries!(keep: 1)
64
67
  end
68
+
69
+ after_ids = @hook.deliveries.sent.order(id: :desc).pluck(:id)
70
+ assert_equal previous_ids[0..-2], after_ids
65
71
  end
66
72
  end
67
73
  end
@@ -29,7 +29,7 @@ module Shipit
29
29
  PullRequest.request_merge!(@stack, @pr.number, @user)
30
30
  assert_predicate @pr.reload, :pending?
31
31
  assert_not_equal original_merge_requested_at, @pr.merge_requested_at
32
- assert_in_delta Time.now.utc, @pr.merge_requested_at, 1
32
+ assert_in_delta Time.now.utc, @pr.merge_requested_at, 2
33
33
  end
34
34
 
35
35
  test ".request_merge! retry rejected pull requests" do
@@ -39,7 +39,7 @@ module Shipit
39
39
  PullRequest.request_merge!(@stack, @pr.number, @user)
40
40
  assert_predicate @pr.reload, :pending?
41
41
  assert_not_equal original_merge_requested_at, @pr.merge_requested_at
42
- assert_in_delta Time.now.utc, @pr.merge_requested_at, 1
42
+ assert_in_delta Time.now.utc, @pr.merge_requested_at, 2
43
43
  assert_nil @pr.rejection_reason
44
44
  end
45
45
 
@@ -68,6 +68,7 @@ module Shipit
68
68
  pull_request = shipit_pull_requests(:shipit_fetching)
69
69
 
70
70
  head_sha = '64b3833d39def7ec65b57b42f496eb27ab4980b6'
71
+ base_sha = 'ba7ab50e02286f7d6c60c1ef75258133dd9ea763'
71
72
  Shipit.github_api.expects(:pull_request).with(@stack.github_repo_name, pull_request.number).returns(
72
73
  stub(
73
74
  id: 4_857_578,
@@ -82,6 +83,10 @@ module Shipit
82
83
  ref: 'super-branch',
83
84
  sha: head_sha,
84
85
  ),
86
+ base: stub(
87
+ ref: 'default-branch',
88
+ sha: base_sha,
89
+ ),
85
90
  ),
86
91
  )
87
92
 
@@ -91,22 +96,25 @@ module Shipit
91
96
  name: 'Bob the Builder',
92
97
  email: 'bob@bob.com',
93
98
  )
94
- Shipit.github_api.expects(:commit).with(@stack.github_repo_name, head_sha).returns(
95
- stub(
96
- sha: head_sha,
97
- author: author,
98
- committer: author,
99
- commit: stub(
100
- message: 'Great feature',
101
- author: stub(date: 1.day.ago),
102
- committer: stub(date: 1.day.ago),
103
- ),
104
- stats: stub(
105
- additions: 24,
106
- deletions: 5,
99
+
100
+ [head_sha, base_sha].each do |sha|
101
+ Shipit.github_api.expects(:commit).with(@stack.github_repo_name, sha).returns(
102
+ stub(
103
+ sha: sha,
104
+ author: author,
105
+ committer: author,
106
+ commit: stub(
107
+ message: 'Great feature',
108
+ author: stub(date: 1.day.ago),
109
+ committer: stub(date: 1.day.ago),
110
+ ),
111
+ stats: stub(
112
+ additions: 24,
113
+ deletions: 5,
114
+ ),
107
115
  ),
108
- ),
109
- )
116
+ )
117
+ end
110
118
 
111
119
  Shipit.github_api.expects(:statuses).with(@stack.github_repo_name, head_sha).returns([stub(
112
120
  state: 'success',
@@ -156,21 +164,30 @@ module Shipit
156
164
  assert_equal 'merge_conflict', @pr.rejection_reason
157
165
  end
158
166
 
159
- test "#reject_unless_mergeable! rejects the PR if it has a failing or pending CI status" do
160
- @pr.head.statuses.create!(stack: @pr.stack, state: 'pending', context: 'ci/circle')
167
+ test "#reject_unless_mergeable! rejects the PR if it has a failing CI status" do
168
+ @pr.head.statuses.create!(stack: @pr.stack, state: 'failure', context: 'ci/circle')
161
169
 
162
170
  refute_predicate @pr, :all_status_checks_passed?
171
+ assert_predicate @pr, :any_status_checks_failed?
163
172
  assert_equal true, @pr.reject_unless_mergeable!
164
173
  assert_predicate @pr, :rejected?
165
174
  assert_equal 'ci_failing', @pr.rejection_reason
166
175
  end
167
176
 
168
- test "#merge! revalidates the PR if it has been enqueued for too long" do
169
- @pr.update!(revalidated_at: 5.hours.ago)
177
+ test "#reject_unless_mergeable! does not reject the PR if it has a pending CI status" do
178
+ @pr.head.statuses.create!(stack: @pr.stack, state: 'pending', context: 'ci/circle')
170
179
 
171
- assert_predicate @pr, :need_revalidation?
172
- assert_equal false, @pr.merge!
173
- assert_predicate @pr, :revalidating?
180
+ refute_predicate @pr, :all_status_checks_passed?
181
+ refute_predicate @pr, :any_status_checks_failed?
182
+ assert_equal false, @pr.reject_unless_mergeable!
183
+ refute_predicate @pr, :rejected?
184
+ end
185
+
186
+ test "#reject_unless_mergeable! rejects the PR if it is stale" do
187
+ @pr.stubs(:stale?).returns(true)
188
+ assert_equal true, @pr.reject_unless_mergeable!
189
+ assert_predicate @pr, :rejected?
190
+ assert_equal 'requires_rebase', @pr.rejection_reason
174
191
  end
175
192
 
176
193
  test "#merge! raises a PullRequest::NotReady if the PR isn't mergeable yet" do
@@ -211,5 +228,42 @@ module Shipit
211
228
  @stack.reload
212
229
  end
213
230
  end
231
+
232
+ test "#all_status_checks_passed? returns false when head commit is unknown" do
233
+ @pr.update(head_id: nil)
234
+ refute_predicate @pr, :all_status_checks_passed?
235
+ end
236
+
237
+ test "#stale? returns false by default" do
238
+ refute_predicate @pr, :stale?
239
+ end
240
+
241
+ test "#stale? returns true when the branch falls behind the maximum commits" do
242
+ @pr.base_commit = shipit_commits(:second)
243
+ @pr.base_ref = 'default-branch'
244
+ Shipit.github_api.expects(:compare).with(@stack.github_repo_name, @pr.base_ref, @pr.head.sha).returns(
245
+ stub(
246
+ behind_by: 10,
247
+ ),
248
+ )
249
+ spec = {'merge' => {'max_divergence' => {'commits' => 1}}}
250
+ @pr.stack.cached_deploy_spec = DeploySpec.new(spec)
251
+ assert_predicate @pr, :stale?
252
+ end
253
+
254
+ test "#stale? returns true when the branch falls behind the maximum age" do
255
+ @pr.base_commit = shipit_commits(:second)
256
+ @pr.head.committed_at = 2.hours.ago
257
+ spec = {'merge' => {'max_divergence' => {'age' => '1h'}}}
258
+ @pr.stack.cached_deploy_spec = DeploySpec.new(spec)
259
+ assert_predicate @pr, :stale?
260
+ end
261
+
262
+ test "#stale? is false when base_commit information is missing" do
263
+ @pr.base_commit = nil
264
+ spec = {'merge' => {'max_divergence' => {'age' => '1h', 'commits' => 10}}}
265
+ @pr.stack.cached_deploy_spec = DeploySpec.new(spec)
266
+ refute_predicate @pr, :stale?
267
+ end
214
268
  end
215
269
  end