pact_broker 2.32.0 → 2.33.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE.md +3 -0
  3. data/CHANGELOG.md +18 -0
  4. data/config/database.yml +5 -8
  5. data/db/migrations/20190510_set_version_sequence.rb +1 -0
  6. data/db/migrations/20190602_add_headers_column_to_webhooks.rb +6 -0
  7. data/db/migrations/20190603_migrate_webhook_headers.rb +10 -0
  8. data/lib/pact_broker/api/resources/index.rb +12 -0
  9. data/lib/pact_broker/api/resources/pact.rb +1 -1
  10. data/lib/pact_broker/api/resources/verifications.rb +1 -1
  11. data/lib/pact_broker/api/resources/webhook_execution.rb +1 -1
  12. data/lib/pact_broker/certificates/certificate.rb +1 -1
  13. data/lib/pact_broker/config/setting.rb +1 -1
  14. data/lib/pact_broker/db/data_migrations/migrate_webhook_headers.rb +30 -0
  15. data/lib/pact_broker/domain/pacticipant.rb +1 -1
  16. data/lib/pact_broker/domain/verification.rb +1 -1
  17. data/lib/pact_broker/domain/version.rb +1 -1
  18. data/lib/pact_broker/domain/webhook.rb +27 -1
  19. data/lib/pact_broker/domain/webhook_request.rb +9 -146
  20. data/lib/pact_broker/integrations/integration.rb +7 -0
  21. data/lib/pact_broker/matrix/repository.rb +0 -1
  22. data/lib/pact_broker/matrix/row.rb +58 -17
  23. data/lib/pact_broker/pacts/pact_publication.rb +8 -1
  24. data/lib/pact_broker/pacts/pact_version.rb +1 -1
  25. data/lib/pact_broker/pacts/repository.rb +5 -8
  26. data/lib/pact_broker/repositories/helpers.rb +5 -4
  27. data/lib/pact_broker/test/test_data_builder.rb +2 -2
  28. data/lib/pact_broker/version.rb +1 -1
  29. data/lib/pact_broker/versions/latest_version.rb +10 -0
  30. data/lib/pact_broker/webhooks/execution.rb +1 -1
  31. data/lib/pact_broker/webhooks/http_request_with_redacted_headers.rb +21 -0
  32. data/lib/pact_broker/webhooks/http_response_with_utf_8_safe_body.rb +21 -0
  33. data/lib/pact_broker/webhooks/job.rb +2 -2
  34. data/lib/pact_broker/webhooks/pact_and_verification_parameters.rb +86 -0
  35. data/lib/pact_broker/webhooks/render.rb +10 -71
  36. data/lib/pact_broker/webhooks/repository.rb +2 -9
  37. data/lib/pact_broker/webhooks/service.rb +5 -5
  38. data/lib/pact_broker/webhooks/triggered_webhook.rb +1 -1
  39. data/lib/pact_broker/webhooks/webhook.rb +9 -30
  40. data/lib/pact_broker/webhooks/webhook_event.rb +1 -1
  41. data/lib/pact_broker/{domain → webhooks}/webhook_execution_result.rb +6 -5
  42. data/lib/pact_broker/webhooks/webhook_request_logger.rb +105 -0
  43. data/lib/pact_broker/webhooks/webhook_request_template.rb +7 -6
  44. data/pact_broker.gemspec +1 -0
  45. data/script/docker/db-restore.sh +5 -0
  46. data/script/docker/db-rm.sh +3 -0
  47. data/script/docker/db-start.sh +7 -0
  48. data/script/import-pg-database.sh +5 -0
  49. data/script/seed.rb +1 -1
  50. data/spec/features/execute_webhook_spec.rb +1 -1
  51. data/spec/features/publish_verification_spec.rb +1 -1
  52. data/spec/integration/webhooks/certificate_spec.rb +4 -6
  53. data/spec/lib/pact_broker/api/decorators/webhook_execution_result_decorator_spec.rb +1 -1
  54. data/spec/lib/pact_broker/api/resources/verifications_spec.rb +1 -1
  55. data/spec/lib/pact_broker/api/resources/webhook_execution_spec.rb +1 -1
  56. data/spec/lib/pact_broker/db/data_migrations/migrate_webhook_headers_spec.rb +78 -0
  57. data/spec/lib/pact_broker/domain/webhook_request_spec.rb +8 -163
  58. data/spec/lib/pact_broker/domain/webhook_spec.rb +45 -8
  59. data/spec/lib/pact_broker/matrix/repository_spec.rb +24 -0
  60. data/spec/lib/pact_broker/pacticipants/find_potential_duplicate_pacticipant_names_spec.rb +4 -4
  61. data/spec/lib/pact_broker/pacts/pact_publication_spec.rb +60 -0
  62. data/spec/lib/pact_broker/webhooks/job_spec.rb +3 -3
  63. data/spec/lib/pact_broker/webhooks/render_spec.rb +22 -7
  64. data/spec/lib/pact_broker/webhooks/repository_spec.rb +24 -24
  65. data/spec/lib/pact_broker/webhooks/service_spec.rb +5 -5
  66. data/spec/lib/pact_broker/webhooks/webhook_request_logger_spec.rb +197 -0
  67. data/spec/lib/pact_broker/webhooks/webhook_request_template_spec.rb +13 -4
  68. data/spec/support/database.rb +1 -1
  69. data/spec/support/ssl_pact_broker_server.rb +46 -0
  70. data/tasks/database.rb +12 -0
  71. data/tasks/db.rake +26 -6
  72. data/tasks/docker_database.rb +25 -0
  73. metadata +35 -3
