shipit-engine 0.33.0 → 0.34.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +13 -2
  3. data/app/assets/stylesheets/_pages/_deploy.scss +0 -2
  4. data/app/controllers/shipit/api/ccmenu_controller.rb +1 -1
  5. data/app/controllers/shipit/api/deploys_controller.rb +2 -0
  6. data/app/controllers/shipit/api/rollbacks_controller.rb +2 -1
  7. data/app/controllers/shipit/api/stacks_controller.rb +1 -0
  8. data/app/controllers/shipit/deploys_controller.rb +1 -1
  9. data/app/controllers/shipit/stacks_controller.rb +2 -2
  10. data/app/controllers/shipit/tasks_controller.rb +2 -2
  11. data/app/controllers/shipit/webhooks_controller.rb +23 -4
  12. data/app/helpers/shipit/shipit_helper.rb +0 -1
  13. data/app/jobs/shipit/deliver_hook_job.rb +1 -1
  14. data/app/jobs/shipit/github_sync_job.rb +13 -9
  15. data/app/jobs/shipit/update_github_last_deployed_ref_job.rb +1 -1
  16. data/app/models/shipit/anonymous_user.rb +6 -2
  17. data/app/models/shipit/check_run.rb +36 -0
  18. data/app/models/shipit/commit.rb +20 -9
  19. data/app/models/shipit/commit_checks.rb +13 -13
  20. data/app/models/shipit/commit_deployment.rb +3 -3
  21. data/app/models/shipit/commit_deployment_status.rb +3 -3
  22. data/app/models/shipit/deploy.rb +16 -11
  23. data/app/models/shipit/deploy_spec/lerna_discovery.rb +12 -4
  24. data/app/models/shipit/duration.rb +2 -0
  25. data/app/models/shipit/hook.rb +26 -2
  26. data/app/models/shipit/merge_request.rb +9 -7
  27. data/app/models/shipit/pull_request.rb +1 -1
  28. data/app/models/shipit/release_status.rb +1 -1
  29. data/app/models/shipit/repository.rb +9 -3
  30. data/app/models/shipit/review_stack.rb +16 -2
  31. data/app/models/shipit/stack.rb +59 -25
  32. data/app/models/shipit/status/group.rb +1 -1
  33. data/app/models/shipit/task.rb +6 -2
  34. data/app/models/shipit/task_execution_strategy/default.rb +4 -5
  35. data/app/models/shipit/team.rb +4 -2
  36. data/app/models/shipit/user.rb +4 -0
  37. data/app/models/shipit/webhooks/handlers/pull_request/review_stack_adapter.rb +1 -1
  38. data/app/models/shipit/webhooks/handlers/push_handler.rb +4 -1
  39. data/app/serializers/shipit/merge_request_serializer.rb +1 -1
  40. data/app/validators/subset_validator.rb +1 -1
  41. data/app/views/layouts/merge_status.html.erb +1 -1
  42. data/app/views/shipit/stacks/_banners.html.erb +2 -1
  43. data/app/views/shipit/stacks/new.html.erb +1 -1
  44. data/config/secrets.development.example.yml +24 -0
  45. data/config/secrets.development.shopify.yml +20 -9
  46. data/db/migrate/20210325194053_remove_stacks_branch_default.rb +5 -0
  47. data/db/migrate/20210504200438_add_github_updated_at_to_check_runs.rb +5 -0
  48. data/lib/shipit.rb +39 -15
  49. data/lib/shipit/command.rb +7 -6
  50. data/lib/shipit/commands.rb +9 -2
  51. data/lib/shipit/engine.rb +2 -0
  52. data/lib/shipit/flock.rb +8 -1
  53. data/lib/shipit/github_app.rb +7 -5
  54. data/lib/shipit/octokit_iterator.rb +3 -3
  55. data/lib/shipit/simple_message_verifier.rb +2 -2
  56. data/lib/shipit/stack_commands.rb +28 -4
  57. data/lib/shipit/task_commands.rb +6 -0
  58. data/lib/shipit/version.rb +1 -1
  59. data/lib/snippets/publish-lerna-independent-packages +35 -34
  60. data/lib/snippets/publish-lerna-independent-packages-legacy +39 -0
  61. data/test/controllers/api/ccmenu_controller_test.rb +1 -1
  62. data/test/controllers/api/deploys_controller_test.rb +17 -0
  63. data/test/controllers/api/stacks_controller_test.rb +21 -7
  64. data/test/controllers/webhooks_controller_test.rb +26 -11
  65. data/test/dummy/app/assets/config/manifest.js +3 -0
  66. data/test/dummy/config/application.rb +1 -1
  67. data/test/dummy/config/database.yml +9 -0
  68. data/test/dummy/config/environments/development.rb +1 -1
  69. data/test/dummy/config/secrets_double_github_app.yml +79 -0
  70. data/test/dummy/db/schema.rb +5 -4
  71. data/test/dummy/db/seeds.rb +1 -0
  72. data/test/fixtures/payloads/check_suite_master.json +2 -30
  73. data/test/fixtures/payloads/push_master.json +1 -1
  74. data/test/fixtures/payloads/push_not_master.json +1 -1
  75. data/test/fixtures/shipit/commits.yml +2 -2
  76. data/test/fixtures/shipit/hooks.yml +1 -0
  77. data/test/fixtures/shipit/tasks.yml +1 -1
  78. data/test/helpers/json_helper.rb +5 -1
  79. data/test/jobs/github_sync_job_test.rb +2 -1
  80. data/test/models/commit_deployment_status_test.rb +3 -3
  81. data/test/models/commits_test.rb +2 -0
  82. data/test/models/deploy_spec_test.rb +7 -0
  83. data/test/models/deploys_test.rb +18 -0
  84. data/test/models/hook_test.rb +30 -1
  85. data/test/models/merge_request_test.rb +19 -4
  86. data/test/models/shipit/check_run_test.rb +124 -5
  87. data/test/models/shipit/review_stack_test.rb +38 -6
  88. data/test/models/shipit/stacks_test.rb +42 -4
  89. data/test/models/shipit/webhooks/handlers/pull_request/review_stack_adapter_test.rb +24 -0
  90. data/test/models/tasks_test.rb +22 -0
  91. data/test/test_helper.rb +15 -0
  92. data/test/unit/anonymous_user_serializer_test.rb +1 -1
  93. data/test/unit/command_test.rb +5 -0
  94. data/test/unit/commit_serializer_test.rb +1 -1
  95. data/test/unit/deploy_commands_test.rb +70 -14
  96. data/test/unit/deploy_serializer_test.rb +1 -1
  97. data/test/unit/github_app_test.rb +2 -3
  98. data/test/unit/github_apps_test.rb +416 -0
  99. data/test/unit/shipit_deployment_checks_test.rb +77 -0
  100. data/test/unit/shipit_test.rb +14 -0
  101. data/test/unit/user_serializer_test.rb +1 -1
  102. metadata +202 -191
