Skip to content

Commit a436e42

Browse files
committed
feat(webhooks): allow testing of an unsaved webhook
1 parent de0d3b7 commit a436e42

10 files changed

+141
-55
lines changed

lib/pact_broker/api.rb

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ module PactBroker
7171
add ['pacts', 'provider', :provider_name, 'consumer', :consumer_name, 'webhooks', 'status'], Api::Resources::PactWebhooksStatus, {resource_name: "pact_webhooks_status"}
7272
add ['pacts', 'provider', :provider_name, 'consumer', :consumer_name, 'version', :consumer_version_number, 'triggered-webhooks'], Api::Resources::PactTriggeredWebhooks, {resource_name: "pact_triggered_webhooks"}
7373

74+
add ['webhooks', 'execute' ], Api::Resources::WebhookExecution, {resource_name: "execute_unsaved_webhook"}
7475
add ['webhooks', :uuid ], Api::Resources::Webhook, {resource_name: "webhook"}
7576
add ['webhooks', :uuid, 'trigger', :trigger_uuid, 'logs' ], Api::Resources::TriggeredWebhookLogs, {resource_name: "triggered_webhook_logs"}
7677
add ['webhooks', :uuid, 'execute' ], Api::Resources::WebhookExecution, {resource_name: "execute_webhook"}

lib/pact_broker/api/decorators/decorator_context.rb

+5-5
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,17 @@ def initialize base_url, resource_url, options = {}
1111
self[:base_url] = base_url
1212
@resource_url = resource_url
1313
self[:resource_url] = resource_url
14-
@resource_title = options[:resource_title]
15-
self[:resource_title] = resource_title
14+
if options[:resource_title]
15+
@resource_title = options[:resource_title]
16+
self[:resource_title] = resource_title
17+
end
1618
merge!(options)
1719
end
1820

1921
def to_s
2022
"DecoratorContext #{super}"
2123
end
22-
2324
end
24-
2525
end
2626
end
27-
end
27+
end

lib/pact_broker/api/decorators/webhook_execution_result_decorator.rb

+6-5
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ def url
3535
end
3636
end
3737

38-
3938
class HTTPResponseDecorator < BaseDecorator
4039
property :status, :getter => lambda { |_| code.to_i }
4140
property :headers, exec_context: :decorator
@@ -63,15 +62,17 @@ def body
6362
property :response_hidden_message, as: :message, exec_context: :decorator, if: lambda { |context| !context[:options][:user_options][:show_response] }
6463

6564
link :webhook do | options |
66-
{
67-
href: webhook_url(options.fetch(:webhook).uuid, options.fetch(:base_url))
68-
}
65+
if options.fetch(:webhook).uuid
66+
{
67+
href: webhook_url(options.fetch(:webhook).uuid, options.fetch(:base_url))
68+
}
69+
end
6970
end
7071

7172
link :'try-again' do | options |
7273
{
7374
title: 'Execute the webhook again',
74-
href: webhook_execution_url(options.fetch(:webhook), options.fetch(:base_url))
75+
href: options.fetch(:resource_url)
7576
}
7677
end
7778

lib/pact_broker/api/resources/webhook.rb

+4-8
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
require 'pact_broker/api/resources/base_resource'
22
require 'pact_broker/services'
33
require 'pact_broker/api/decorators/webhook_decorator'
4+
require 'pact_broker/api/resources/webhook_resource_methods'
45

56
module PactBroker
67
module Api
78
module Resources
8-
99
class Webhook < BaseResource
1010

11+
include WebhookResourceMethods
12+
1113
def content_types_accepted
1214
[["application/json", :from_json]]
1315
end
@@ -26,7 +28,7 @@ def resource_exists?
2628

2729
def malformed_request?
2830
if request.put?
29-
return invalid_json? || validation_errors?(webhook)
31+
return invalid_json? || webhook_validation_errors?(new_webhook)
3032
end
3133
false
3234
end
@@ -51,12 +53,6 @@ def delete_resource
5153

5254
private
5355

54-
def validation_errors? webhook
55-
errors = webhook_service.errors(new_webhook)
56-
set_json_validation_error_messages(errors.messages) if !errors.empty?
57-
!errors.empty?
58-
end
59-
6056
def webhook
6157
@webhook ||= webhook_service.find_by_uuid uuid
6258
end

lib/pact_broker/api/resources/webhook_execution.rb