@@ -3,6 +3,66 @@ require 'pact_broker/pacts/pact_publication'
3
3
  module PactBroker
4
4
  module Pacts
5
5
  describe PactPublication do
6
+ describe "save and upsert" do
7
+ before do
8
+ td.create_consumer
9
+ .create_provider
10
+ .create_consumer_version
11
+ .create_pact
12
+ end
13
+
14
+ let(:params) do
15
+ {
16
+ consumer_id: td.consumer.id,
17
+ provider_id: td.provider.id,
18
+ consumer_version_id: td.consumer_version.id,
19
+ pact_version_id: PactVersion.first.id,
20
+ revision_number: 1
21
+ }
22
+ end
23
+
24
+ let(:pact_publication) do
25
+ PactPublication.new(params)
26
+ end
27
+
28
+ context "when using a PactPublication with the same provider/consumer version/revision number as an existing PactPublication" do
29
+ describe "save" do
30
+ it "raises a constraint exception" do
31
+ expect { pact_publication.save }.to raise_error Sequel::UniqueConstraintViolation
32
+ end
33
+ end
34
+
35
+ describe "upsert" do
36
+ it "does not raise an error" do
37
+ pact_publication.upsert
38
+ end
39
+
40
+ it "sets the relationship objects" do
41
+ pact_publication.upsert
42
+ expect(pact_publication.id).to_not be nil
43
+ expect(pact_publication.consumer.id).to eq td.consumer.id
44
+ expect(pact_publication.consumer.name).to eq td.consumer.name
45
+ end
46
+
47
+ context "with objects instead of ids" do
48
+ let(:params) do
49
+ {
50
+ consumer: td.consumer,
51
+ provider: td.provider,
52
+ consumer_version: td.consumer_version,
53
+ pact_version: PactVersion.first,
54
+ revision_number: 1
55
+ }
56
+ end
57
+
58
+ it "also works" do
59
+ pact_publication.upsert
60
+ expect(pact_publication.consumer_id).to eq td.consumer.id
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
6
66
 
7
67
  describe "#latest_tag_names" do
8
68
  before do
@@ -23,7 +23,7 @@ module PactBroker
23
23
  triggered_webhook: triggered_webhook,
24
24
  database_connector: database_connector,
25
25
  webhook_context: webhook_context,
26
- execution_options: { the: 'options' }
26
+ logging_options: { the: 'options' }
27
27
  }
28
28
  end
29
29
 
@@ -84,7 +84,7 @@ module PactBroker
84
84
  it "executes the job with an log message indicating that the webhook will be retried" do
85
85
  expect(PactBroker::Webhooks::Service).to receive(:execute_triggered_webhook_now)