@@ -42,8 +42,9 @@ module Shipit
42
42
 
43
43
  attr_reader :oauth_teams, :domain, :bot_login
44
44
 
45
- def initialize(config)
45
+ def initialize(organization, config)
46
46
  super()
47
+ @organization = organization
47
48
  @config = (config || {}).with_indifferent_access
48
49
  @domain = @config[:domain] || DOMAIN
49
50
  @webhook_secret = @config[:webhook_secret].presence
@@ -92,10 +93,11 @@ module Shipit
92
93
  end
93
94
 
94
95
  def fetch_new_token
96
+ cache_key = @organization.nil? ? '' : "#{@organization.downcase}:"
95
97
  # Rails can add 5 minutes to the cache entry expiration time when any TTL is provided,
96
98
  # so our TTL setting can be lower, and TTL + expires_in should be lower than the GitHub token expiration.
97
99
  Rails.cache.fetch(
98
- 'github:integration:access-token',
100
+ "github:integration:#{cache_key}access-token",
99
101
  expires_in: GITHUB_TOKEN_RAILS_CACHE_LIFETIME,
100
102
  race_condition_ttl: 4.minutes,
101
103
  ) do
@@ -181,15 +183,15 @@ module Shipit
181
183
  end
182
184
 
183
185
  def app_id
