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
@@ -11,7 +11,7 @@ module Shipit
11
11
  assert_equal DeploySerializer, serializer
12
12
  serialized = serializer.new(deploy).to_json
13
13
 
14
- assert_json("commits.0.author.name", first_commit_author.name, document: serialized)
14
+ assert_json_document(serialized, "commits.0.author.name", first_commit_author.name)
15
15
  end
16
16
  end
17
17
  end
@@ -18,7 +18,7 @@ module Shipit
18
18
 
19
19
  test "#initialize doesn't raise if given an empty config" do
20
20
  assert_nothing_raised do
21
- GitHubApp.new({})
21
+ GitHubApp.new(nil, {})
22
22
  end
23
23
  end
24
24
 
@@ -183,7 +183,6 @@ module Shipit
183
183
  .any_instance
184
184
  .expects(:create_app_installation_access_token).with(config[:installation_id], anything)
185
185
  .returns(second_token)
186
-
187
186
  first_token = valid_app.token
188
187
 
189
188
  first_cached_token = Rails.cache.fetch(@token_cache_key)
@@ -199,7 +198,7 @@ module Shipit
199
198
  private
200
199
 
201
200
  def app(extra_config = {})
202
- GitHubApp.new(default_config.deep_merge(extra_config))
201
+ GitHubApp.new(nil, default_config.deep_merge(extra_config))
203
202
  end
204
203
 
205
204
  def default_config