+31-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
11
require 'pact_broker/api/resources/base_resource'
22
require 'pact_broker/services'
33
require 'pact_broker/api/decorators/webhook_execution_result_decorator'
4+
require 'pact_broker/api/resources/webhook_resource_methods'
45
require 'pact_broker/constants'
56

67
module PactBroker
78
module Api
89
module Resources
910
class WebhookExecution < BaseResource
11+
include WebhookResourceMethods
12+
13+
def content_types_accepted
14+
[["application/json"]]
15+
end
16+
17+
def content_types_provided
18+
[["application/hal+json"]]
19+
end
1020

1121
def allowed_methods
1222
["POST", "OPTIONS"]
@@ -23,26 +33,39 @@ def resource_exists?
2333
webhook
2434
end
2535

36+
def malformed_request?
37+
if uuid
38+
false
39+
else
40+
webhook_validation_errors?(webhook)
41+
end
42+
end
43+
2644
private
2745

2846
def post_response_body webhook_execution_result
2947
Decorators::WebhookExecutionResultDecorator.new(webhook_execution_result).to_json(user_options: user_options)
3048
end
3149

3250
def webhook
33-
@webhook ||= webhook_service.find_by_uuid uuid
51+
@webhook ||= begin
52+
if uuid
53+
webhook_service.find_by_uuid uuid
54+
else
55+
build_unsaved_webhook
56+
end
57+
end
3458
end
3559

3660
def uuid
3761
identifier_from_path[:uuid]
3862
end
3963

4064
def user_options
41-
{
42-
base_url: base_url,
65+
decorator_context(
4366
webhook: webhook,
4467
show_response: PactBroker.configuration.show_webhook_response?
45-
}
68+
)
4669
end
4770

4871
def webhook_options
@@ -55,6 +78,10 @@ def webhook_options
5578
}
5679
}
5780
end
81+
82+
def build_unsaved_webhook
83+
Decorators::WebhookDecorator.new(PactBroker::Domain::Webhook.new).from_json(request_body)
84+
end
5885
end
5986
end
6087
end
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,17 @@
11
module PactBroker
22
module Api
3-
43
module Resources
5-
64
module WebhookResourceMethods
7-
8-
def malformed_webhook_request? webhook
9-
begin
10-
if (errors = webhook.validate).any?
11-
set_json_validation_error_messages errors
12-
return true
13-
end
14-
rescue
15-
set_json_error_message 'Invalid JSON'
16-
return true
5+
def webhook_validation_errors? webhook
6+
errors = webhook_service.errors(webhook)
7+
if !errors.empty?
8+
set_json_validation_error_messages(errors.messages)
9+
true
10+
else
11+
false
1712
end
18-
false
1913
end
2014
end
21-
2215
end
2316
end
24-
end
17+
end

lib/pact_broker/api/resources/webhooks.rb

+3-12
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
require 'pact_broker/api/decorators/webhook_decorator'
33
require 'pact_broker/api/decorators/webhooks_decorator'
44
require 'pact_broker/api/contracts/webhook_contract'
5+
require 'pact_broker/api/resources/webhook_resource_methods'
56

67
module PactBroker
78
module Api
89
module Resources
910
class Webhooks < BaseResource
11+
include WebhookResourceMethods
1012

1113
def allowed_methods
1214
["POST", "GET", "OPTIONS"]
@@ -27,22 +29,11 @@ def resource_exists?
2729

2830
def malformed_request?
2931
if request.post?
30-
return invalid_json? || validation_errors?(webhook)
32+
return invalid_json? || webhook_validation_errors?(webhook)
3133
end
3234
false
3335
end
3436