184
- @app_id ||= @config.fetch(:app_id)
186
+ @config.fetch(:app_id)
185
187
  end
186
188
 
187
189
  def installation_id
188
- @installation_id ||= @config.fetch(:installation_id)
190
+ @config.fetch(:installation_id)
189
191
  end
190
192
 
191
193
  def private_key
192
- @private_key ||= @config.fetch(:private_key)
194
+ @config.fetch(:private_key)
193
195
  end
194
196
 
195
197
  def authentication_payload
@@ -3,12 +3,12 @@ module Shipit
3
3
  class OctokitIterator
4
4
  include Enumerable
5
5
 
6
- def initialize(relation = nil)
6
+ def initialize(relation = nil, github_api: nil)
7
7
  if relation
8
8
  @response = relation.get(per_page: 100)
9
9
  else
10
- data = yield Shipit.github.api
11
- @response = Shipit.github.api.last_response if data.present?
10
+ data = yield github_api
11
+ @response = github_api.last_response if data.present?
12
12
  end
13
13
  end
14
14
 
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
  module Shipit
3
3
  class SimpleMessageVerifier < ActiveSupport::MessageVerifier
4
- def initialize(secret, options = {})
4
+ def initialize(secret, **options)
5
5
  options[:serializer] ||= ToS
6
- super(secret, options)
6
+ super(secret, **options)
7
7
  end
8
8
 
9
9
  private
@@ -15,19 +15,25 @@ module Shipit
15
15
 
16
16
  def fetch
17
17
  create_directories
18
- if Dir.exist?(@stack.git_path)
18
+ if valid_git_repository?(@stack.git_path)
19
19
  git('fetch', 'origin', '--tags', @stack.branch, env: env, chdir: @stack.git_path)
20
20
  else
21
+ @stack.clear_git_cache!
21
22
  git_clone(@stack.repo_git_url, @stack.git_path, branch: @stack.branch, env: env, chdir: @stack.deploys_path)
22
23
  end
23
24
  end
24
25
 
25
26
  def fetched?(commit)
26
- git_dir = File.join(@stack.git_path, '.git')
27
- if Dir.exist?(git_dir)
27
+ if valid_git_repository?(@stack.git_path)
28
28
  git('rev-parse', '--quiet', '--verify', "#{commit.sha}^{commit}", env: env, chdir: @stack.git_path)
29
29
  else
30
- Command.new('test', '-d', git_dir, env: env, chdir: @stack.deploys_path)
30
+ # When the stack's git cache is not valid, the commit is
31
+ # NOT fetched. To keep the interface of this method
32
+ # consistent, we must return a Shipit::Command whose #success?
33
+ # method returns false - has a non-zero exit status. We utilize
34
+ # the POSIX 'test' command with no arguments which should
35
+ # always have an exit status of 1.
36
+ Command.new('test', env: env, chdir: @stack.deploys_path)
31
37
  end
32
38
  end
33
39
 
@@ -71,6 +77,18 @@ module Shipit
71
77
  end
72
78
  end
73
79
 
80
+ def valid_git_repository?(path)
81
+ path.exist? &&
82
+ !path.empty? &&
83
+ git_cmd_succeeds?(path)
84
+ end
85
+
86
+ def git_cmd_succeeds?(path)
87
+ git("rev-parse", "--git-dir", chdir: path)
88
+ .tap(&:run)
89
+ .success?
90
+ end
91
+
74
92
  def git_clone(url, path, branch: 'master', **kwargs)