86
86
  .with(triggered_webhook, {
87
- execution_options: {
87
+ logging_options: {
88
88
  failure_log_message: "Retrying webhook in 10 seconds",
89
89
  success_log_message: "Successfully executed webhook",
90
90
  the: 'options'
@@ -132,7 +132,7 @@ module PactBroker
132
132
  it "executes the job with an log message indicating that the webhook has failed" do
133
133
  expect(PactBroker::Webhooks::Service).to receive(:execute_triggered_webhook_now)
134
134
  .with(triggered_webhook, hash_including(
135
- execution_options: hash_including(
135
+ logging_options: hash_including(
136
136
  failure_log_message: "Webhook execution failed after 7 attempts",
137
137
  success_log_message: "Successfully executed webhook")
138
138
  )
@@ -1,6 +1,8 @@
1
1
  require 'pact_broker/webhooks/render'
2
2
  require 'pact_broker/pacts/placeholder_pact'
3
3
  require 'pact_broker/verifications/placeholder_verification'
4
+ require 'pact_broker/webhooks/pact_and_verification_parameters'
5
+
4
6
  require 'cgi'
5
7
 
6
8
  module PactBroker
@@ -124,7 +126,8 @@ module PactBroker
124
126
  it "replaces #{template} with #{expected_output.inspect}" do
125
127
  the_pact = send(pact_var_name)
126
128
  the_verification = send(verification_var_name)
127
- output = Render.call(template, the_pact, the_verification, webhook_context)
129
+ template_parameters = PactAndVerificationParameters.new(the_pact, the_verification, webhook_context).to_hash
130
+ output = Render.call(template, template_parameters)
128
131
  expect(output).to eq expected_output
129
132
  end
130
133
  end
@@ -132,7 +135,8 @@ module PactBroker
132
135
 
133
136
  context "with an escaper" do
134
137
  subject do
135
- Render.call(template, pact, verification, webhook_context) do | value |
138
+ template_parameters = PactAndVerificationParameters.new(pact, verification, webhook_context).to_hash
139
+ Render.call(template, template_parameters) do | value |
136
140
  CGI.escape(value)
137
141
  end
138
142
  end
@@ -147,19 +151,24 @@ module PactBroker
147
151
  let(:webhook_context) do
148
152
  {
149
153
  consumer_version_number: "webhook-version-number",
150
- consumer_version_tags: %w[webhook tags]
154
+ consumer_version_tags: %w[webhook tags],
155
+ base_url: base_url
156
+
151
157
  }
152
158
  end
159
+ let(:template_parameters) do
160
+ PactAndVerificationParameters.new(pact, verification, webhook_context).to_hash
161
+ end
153
162
 
154
163
  it "uses the consumer_version_number in preference to the field on the domain models" do
155
164
  template = "${pactbroker.consumerVersionNumber}"
156
- output = Render.call(template, pact, verification, webhook_context)
165
+ output = Render.call(template, template_parameters)
157
166
  expect(output).to eq "webhook-version-number"
158
167
  end
159
168
 
160
169
  it "uses the consumer_version_tags in preference to the field on the domain models" do
161
170
  template = "${pactbroker.consumerVersionTags}"
162
- output = Render.call(template, pact, verification, webhook_context)
171
+ output = Render.call(template, template_parameters)
163
172
  expect(output).to eq "webhook, tags"
164
173
  end
165
174
  end
@@ -170,12 +179,18 @@ module PactBroker
170
179
  let(:placeholder_verification) { PactBroker::Verifications::PlaceholderVerification.new }
171
180
  let(:base_url) { "http://broker" }
172
181
 
182
+ let(:template_parameters) do
183
+ PactAndVerificationParameters.new(placeholder_pact, nil, { base_url: base_url }).to_hash
184
+ end
185
+
173
186
  it "does not blow up with a placeholder pact" do
174
- Render.call("", placeholder_pact, nil, {})
187
+ template_parameters = PactAndVerificationParameters.new(placeholder_pact, nil, { base_url: base_url }).to_hash
188
+ Render.call("", template_parameters)
175
189
  end
176
190
 
177
191
  it "does not blow up with a placeholder verification" do
178
- Render.call("", placeholder_pact, placeholder_verification, {})
192
+ template_parameters = PactAndVerificationParameters.new(placeholder_pact, placeholder_verification, { base_url: base_url }).to_hash
193
+ Render.call("", template_parameters)
179
194
  end
180
195
  end
181
196
  end
@@ -27,7 +27,6 @@ module PactBroker
27
27
  let(:provider) { td.create_pacticipant 'Provider'; td.pacticipant}
28
28
  let(:uuid) { 'the-uuid' }
29
29
  let(:created_webhook_record) { ::DB::PACT_BROKER_DB[:webhooks].order(:id).last }
30
- let(:created_headers) { ::DB::PACT_BROKER_DB[:webhook_headers].where(webhook_id: created_webhook_record[:id]).order(:name).all }
31
30
  let(:created_events) { ::DB::PACT_BROKER_DB[:webhook_events].where(webhook_id: created_webhook_record[:id]).order(:name).all }
32
31
  let(:expected_webhook_record) do
33
32
  {
@@ -50,19 +49,14 @@ module PactBroker
50
49
  expect(created_webhook_record).to include expected_webhook_record
51
50
  end
52
51
 
53
- it "saves the headers" do
52
+ it "saves the webhook headers as JSON" do
54
53
  subject
55
- expect(created_headers.size).to eq 2
56
- expect(created_headers.first[:name]).to eq "Accept"
57
- expect(created_headers.first[:value]).to eq "application/json"
58
- expect(created_headers.last[:name]).to eq "Content-Type"
59
- expect(created_headers.last[:value]).to eq "application/json"
54
+ expect(JSON.parse(created_webhook_record[:headers])).to eq headers
60
55
  end
61
56
 
62
57
  it "saves the webhook events" do
63
58
  expect(subject.events.first[:name]).to eq "something_happened"
64
59
  end
65
-
66
60
  end
67
61
 
68
62
  describe "delete_by_uuid" do
@@ -74,12 +68,6 @@ module PactBroker
74
68
 
75
69
  subject { Repository.new.delete_by_uuid(uuid) }
76
70
 
77
- it "deletes the webhook headers" do
78
- expect { subject }.to change {
79
- ::DB::PACT_BROKER_DB[:webhook_headers].count
80
- }.by(-2)
81
- end
82
-
83
71
  it "deletes the webhook" do
84
72
  expect { subject }.to change {
85
73
  ::DB::PACT_BROKER_DB[:webhooks].where(uuid: uuid).count
@@ -240,16 +228,28 @@ module PactBroker
240
228
  subject { Repository.new.update_by_uuid(uuid, new_webhook) }
241
229
 
242
230
  it "updates the webhook" do
243
- updated_webhook = subject
244
- expect(updated_webhook.uuid).to eq uuid
245
- expect(updated_webhook.request.method).to eq 'GET'
246
- expect(updated_webhook.request.url).to eq 'http://example.com'
247
- expect(updated_webhook.request.body).to eq 'foo'
248
- expect(updated_webhook.request.headers).to eq 'Content-Type' => 'text/plain'
249
- expect(updated_webhook.request.username).to eq nil
250
- expect(updated_webhook.request.password).to eq nil
251
- expect(updated_webhook.events.first.name).to eq 'something_else'
252
- expect(updated_webhook.consumer.name).to eq "Foo2"
231
+ expect(subject.uuid).to eq uuid
232
+ expect(subject.request.method).to eq 'GET'
233
+ expect(subject.request.url).to eq 'http://example.com'
234
+ expect(subject.request.body).to eq 'foo'
235
+ expect(subject.request.headers).to eq 'Content-Type' => 'text/plain'
236
+ expect(subject.request.username).to eq nil
237
+ expect(subject.request.password).to eq nil
238
+ expect(subject.events.first.name).to eq 'something_else'
239
+ expect(subject.consumer.name).to eq "Foo2"
240
+ end
241
+
242
+ context "when the updated params do not contain a consumer or provider" do
243
+ let(:new_webhook) do
244
+ PactBroker::Domain::Webhook.new(
245
+ events: [new_event],
246
+ request: new_request
247
+ )
248
+ end
249
+
250
+ it "removes the existing consumer or provider" do
251
+ expect(subject.consumer).to be nil
252
+ end
253
253
  end
254
254
  end
255
255
 
@@ -154,7 +154,7 @@ module PactBroker
154
154
  let(:options) do
155
155
  { database_connector: double('database_connector'),
156
156
  webhook_context: {},
157
- execution_options: {}
157
+ logging_options: {}
158
158
  }
159
159
  end
160
160
 
@@ -219,7 +219,7 @@ module PactBroker
219
219
  let(:result) { double('result') }
220
220
  let(:options) do
221
221
  {
222
- execution_options: {
222
+ logging_options: {
223
223
  failure_log_message: "Webhook execution failed",
224
224
  show_response: 'foo',
225
225
  },
@@ -346,10 +346,10 @@ module PactBroker
346
346
  {
347
347
  database_connector: database_connector,
348
348
  webhook_context: { base_url: 'http://example.org' },
349
- execution_options: execution_options
349
+ logging_options: logging_options
350
350
  }
351
351
  end
352
- let(:execution_options) { { show_response: true } }
352
+ let(:logging_options) { { show_response: true } }
353
353
  let(:database_connector) { ->(&block) { block.call } }
354
354
  let(:pact) do
355
355
  td.create_consumer
@@ -369,7 +369,7 @@ module PactBroker
369
369
  end
370
370
 
371
371
  it "executes the webhook with the correct options" do
372
- expect_any_instance_of(PactBroker::Domain::WebhookRequest).to receive(:execute).with(hash_including(execution_options)).and_call_original
372
+ expect_any_instance_of(PactBroker::Domain::WebhookRequest).to receive(:execute).and_call_original
373
373
  subject
374
374
  end
375
375
 
@@ -0,0 +1,197 @@
1
+ require 'pact_broker/webhooks/webhook_request_logger'
2
+ require 'pact_broker/domain/webhook_request'
3
+
4
+ module PactBroker
5
+ module Webhooks
6
+ describe WebhookRequestLogger do
7
+ before do
8
+ if response
9
+ response_headers.each do | key, value |
10
+ allow(response).to receive(:each_header).and_yield(key, value)
11
+ end
12
+ end
13
+ allow(webhook_request_logger).to receive(:logger).and_return(logger)
14
+ subject
15
+ end
16
+
17
+ let(:logger) { double('logger').as_null_object }
18
+ let(:uuid) { "uuid" }
19
+ let(:options) { { failure_log_message: 'oops', show_response: show_response } }
20
+ let(:show_response) { true }
21
+ let(:username) { nil }
22
+ let(:password) { nil }
23
+ let(:url) { 'http://example.org/hook' }
24
+ let(:headers) { {'Content-Type' => 'text/plain', 'Authorization' => 'foo'} }
25
+ let(:body) { 'reqbody' }
26
+ let(:webhook_request) do
27
+ PactBroker::Domain::WebhookRequest.new(
28
+ method: 'post',
29
+ url: url,
30
+ headers: headers,
31
+ username: username,
32
+ password: password,
33
+ body: body)
34
+ end
35
+ let(:error) { nil }
36
+ let(:status) { 200 }
37
+ let(:response) do
38
+ double('response',
39
+ http_version: "1.0",
40
+ message: "OK",
41
+ code: status,
42
+ body: response_body,
43
+ to_hash: response_headers
44
+ )
45
+ end
46
+ let(:response_body) { "respbod" }
47
+ let(:response_headers) do
48
+ {
49
+ 'content-type' => 'text/foo, blah'
50
+ }
51
+ end
52
+
53
+ let(:webhook_request_logger) { WebhookRequestLogger.new(options) }
54
+
55
+ subject(:logs) { webhook_request_logger.log(uuid, webhook_request, response, error) }
56
+
57
+ describe "application logs" do
58
+ it "logs the request" do
59
+ expect(logger).to have_received(:info).with(/POST.*example/)
60
+ expect(logger).to have_received(:debug).with(/.*text\/plain/)
61
+ expect(logger).to have_received(:debug).with(/.*reqbody/)
62
+ end
63
+
64
+ it "logs the response" do
65
+ expect(logger).to have_received(:info).with(/response.*200/)
66
+ expect(logger).to have_received(:debug).with(/text\/foo/)
67
+ expect(logger).to have_received(:debug).with(/respbod/)
68
+ end
69
+ end
70
+
71
+ describe "execution logs" do
72
+
73
+ it "logs the request method and path" do
74
+ expect(logs).to include "POST http://example.org/hook"
75
+ end
76
+
77
+ it "logs the request headers" do
78
+ expect(logs).to include "content-type: text/plain"
79
+ end
80
+
81
+ it "logs the request body" do
82
+ expect(logs).to include body
83
+ end
84
+
85
+ context "when show_response is true" do
86
+ it "logs the response status" do
87
+ expect(logs).to include "HTTP/1.0 200"
88
+ end
89
+
90
+ it "logs the response headers" do
91
+ expect(logs).to include "content-type: text/foo, blah"
92
+ end
93
+
94
+ it "logs the response body" do
95
+ expect(logs).to include "respbod"
96
+ end
97
+ end
98
+
99
+ context "when show_response is false" do
100
+ let(:show_response) { false }
101
+
102
+ it "does not log the response status" do
103
+ expect(logs).to_not include "HTTP/1.0 200"
104
+ end
105
+
106
+ it "does not log the response headers" do
107
+ expect(logs).to_not include "content-type: text/foo, blah"
108
+ end
109
+
110
+ it "does not log the response body" do
111
+ expect(logs).to_not include "respbod"
112
+ end
113
+
114
+ it "logs a message about why the response is hidden" do
115
+ expect(logs).to include "security purposes"
116
+ end
117
+ end
118
+
119
+ context "when the response code is a success" do
120
+ it "does not log the failure_log_message" do
121
+ expect(logs).to_not include "oops"
122
+ end
123
+ end
124
+
125
+ context "when the response code is not successful" do
126
+ let(:status) { 400 }
127
+
128
+ it "logs the failure_log_message" do
129
+ expect(logs).to include "oops"
130
+ end
131
+ end
132
+
133
+ context "with basic auth" do
134
+ let(:headers) { { 'authorization' => 'foo' } }
135
+
136
+ it "logs the Authorization header with a starred value" do
137
+ expect(logs).to include "authorization: **********"
138
+ end
139
+ end
140
+
141
+ context "when the response body contains a non UTF-8 character" do
142
+ let(:response_body) { "This has some \xC2 invalid chars" }
143
+
144
+ it "logs the safe body so it doesn't blow up the database" do
145
+ expect(logs).to include "This has some invalid chars"
146
+ end
147
+
148
+ it "logs that it has cleaned the string to the execution logger" do
149
+ expect(logs).to include("Note that invalid UTF-8 byte sequences were removed")
150
+ end
151
+ end
152
+
153
+ context "when an error occurs executing the request" do
154
+
155
+ class WebhookTestError < StandardError; end
156
+
157
+ before do
158
+ allow(error).to receive(:backtrace).and_return([])
159
+ end
160
+
161
+ let(:response) { nil }
162
+ let(:error) do
163
+ err = WebhookTestError.new("blah")
164
+ allow(err).to receive(:backtrace).and_return([])
165
+ err
166
+ end
167
+
168
+ it "logs the error" do
169
+ expect(logger).to have_received(:info).with(/Error.*WebhookTestError.*blah/)
170
+ end
171
+
172
+ it "logs the failure_log_message" do
173
+ expect(logs).to include "oops"
174
+ end
175
+
176
+ context "when show_response is true" do
177
+ it "logs the exception information" do
178
+ expect(logs).to include "blah"
179
+ end
180
+ end
181
+
182
+ context "when show_response is false" do
183
+ let(:show_response) { false }
184
+
185
+ it "does not logs the exception information" do
186
+ expect(logs).to_not include "blah"
187
+ end
188
+
189
+ it "logs a message about why the response is hidden" do
190
+ expect(logs).to include "security purposes"
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
196
+ end
197
+ end