@@ -0,0 +1,416 @@
1
+ # frozen_string_literal: true
2
+ require 'test_helper'
3
+
4
+ module Shipit
5
+ class GitHubAppsTestOrgOne < ActiveSupport::TestCase
6
+ setup do
7
+ @organization = "OrgOne"
8
+ @github = app(@organization)
9
+ @enterprise = app(@organization, domain: 'github.example.com')
10
+ @rails_env = Rails.env
11
+ @token_cache_key = "github:integration:#{@organization.downcase}:access-token"
12
+ Rails.cache.delete(@token_cache_key)
13
+ end
14
+
15
+ teardown do
16
+ Rails.env = @rails_env
17
+ Rails.cache.delete(@token_cache_key)
18
+ end
19
+
20
+ test "#domain defaults to github.com" do
21
+ assert_equal 'github.com', @github.domain
22
+ end
23
+
24
+ test "#url returns the HTTPS url to the github installation" do
25
+ assert_equal 'https://github.example.com', @enterprise.url
26
+ assert_equal 'https://github.example.com/foo/bar', @enterprise.url('/foo/bar')
27
+ assert_equal 'https://github.example.com/foo/bar/baz', @enterprise.url('foo/bar', 'baz')
28
+ end
29
+
30
+ test "#new_client retruns an Octokit::Client configured to use the github installation" do
31
+ assert_equal 'https://github.example.com/', @enterprise.new_client.web_endpoint
32
+ assert_equal 'https://github.example.com/api/v3/', @enterprise.new_client.api_endpoint
33
+ end
34
+
35
+ test "#oauth_config.last[:client_options] is nil if domain is not overriden" do
36
+ assert_nil @github.oauth_config.last[:client_options][:site]
37
+ end
38
+
39
+ test "#oauth_config.last[:client_options] returns Enterprise endpoint if domain is overriden" do
40
+ assert_equal 'https://github.example.com/api/v3/', @enterprise.oauth_config.last[:client_options][:site]
41
+ end
42
+
43
+ test "#initialize doesn't raise if given an empty config" do
44
+ assert_nothing_raised do
45
+ GitHubApp.new(@organization, {})
46
+ end
47
+ end
48
+
49
+ test "#api_status" do
50
+ stub_request(:get, "https://www.githubstatus.com/api/v2/components.json").to_return(
51
+ status: 200,
52
+ body: %(
53
+ {
54
+ "page":{},
55
+ "components":[
56
+ {
57
+ "id":"brv1bkgrwx7q",
58
+ "name":"API Requests",
59
+ "status":"operational",
60
+ "created_at":"2017-01-31T20:01:46.621Z",
61
+ "updated_at":"2019-07-23T18:41:18.197Z",
62
+ "position":2,
63
+ "description":"Requests for GitHub APIs",
64
+ "showcase":false,
65
+ "group_id":null,
66
+ "page_id":"kctbh9vrtdwd",
67
+ "group":false,
68
+ "only_show_if_degraded":false
69
+ }
70
+ ]
71
+ }
72
+ ),
73
+ )
74
+ assert_equal "operational", app(@organization).api_status[:status]
75
+ end
76
+
77
+ test "#github token is refreshed after expiration" do
78
+ Rails.env = 'not_test'
79
+ config = {
80
+ app_id: "test_id",
81
+ installation_id: "test_installation_id",
82
+ private_key: "test_private_key",
83
+ }
84
+ initial_token = OpenStruct.new(
85
+ token: "some_initial_github_token",
86
+ expires_at: Time.now.utc + 60.minutes,
87
+ )
88
+ second_token = OpenStruct.new(
89
+ token: "some_new_github_token",
90
+ expires_at: initial_token.expires_at + 60.minutes,
91
+ )
92
+ auth_payload = "test_auth_payload"
93
+
94
+ GitHubApp.any_instance.expects(:authentication_payload).twice.returns(auth_payload)
95
+ valid_app = app(@organization, config)
96
+
97
+ freeze_time do
98
+ Octokit::Client
99
+ .any_instance
100
+ .expects(:create_app_installation_access_token).twice.with(config[:installation_id], anything)
101
+ .returns(initial_token, second_token)
102
+
103
+ initial_token = valid_app.token
104
+ initial_cached_token = Rails.cache.fetch(@token_cache_key)
105
+ assert_equal initial_token, initial_cached_token.to_s
106
+
107
+ travel 5.minutes
108
+ assert_equal initial_token, valid_app.token
109
+
110
+ travel_to initial_cached_token.expires_at + 5.minutes
111
+ assert_equal second_token.token, valid_app.token
112
+ end
113
+ end
114
+
115
+ test "#github token is refreshed in refresh window before expiry" do
116
+ Rails.env = 'not_test'
117
+ config = {
118
+ app_id: "test_id",
119
+ installation_id: "test_installation_id",
120
+ private_key: "test_private_key",
121
+ }
122
+ initial_token = OpenStruct.new(
123
+ token: "some_initial_github_token",
124
+ expires_at: Time.now.utc + 60.minutes,
125
+ )
126
+ second_token = OpenStruct.new(
127
+ token: "some_new_github_token",
128
+ expires_at: initial_token.expires_at + 60.minutes,
129
+ )
130
+ auth_payload = "test_auth_payload"
131
+
132
+ GitHubApp.any_instance.expects(:authentication_payload).twice.returns(auth_payload)
133
+ valid_app = app(@organization, config)
134
+
135
+ freeze_time do
136
+ Octokit::Client
137
+ .any_instance
138
+ .expects(:create_app_installation_access_token).twice.with(config[:installation_id], anything)
139
+ .returns(initial_token, second_token)
140
+
141
+ initial_token = valid_app.token
142
+ initial_cached_token = Rails.cache.fetch(@token_cache_key)
143
+ assert_equal initial_token, initial_cached_token.to_s
144
+
145
+ # Travel forward, but before the token is refreshed, so the cached value should be the same.
146
+ travel 40.minutes
147
+ assert_equal initial_token, valid_app.token
148
+
149
+ # Travel to when the token should refresh, but is not expired, which should result in our cache.fetch update block.
150
+ travel 15.minutes
151
+ updated_token = valid_app.token
152
+ assert_not_equal initial_token, updated_token
153
+
154
+ cached_token = Rails.cache.fetch(@token_cache_key)
155
+ assert_operator cached_token.expires_at, :>, initial_cached_token.expires_at
156
+ end
157
+ end
158
+
159
+ test "#github token is missing refresh_at field" do
160
+ # $debugging = true
161
+ Rails.env = 'not_test'
162
+ config = {
163
+ app_id: "test_id",
164
+ installation_id: "test_installation_id",
165
+ private_key: "test_private_key",
166
+ }
167
+ initial_cached_token = Shipit::GitHubApp::Token.new("some_initial_github_token", Time.now.utc - 1.minute)
168
+ initial_cached_token.instance_variable_set(:@refresh_at, nil)
169
+
170
+ second_token = OpenStruct.new(
171
+ token: "some_new_github_token",
172
+ expires_at: initial_cached_token.expires_at + 60.minutes,
173
+ )
174
+ auth_payload = "test_auth_payload"
175
+
176
+ GitHubApp.any_instance.expects(:authentication_payload).returns(auth_payload)
177
+ valid_app = app(@organization, config)
178
+
179
+ freeze_time do
180
+ valid_app.instance_variable_set(:@token, initial_cached_token)
181
+ Rails.cache.write(@token_cache_key, initial_cached_token, expires_in: 1.minute)
182
+
183
+ Octokit::Client
184
+ .any_instance
185
+ .expects(:create_app_installation_access_token).with(config[:installation_id], anything)
186
+ .returns(second_token)
187
+
188
+ first_token = valid_app.token
189
+
190
+ first_cached_token = Rails.cache.fetch(@token_cache_key)
191
+ assert_equal first_token, first_cached_token.to_s
192
+
193
+ travel_to first_cached_token.expires_at + 5.minutes
194
+ new_token = valid_app.token
195
+
196
+ assert_equal second_token.token, new_token
197
+ end
198
+ end
199
+
200
+ private
201
+
202
+ def app(organization, extra_config = {})
203
+ GitHubApp.new(organization, double_github_app_config.deep_merge(extra_config))
204
+ end
205
+
206
+ def double_github_app_config
207
+ YAML.load_file('test/dummy/config/secrets_double_github_app.yml')
208
+ end
209
+ end
210
+
211
+ class GitHubAppsTestOrgTwo < ActiveSupport::TestCase
212
+ setup do
213
+ @organization = "OrgTwo"
214
+ @github = app(@organization)
215
+ @enterprise = app(@organization, domain: 'github.example.com')
216
+ @rails_env = Rails.env
217
+ @token_cache_key = "github:integration:#{@organization.downcase}:access-token"
218
+ Rails.cache.delete(@token_cache_key)
219
+ end
220
+
221
+ teardown do
222
+ Rails.env = @rails_env
223
+ Rails.cache.delete(@token_cache_key)
224
+ end
225
+
226
+ test "#domain defaults to github.com" do
227
+ assert_equal 'github.com', @github.domain
228
+ end
229
+
230
+ test "#url returns the HTTPS url to the github installation" do
231
+ assert_equal 'https://github.example.com', @enterprise.url
232
+ assert_equal 'https://github.example.com/foo/bar', @enterprise.url('/foo/bar')
233
+ assert_equal 'https://github.example.com/foo/bar/baz', @enterprise.url('foo/bar', 'baz')
234
+ end
235
+
236
+ test "#new_client retruns an Octokit::Client configured to use the github installation" do
237
+ assert_equal 'https://github.example.com/', @enterprise.new_client.web_endpoint
238
+ assert_equal 'https://github.example.com/api/v3/', @enterprise.new_client.api_endpoint
239
+ end
240
+
241
+ test "#oauth_config.last[:client_options] is nil if domain is not overriden" do
242
+ assert_nil @github.oauth_config.last[:client_options][:site]
243
+ end
244
+
245
+ test "#oauth_config.last[:client_options] returns Enterprise endpoint if domain is overriden" do
246
+ assert_equal 'https://github.example.com/api/v3/', @enterprise.oauth_config.last[:client_options][:site]
247
+ end
248
+
249
+ test "#initialize doesn't raise if given an empty config" do
250
+ assert_nothing_raised do
251
+ GitHubApp.new(@organization, {})
252
+ end
253
+ end
254
+
255
+ test "#api_status" do
256
+ stub_request(:get, "https://www.githubstatus.com/api/v2/components.json").to_return(
257
+ status: 200,
258
+ body: %(
259
+ {
260
+ "page":{},
261
+ "components":[
262
+ {
263
+ "id":"brv1bkgrwx7q",
264
+ "name":"API Requests",
265
+ "status":"operational",
266
+ "created_at":"2017-01-31T20:01:46.621Z",
267
+ "updated_at":"2019-07-23T18:41:18.197Z",
268
+ "position":2,
269
+ "description":"Requests for GitHub APIs",
270
+ "showcase":false,
271
+ "group_id":null,
272
+ "page_id":"kctbh9vrtdwd",
273
+ "group":false,
274
+ "only_show_if_degraded":false
275
+ }
276
+ ]
277
+ }
278
+ ),
279
+ )
280
+ assert_equal "operational", app(@organization).api_status[:status]
281
+ end
282
+
283
+ test "#github token is refreshed after expiration" do
284
+ Rails.env = 'not_test'
285
+ config = {
286
+ app_id: "test_id",
287
+ installation_id: "test_installation_id",
288
+ private_key: "test_private_key",
289
+ }
290
+ initial_token = OpenStruct.new(
291
+ token: "some_initial_github_token",
292
+ expires_at: Time.now.utc + 60.minutes,
293
+ )
294
+ second_token = OpenStruct.new(
295
+ token: "some_new_github_token",
296
+ expires_at: initial_token.expires_at + 60.minutes,
297
+ )
298
+ auth_payload = "test_auth_payload"
299
+
300
+ GitHubApp.any_instance.expects(:authentication_payload).twice.returns(auth_payload)
301
+ valid_app = app(@organization, config)
302
+
303
+ freeze_time do
304
+ Octokit::Client
305
+ .any_instance
306
+ .expects(:create_app_installation_access_token).twice.with(config[:installation_id], anything)
307
+ .returns(initial_token, second_token)
308
+
309
+ initial_token = valid_app.token
310
+ initial_cached_token = Rails.cache.fetch(@token_cache_key)
311
+ assert_equal initial_token, initial_cached_token.to_s
312
+
313
+ travel 5.minutes
314
+ assert_equal initial_token, valid_app.token
315
+
316
+ travel_to initial_cached_token.expires_at + 5.minutes
317
+ assert_equal second_token.token, valid_app.token
318
+ end
319
+ end
320
+
321
+ test "#github token is refreshed in refresh window before expiry" do
322
+ Rails.env = 'not_test'
323
+ config = {
324
+ app_id: "test_id",
325
+ installation_id: "test_installation_id",
326
+ private_key: "test_private_key",
327
+ }
328
+ initial_token = OpenStruct.new(
329
+ token: "some_initial_github_token",
330
+ expires_at: Time.now.utc + 60.minutes,
331
+ )
332
+ second_token = OpenStruct.new(
333
+ token: "some_new_github_token",
334
+ expires_at: initial_token.expires_at + 60.minutes,
335
+ )
336
+ auth_payload = "test_auth_payload"
337
+
338
+ GitHubApp.any_instance.expects(:authentication_payload).twice.returns(auth_payload)
339
+ valid_app = app(@organization, config)
340
+
341
+ freeze_time do
342
+ Octokit::Client
343
+ .any_instance
344
+ .expects(:create_app_installation_access_token).twice.with(config[:installation_id], anything)
345
+ .returns(initial_token, second_token)
346
+
347
+ initial_token = valid_app.token
348
+ initial_cached_token = Rails.cache.fetch(@token_cache_key)
349
+ assert_equal initial_token, initial_cached_token.to_s
350
+
351
+ # Travel forward, but before the token is refreshed, so the cached value should be the same.
352
+ travel 40.minutes
353
+ assert_equal initial_token, valid_app.token
354
+
355
+ # Travel to when the token should refresh, but is not expired, which should result in our cache.fetch update block.
356
+ travel 15.minutes
357
+ updated_token = valid_app.token
358
+ assert_not_equal initial_token, updated_token
359
+
360
+ cached_token = Rails.cache.fetch(@token_cache_key)
361
+ assert_operator cached_token.expires_at, :>, initial_cached_token.expires_at
362
+ end
363
+ end
364
+
365
+ test "#github token is missing refresh_at field" do
366
+ # $debugging = true
367
+ Rails.env = 'not_test'
368
+ config = {
369
+ app_id: "test_id",
370
+ installation_id: "test_installation_id",
371
+ private_key: "test_private_key",
372
+ }
373
+ initial_cached_token = Shipit::GitHubApp::Token.new("some_initial_github_token", Time.now.utc - 1.minute)
374
+ initial_cached_token.instance_variable_set(:@refresh_at, nil)
375
+
376
+ second_token = OpenStruct.new(
377
+ token: "some_new_github_token",
378
+ expires_at: initial_cached_token.expires_at + 60.minutes,
379
+ )
380
+ auth_payload = "test_auth_payload"
381
+
382
+ GitHubApp.any_instance.expects(:authentication_payload).returns(auth_payload)
383
+ valid_app = app(@organization, config)
384
+
385
+ freeze_time do
386
+ valid_app.instance_variable_set(:@token, initial_cached_token)
387
+ Rails.cache.write(@token_cache_key, initial_cached_token, expires_in: 1.minute)
388
+
389
+ Octokit::Client
390
+ .any_instance
391
+ .expects(:create_app_installation_access_token).with(config[:installation_id], anything)
392
+ .returns(second_token)
393
+
394
+ first_token = valid_app.token
395
+
396
+ first_cached_token = Rails.cache.fetch(@token_cache_key)
397
+ assert_equal first_token, first_cached_token.to_s
398
+
399
+ travel_to first_cached_token.expires_at + 5.minutes
400
+ new_token = valid_app.token
401
+
402
+ assert_equal second_token.token, new_token
403
+ end
404
+ end
405
+
406
+ private
407
+
408
+ def app(organization, extra_config = {})
409
+ GitHubApp.new(organization, double_github_app_config.deep_merge(extra_config))
410
+ end
411
+
412
+ def double_github_app_config
413
+ YAML.load_file('test/dummy/config/secrets_double_github_app.yml')
414
+ end
415
+ end
416
+ end