75
93
  git('clone', *modern_git_args, '--recursive', '--branch', branch, url, path, **kwargs)
76
94
  end
@@ -83,5 +101,11 @@ module Shipit
83
101
  def create_directories
84
102
  FileUtils.mkdir_p(@stack.deploys_path)
85
103
  end
104
+
105
+ private
106
+
107
+ def github
108
+ Shipit.github(organization: @stack.repository.owner)
109
+ end
86
110
  end
87
111
  end
@@ -88,5 +88,11 @@ module Shipit
88
88
  @task.working_directory
89
89
  end
90
90
  end
91
+
92
+ private
93
+
94
+ def github
95
+ Shipit.github(organization: @stack.repository.owner)
96
+ end
91
97
  end
92
98
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Shipit
3
- VERSION = '0.33.0'
3
+ VERSION = '0.34.0'
4
4
  end
@@ -1,39 +1,40 @@
1
1
  #!/usr/bin/env node
2
- const {resolve} = require('path');
3
- const {execSync} = require('child_process')
4
- const Repository = require(resolve('.', 'node_modules', 'lerna', 'lib', 'Repository'));
5
- const PackageUtilities = require(resolve('.', 'node_modules', 'lerna', 'lib', 'PackageUtilities'));
6
-
7
- function npmTag(version) {
8
- const isNext = ['-beta', '-alpha', '-rc', '-next'].some(distributionType =>
9
- version.includes(distributionType),
10
- );
11
- return isNext ? 'next' : 'latest';
12
- }
2
+ const { execSync } = require("child_process");
13
3
 
14
- const taggedPackages = execSync('git tag --points-at HEAD')
4
+ const taggedPackages = execSync("git tag --points-at HEAD")
15
5
  .toString()
16
6
  .trim()