35-
def validation_errors? webhook
36-
errors = webhook_service.errors(webhook)
37-
38-
unless errors.empty?
39-
response.headers['Content-Type'] = 'application/hal+json;charset=utf-8'
40-
response.body = { errors: errors.messages }.to_json
41-
end
42-
43-
!errors.empty?
44-
end
45-
4637
def create_path
4738
webhook_url next_uuid, base_url
4839
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
require 'support/test_data_builder'
2+
require 'webmock/rspec'
3+
require 'rack/pact_broker/database_transaction'
4+
5+
describe "Execute a webhook" do
6+
7+
let(:td) { TestDataBuilder.new }
8+
9+
before do
10+
td.create_pact_with_hierarchy("Foo", "1", "Bar")
11+
allow(PactBroker.configuration).to receive(:webhook_scheme_whitelist).and_return(%w[http])
12+
end
13+
14+
let(:params) do
15+
{
16+
request: {
17+
method: 'POST',
18+
url: 'http://example.org',
19+
headers: {'Content-Type' => 'application/json'},
20+
body: '${pactbroker.pactUrl}'
21+
}
22+
}
23+
end
24+
let(:rack_headers) { { "CONTENT_TYPE" => "application/json", "HTTP_ACCEPT" => "application/hal+json" } }
25+
26+
let(:path) { "/webhooks/execute" }
27+
let(:response_body) { JSON.parse(last_response.body, symbolize_names: true)}
28+
29+
subject { post(path, params.to_json, rack_headers) }
30+
31+
context "when the execution is successful" do
32+
let!(:request) do
33+
stub_request(:post, /http/).with(body: expected_webhook_url).to_return(:status => 200, body: response_body)
34+
end
35+
36+
let(:expected_webhook_url) { %r{http://example.org/pacts/provider/Bar/consumer/Foo.*} }
37+
let(:response_body) { "webhook-response-body" }
38+
39+
it "performs the HTTP request" do
40+
subject
41+
expect(request).to have_been_made
42+
end
43+
44+
it "returns a 200 response" do
45+
expect(subject.status).to be 200
46+
end
47+
end
48+
49+
context "when there is a validation error" do
50+
let(:params) { {} }
51+
52+
it "returns a 400" do
53+
expect(subject.status).to be 400
54+
end
55+
end
56+
end

spec/lib/pact_broker/api/decorators/webhook_execution_result_decorator_spec.rb

+17-5
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,35 @@ module Decorators
2020
let(:response) { double('http_response', code: '200', body: response_body, to_hash: headers) }
2121
let(:response_body) { 'body' }
2222
let(:error) { nil }
23-
let(:webhook) { instance_double(PactBroker::Domain::Webhook, uuid: 'some-uuid') }
23+
let(:webhook) { instance_double(PactBroker::Domain::Webhook, uuid: uuid) }
24+
let(:uuid) { 'some-uuid' }
2425
let(:show_response) { true }
2526
let(:json) {
2627
WebhookExecutionResultDecorator.new(webhook_execution_result)
27-
.to_json(user_options: { base_url: 'http://example.org', webhook: webhook, show_response: show_response })
28+
.to_json(user_options: { resource_url: 'http://resource-url', base_url: 'http://example.org', webhook: webhook, show_response: show_response })
2829
}
2930

3031
let(:subject) { JSON.parse(json, symbolize_names: true)}
3132

3233
it "includes a link to execute the webhook again" do
33-
expect(subject[:_links][:'try-again'][:href]).to eq 'http://example.org/webhooks/some-uuid/execute'
34+
expect(subject[:_links][:'try-again'][:href]).to eq 'http://resource-url'
3435
end
3536

36-
it "includes a link to the webhook" do
37-
expect(subject[:_links][:webhook][:href]).to eq 'http://example.org/webhooks/some-uuid'
37+
context "when there is a uuid" do
38+
it "include a link to the webhook" do
39+
expect(subject[:_links][:webhook][:href]).to eq 'http://example.org/webhooks/some-uuid'
40+
end
41+
end
42+
43+
context "when there is a not uuid because this is an unsaved webhook" do
44+
let(:uuid) { nil }
45+
46+
it "does not includes a link to the webhook" do
47+
expect(subject[:_links]).to_not have_key(:webhook)
48+
end
3849
end
3950

51+
4052
context "when there is an error" do
4153
let(:error) { double('error', message: 'message', backtrace: ['blah','blah']) }
4254

spec/lib/pact_broker/api/resources/webhook_execution_spec.rb

+10-1
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,23 @@ module Resources
5555
end
5656

5757
context "when execution is successful" do
58+
let(:expected_user_options) do
59+
{
60+
resource_url: 'http://example.org/webhooks/some-uuid/execute',
61+
base_url: 'http://example.org',
62+
webhook: webhook,
63+
show_response: 'foo',
64+
}
65+
end
66+
5867
it "returns a 200 JSON response" do
5968
subject
6069
expect(last_response).to be_a_hal_json_success_response
6170
end
6271

6372
it "generates a JSON response body for the execution result" do
6473
allow(PactBroker.configuration).to receive(:show_webhook_response?).and_return('foo')
65-
expect(decorator).to receive(:to_json).with(user_options: { base_url: 'http://example.org', webhook: webhook, show_response: 'foo' })
74+
expect(decorator).to receive(:to_json).with(user_options: expected_user_options)
6675
subject
6776
end
6877

0 commit comments

Comments
 (0)