17
- .split('\n')
18
- .map(tag => {
19
- if (tag.startsWith('@')) {
20
- return `@${tag.split('@')[1]}`;
21
- }
22
- return tag.split('@')[0];
23
- });
24
-
25
- const repository = new Repository('.');
26
- const unsortedPackages = PackageUtilities.getPackages({rootPath: '.', packageConfigs: repository.packageConfigs});
27
- const packages = PackageUtilities.topologicallyBatchPackages(unsortedPackages, true)
28
- .reduce((acc, packageGroup) => [...acc, ...packageGroup], [])
29
- .filter(({name}) => taggedPackages.includes(name));
30
-
31
- packages.forEach(({name, version}) => {
32
- const command = `node_modules/.bin/lerna publish --yes --npm-client=npm --skip-npm=false --skip-git --force-publish=${name} --repo-version=${version} --scope=${name} --npm-tag=${npmTag(
33
- version,
34
- )}`;
35
-
36
- // eslint-disable-next-line no-console
37
- console.log(command);
38
- require('child_process').execSync(command);
7
+ .split("\n");
8
+
9
+ // anything that matches `-` after MAJOR.MINOR.PATCH
10
+ const validPattern = new RegExp(/\d+\.\d+\.\d+-(\w+)/);
11
+
12
+ // Ensure that all releases use the same prerelease suffix.
13
+ // We want to only release all final versions, or all betas etc
14
+ const allPrereleaseSuffixes = taggedPackages.map((version) => {
15
+ const result = validPattern.exec(version);
16
+ return result ? result[1] : '';
39
17
  });
18
+ if ((new Set(allPrereleaseSuffixes)).size > 1) {
19
+ throw Error("All packages must be of the same type of release. Versions cannot be mixed.");
20
+ }
21
+
22
+ // set this using machine.environment.SHIPIT_LERNA_PUBLISH_MECHANISM in your shipit.yml
23
+ const publishMechanism = process.env.SHIPIT_LERNA_PUBLISH_MECHANISM || 'from-git';
24
+
25
+ if (!['from-package', 'from-git'].includes(publishMechanism)) {
26
+ throw Error("SHIPIT_LERNA_PUBLISH_MECHANISM must be 'from-package' or 'from-git'.");
27
+ }
28
+
29
+ const command = [
30
+ "node_modules/.bin/lerna publish",
31
+ publishMechanism,
32
+ "--yes",
33
+ "--dist-tag latest",
34
+ "--pre-dist-tag next",
35
+ ];
36
+
37
+ const commandString = command.join(" ");
38
+
39
+ console.log(`${commandString}`);
40
+ execSync(commandString);
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ const {resolve} = require('path');
3
+ const {execSync} = require('child_process')
4
+ const Repository = require(resolve('.', 'node_modules', 'lerna', 'lib', 'Repository'));
5
+ const PackageUtilities = require(resolve('.', 'node_modules', 'lerna', 'lib', 'PackageUtilities'));
6
+
7
+ function npmTag(version) {
8
+ const isNext = ['-beta', '-alpha', '-rc', '-next'].some(distributionType =>
9
+ version.includes(distributionType),
10
+ );
11
+ return isNext ? 'next' : 'latest';
12
+ }
13
+
14
+ const taggedPackages = execSync('git tag --points-at HEAD')
15
+ .toString()
16
+ .trim()
17
+ .split('\n')
18
+ .map(tag => {
19
+ if (tag.startsWith('@')) {
20
+ return `@${tag.split('@')[1]}`;
21
+ }
22
+ return tag.split('@')[0];
23
+ });
24
+
25
+ const repository = new Repository('.');
26
+ const unsortedPackages = PackageUtilities.getPackages({rootPath: '.', packageConfigs: repository.packageConfigs});
27
+ const packages = PackageUtilities.topologicallyBatchPackages(unsortedPackages, true)
28
+ .reduce((acc, packageGroup) => [...acc, ...packageGroup], [])
29
+ .filter(({name}) => taggedPackages.includes(name));
30
+
31
+ packages.forEach(({name, version}) => {
32
+ const command = `node_modules/.bin/lerna publish --yes --npm-client=npm --skip-npm=false --skip-git --force-publish=${name} --repo-version=${version} --scope=${name} --npm-tag=${npmTag(
33
+ version,
34
+ )}`;
35
+
36
+ // eslint-disable-next-line no-console
37
+ console.log(command);
38
+ require('child_process').execSync(command);
39
+ });
@@ -44,7 +44,7 @@ module Shipit
44
44
  end
45
45
 
46
46
  test "stacks with no deploys render correctly" do
47
- stack = Stack.create!(repository: Repository.new(owner: "foo", name: "bar"))
47
+ stack = Stack.create!(repository: Repository.new(owner: "foo", name: "bar"), branch: 'main')
48
48
  get :show, params: { stack_id: stack.to_param }
49
49
  assert_payload 'lastBuildStatus', 'Success'
50
50
  end
@@ -83,6 +83,23 @@ module Shipit
83
83
  assert_response :conflict
84
84
  end
85
85
 
86
+ test "#create refuses to deploy unsuccessful commits if the require_ci flag is passed" do
87
+ Commit.any_instance.expects(:deployable?).returns(false)
88
+
89
+ assert_no_difference -> { @stack.deploys.count } do
90
+ post :create, params: { stack_id: @stack.to_param, sha: @commit.sha, require_ci: true }
91
+ end
92
+ assert_response :unprocessable_entity
93
+ assert_json 'errors.require_ci', ["Commit is not deployable"]
94
+ end
95
+
96
+ test "#create deploys failing commits if the require_ci flag is not passed" do
97
+ Commit.any_instance.expects(:deployable?).returns(false)
98
+
99
+ post :create, params: { stack_id: @stack.to_param, sha: @commit.sha }
100
+ assert_response :accepted
101
+ end
102
+
86
103
  test "#create refuses to deploy locked stacks" do
87
104
  @stack.update!(lock_reason: 'Something broken')
88
105
 
@@ -23,7 +23,7 @@ module Shipit
23
23
 
24
24
  test "#create fails with invalid stack" do
25
25
  assert_no_difference "Stack.count" do
26
- post :create, params: { repo_owner: 'some', repo_name: 'owner/path' }
26
+ post :create, params: { repo_owner: 'some', repo_name: 'owner/path', branch: 'main' }
27
27
  end
28
28
  assert_response :unprocessable_entity
29
29
  assert_json 'errors', 'repository' => ['is invalid']
@@ -48,12 +48,12 @@ module Shipit
48
48
 
49
49
  assert_no_difference -> { Stack.count } do
50
50
  post :create,
51
- params: {
52
- repo_name: existing_stack.repo_name,
53
- repo_owner: existing_stack.repo_owner,
54
- environment: existing_stack.environment,
55
- branch: existing_stack.branch,
56
- }
51
+ params: {
52
+ repo_name: existing_stack.repo_name,
53
+ repo_owner: existing_stack.repo_owner,
54
+ environment: existing_stack.environment,
55
+ branch: existing_stack.branch,
56
+ }
57
57
  end
58
58
 
59
59
  assert_response :unprocessable_entity
@@ -82,6 +82,20 @@ module Shipit
82
82
  assert_equal true, @stack.continuous_deployment
83
83
  end
84
84
 
85
+ test "#update allows changing the branch name" do
86
+ assert_equal 'master', @stack.branch
87
+
88
+ patch :update, params: {
89
+ id: @stack.to_param,
90
+ branch: 'test',
91
+ }
92
+
93
+ assert_response :ok
94
+ @stack.reload
95
+
96
+ assert_equal 'test', @stack.branch
97
+ end
98
+
85
99
  test "#index returns a list of stacks" do
86
100
  stack = Stack.last
87
101
  get :index
@@ -22,14 +22,15 @@ module Shipit
22
22
  test ":push with the target branch queues a GithubSyncJob" do
23
23
  request.headers['X-Github-Event'] = 'push'
24
24
 
25
+ body = JSON.parse(payload(:push_master)).to_json
25
26
  assert_enqueued_with(job: GithubSyncJob, args: [stack_id: @stack.id]) do
26
- post :create, body: payload(:push_master), as: :json
27
+ post :create, body: body, as: :json
27
28
  end
28
29
  end
29
30
 
30
31
  test ":push does not enqueue a job if not the target branch" do
31
32
  request.headers['X-Github-Event'] = 'push'
32
- params = payload(:push_not_master)
33
+ params = JSON.parse(payload(:push_not_master)).to_json
33
34
  assert_no_enqueued_jobs do
34
35
  post :create, body: params, as: :json
35
36
  end
@@ -40,8 +41,9 @@ module Shipit
40
41
 
41
42
  commit = shipit_commits(:first)
42
43
 
44
+ body = JSON.parse(payload(:status_master)).merge(repository_params).to_json
43
45
  assert_difference 'commit.statuses.count', 1 do
44
- post :create, body: payload(:status_master), as: :json
46
+ post :create, body: body, as: :json
45
47
  end
46
48
 
47
49
  status = commit.statuses.last
@@ -55,14 +57,14 @@ module Shipit
55
57
 
56
58
  test ":state with a unexisting commit respond with 200 OK" do
57
59
  request.headers['X-Github-Event'] = 'status'
58
- params = { 'sha' => 'notarealcommit', 'state' => 'pending', 'branches' => [{ 'name' => 'master' }] }.to_json
60
+ params = { 'sha' => 'notarealcommit', 'state' => 'pending', 'branches' => [{ 'name' => 'master' }] }.merge(repository_params).to_json
59
61
  post :create, body: params, as: :json
60
62
  assert_response :ok
61
63
  end
62
64
 
63
65
  test ":state in an untracked branche bails out" do
64
66
  request.headers['X-Github-Event'] = 'status'
65
- params = { 'sha' => 'notarealcommit', 'state' => 'pending', 'branches' => [] }.to_json
67
+ params = { 'sha' => 'notarealcommit', 'state' => 'pending', 'branches' => [] }.merge(repository_params).to_json
66
68
  post :create, body: params, as: :json
67
69
  assert_response :ok
68
70
  end
@@ -70,8 +72,9 @@ module Shipit
70
72
  test ":check_suite with the target branch queues a RefreshCheckRunsJob" do
71
73
  request.headers['X-Github-Event'] = 'check_suite'
72
74
 
75
+ body = JSON.parse(payload(:check_suite_master)).to_json
73
76
  assert_enqueued_with(job: RefreshCheckRunsJob) do
74
- post :create, body: payload(:check_suite_master), as: :json
77
+ post :create, body: body, as: :json
75
78
  assert_response :ok
76
79
  end
77
80
  end
@@ -88,13 +91,13 @@ module Shipit
88
91
  test "verifies webhook signature" do
89
92
  commit = shipit_commits(:first)
90
93
 
91
- payload = { "sha" => commit.sha, "state" => "pending", "target_url" => "https://ci.example.com/1000/output" }.to_json
94
+ payload = { "sha" => commit.sha, "state" => "pending", "target_url" => "https://ci.example.com/1000/output" }.merge(repository_params).to_json
92
95
  signature = 'sha1=4848deb1c9642cd938e8caa578d201ca359a8249'
93
96
 
94
97
  @request.headers['X-Github-Event'] = 'push'
95
98
  @request.headers['X-Hub-Signature'] = signature
96
99
 
97
- Shipit.github.expects(:verify_webhook_signature).with(signature, payload).returns(false)
100
+ Shipit.github(organization: 'shopify').expects(:verify_webhook_signature).with(signature, payload).returns(false)
98
101
 
99
102
  post :create, body: payload, as: :json
100
103
  assert_response :unprocessable_entity
@@ -157,7 +160,7 @@ module Shipit
157
160
  test "other events trigger custom handlers" do
158
161
  event = 'pull_request'
159
162
  mock_handler = mock
160
- mock_handler.expects(:call).with(pull_request_params.stringify_keys).once
163
+ mock_handler.expects(:call).with(pull_request_params.deep_stringify_keys).once
161
164
  Shipit::Webhooks.handlers["pull_request"] = [mock_handler]
162
165
 
163
166
  @request.headers['X-Github-Event'] = event
@@ -165,20 +168,32 @@ module Shipit
165
168
  assert_response :ok
166
169
  end
167
170
 
171
+ test "events with no handler are dropped" do
172
+ event = 'not_a_real_event'
173
+
174
+ @request.headers['X-Github-Event'] = event
175
+ post :create, body: pull_request_params.to_json, as: :json
176
+ assert_response 204
177
+ end
178
+
168
179
  private
169
180
 
170
181
  def pull_request_params
171
- { action: 'opened', number: 2, pull_request: 'foobar' }
182
+ { action: 'opened', number: 2, pull_request: 'foobar' }.merge(repository_params)
172
183
  end
173
184
 
174
185
  def membership_params
175
- { action: 'added', team: team_params, organization: { login: 'shopify' }, member: { login: 'walrus' } }
186
+ { action: 'added', team: team_params, organization: { login: 'shopify' }, member: { login: 'walrus' } }.merge(repository_params)
176
187
  end
177
188
 
178
189
  def team_params
179
190
  { id: shipit_teams(:shopify_developers).id, slug: 'developers', name: 'Developers', url: 'http://example.com' }
180
191
  end
181
192
 
193
+ def repository_params
194
+ { repository: { owner: { login: 'shopify' } } }
195
+ end
196
+
182
197
  def george
183
198
  stub(
184
199
  id: 42,