Skip to content

Commit

Permalink
feat(webhooks): allow testing of an unsaved webhook
Browse files Browse the repository at this point in the history
  • Loading branch information
bethesque committed Jun 21, 2019
1 parent de0d3b7 commit a436e42
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 55 deletions.
1 change: 1 addition & 0 deletions lib/pact_broker/api.rb
Expand Up @@ -71,6 +71,7 @@ module PactBroker
add ['pacts', 'provider', :provider_name, 'consumer', :consumer_name, 'webhooks', 'status'], Api::Resources::PactWebhooksStatus, {resource_name: "pact_webhooks_status"}
add ['pacts', 'provider', :provider_name, 'consumer', :consumer_name, 'version', :consumer_version_number, 'triggered-webhooks'], Api::Resources::PactTriggeredWebhooks, {resource_name: "pact_triggered_webhooks"}

add ['webhooks', 'execute' ], Api::Resources::WebhookExecution, {resource_name: "execute_unsaved_webhook"}
add ['webhooks', :uuid ], Api::Resources::Webhook, {resource_name: "webhook"}
add ['webhooks', :uuid, 'trigger', :trigger_uuid, 'logs' ], Api::Resources::TriggeredWebhookLogs, {resource_name: "triggered_webhook_logs"}
add ['webhooks', :uuid, 'execute' ], Api::Resources::WebhookExecution, {resource_name: "execute_webhook"}
Expand Down
10 changes: 5 additions & 5 deletions lib/pact_broker/api/decorators/decorator_context.rb
Expand Up @@ -11,17 +11,17 @@ def initialize base_url, resource_url, options = {}
self[:base_url] = base_url
@resource_url = resource_url
self[:resource_url] = resource_url
@resource_title = options[:resource_title]
self[:resource_title] = resource_title
if options[:resource_title]
@resource_title = options[:resource_title]
self[:resource_title] = resource_title
end
merge!(options)
end

def to_s
"DecoratorContext #{super}"
end

end

end
end
end
end
Expand Up @@ -35,7 +35,6 @@ def url
end
end


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

link :webhook do | options |
{
href: webhook_url(options.fetch(:webhook).uuid, options.fetch(:base_url))
}
if options.fetch(:webhook).uuid
{
href: webhook_url(options.fetch(:webhook).uuid, options.fetch(:base_url))
}
end
end

link :'try-again' do | options |
{
title: 'Execute the webhook again',
href: webhook_execution_url(options.fetch(:webhook), options.fetch(:base_url))
href: options.fetch(:resource_url)
}
end

Expand Down
12 changes: 4 additions & 8 deletions lib/pact_broker/api/resources/webhook.rb
@@ -1,13 +1,15 @@
require 'pact_broker/api/resources/base_resource'
require 'pact_broker/services'
require 'pact_broker/api/decorators/webhook_decorator'
require 'pact_broker/api/resources/webhook_resource_methods'

module PactBroker
module Api
module Resources

class Webhook < BaseResource

include WebhookResourceMethods

def content_types_accepted
[["application/json", :from_json]]
end
Expand All @@ -26,7 +28,7 @@ def resource_exists?

def malformed_request?
if request.put?
return invalid_json? || validation_errors?(webhook)
return invalid_json? || webhook_validation_errors?(new_webhook)
end
false
end
Expand All @@ -51,12 +53,6 @@ def delete_resource

private

def validation_errors? webhook
errors = webhook_service.errors(new_webhook)
set_json_validation_error_messages(errors.messages) if !errors.empty?
!errors.empty?
end

def webhook
@webhook ||= webhook_service.find_by_uuid uuid
end
Expand Down
35 changes: 31 additions & 4 deletions lib/pact_broker/api/resources/webhook_execution.rb
@@ -1,12 +1,22 @@
require 'pact_broker/api/resources/base_resource'
require 'pact_broker/services'
require 'pact_broker/api/decorators/webhook_execution_result_decorator'
require 'pact_broker/api/resources/webhook_resource_methods'
require 'pact_broker/constants'

module PactBroker
module Api
module Resources
class WebhookExecution < BaseResource
include WebhookResourceMethods

def content_types_accepted
[["application/json"]]
end

def content_types_provided
[["application/hal+json"]]
end

def allowed_methods
["POST", "OPTIONS"]
Expand All @@ -23,26 +33,39 @@ def resource_exists?
webhook
end

def malformed_request?
if uuid
false
else
webhook_validation_errors?(webhook)
end
end

private

def post_response_body webhook_execution_result
Decorators::WebhookExecutionResultDecorator.new(webhook_execution_result).to_json(user_options: user_options)
end

def webhook
@webhook ||= webhook_service.find_by_uuid uuid
@webhook ||= begin
if uuid
webhook_service.find_by_uuid uuid
else
build_unsaved_webhook
end
end
end

def uuid
identifier_from_path[:uuid]
end

def user_options
{
base_url: base_url,
decorator_context(
webhook: webhook,
show_response: PactBroker.configuration.show_webhook_response?
}
)
end

def webhook_options
Expand All @@ -55,6 +78,10 @@ def webhook_options
}
}
end

def build_unsaved_webhook
Decorators::WebhookDecorator.new(PactBroker::Domain::Webhook.new).from_json(request_body)
end
end
end
end
Expand Down
23 changes: 8 additions & 15 deletions lib/pact_broker/api/resources/webhook_resource_methods.rb
@@ -1,24 +1,17 @@
module PactBroker
module Api

module Resources

module WebhookResourceMethods

def malformed_webhook_request? webhook
begin
if (errors = webhook.validate).any?
set_json_validation_error_messages errors
return true
end
rescue
set_json_error_message 'Invalid JSON'
return true
def webhook_validation_errors? webhook
errors = webhook_service.errors(webhook)
if !errors.empty?
set_json_validation_error_messages(errors.messages)
true
else
false
end
false
end
end

end
end
end
end
15 changes: 3 additions & 12 deletions lib/pact_broker/api/resources/webhooks.rb
Expand Up @@ -2,11 +2,13 @@
require 'pact_broker/api/decorators/webhook_decorator'
require 'pact_broker/api/decorators/webhooks_decorator'
require 'pact_broker/api/contracts/webhook_contract'
require 'pact_broker/api/resources/webhook_resource_methods'

module PactBroker
module Api
module Resources
class Webhooks < BaseResource
include WebhookResourceMethods

def allowed_methods
["POST", "GET", "OPTIONS"]
Expand All @@ -27,22 +29,11 @@ def resource_exists?

def malformed_request?
if request.post?
return invalid_json? || validation_errors?(webhook)
return invalid_json? || webhook_validation_errors?(webhook)
end
false
end

def validation_errors? webhook
errors = webhook_service.errors(webhook)

unless errors.empty?
response.headers['Content-Type'] = 'application/hal+json;charset=utf-8'
response.body = { errors: errors.messages }.to_json
end

!errors.empty?
end

def create_path
webhook_url next_uuid, base_url
end
Expand Down
56 changes: 56 additions & 0 deletions spec/features/execute_unsaved_webhook_spec.rb
@@ -0,0 +1,56 @@
require 'support/test_data_builder'
require 'webmock/rspec'
require 'rack/pact_broker/database_transaction'

describe "Execute a webhook" do

let(:td) { TestDataBuilder.new }

before do
td.create_pact_with_hierarchy("Foo", "1", "Bar")
allow(PactBroker.configuration).to receive(:webhook_scheme_whitelist).and_return(%w[http])
end

let(:params) do
{
request: {
method: 'POST',
url: 'http://example.org',
headers: {'Content-Type' => 'application/json'},
body: '${pactbroker.pactUrl}'
}
}
end
let(:rack_headers) { { "CONTENT_TYPE" => "application/json", "HTTP_ACCEPT" => "application/hal+json" } }

let(:path) { "/webhooks/execute" }
let(:response_body) { JSON.parse(last_response.body, symbolize_names: true)}

subject { post(path, params.to_json, rack_headers) }

context "when the execution is successful" do
let!(:request) do
stub_request(:post, /http/).with(body: expected_webhook_url).to_return(:status => 200, body: response_body)
end

let(:expected_webhook_url) { %r{http://example.org/pacts/provider/Bar/consumer/Foo.*} }
let(:response_body) { "webhook-response-body" }

it "performs the HTTP request" do
subject
expect(request).to have_been_made
end

it "returns a 200 response" do
expect(subject.status).to be 200
end
end

context "when there is a validation error" do
let(:params) { {} }

it "returns a 400" do
expect(subject.status).to be 400
end
end
end
Expand Up @@ -20,23 +20,35 @@ module Decorators
let(:response) { double('http_response', code: '200', body: response_body, to_hash: headers) }
let(:response_body) { 'body' }
let(:error) { nil }
let(:webhook) { instance_double(PactBroker::Domain::Webhook, uuid: 'some-uuid') }
let(:webhook) { instance_double(PactBroker::Domain::Webhook, uuid: uuid) }
let(:uuid) { 'some-uuid' }
let(:show_response) { true }
let(:json) {
WebhookExecutionResultDecorator.new(webhook_execution_result)
.to_json(user_options: { base_url: 'http://example.org', webhook: webhook, show_response: show_response })
.to_json(user_options: { resource_url: 'http://resource-url', base_url: 'http://example.org', webhook: webhook, show_response: show_response })
}

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

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

it "includes a link to the webhook" do
expect(subject[:_links][:webhook][:href]).to eq 'http://example.org/webhooks/some-uuid'
context "when there is a uuid" do
it "include a link to the webhook" do
expect(subject[:_links][:webhook][:href]).to eq 'http://example.org/webhooks/some-uuid'
end
end

context "when there is a not uuid because this is an unsaved webhook" do
let(:uuid) { nil }

it "does not includes a link to the webhook" do
expect(subject[:_links]).to_not have_key(:webhook)
end
end


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

Expand Down
11 changes: 10 additions & 1 deletion spec/lib/pact_broker/api/resources/webhook_execution_spec.rb
Expand Up @@ -55,14 +55,23 @@ module Resources
end

context "when execution is successful" do
let(:expected_user_options) do
{
resource_url: 'http://example.org/webhooks/some-uuid/execute',
base_url: 'http://example.org',
webhook: webhook,
show_response: 'foo',
}
end

it "returns a 200 JSON response" do
subject
expect(last_response).to be_a_hal_json_success_response
end

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

Expand Down

0 comments on commit a436e42

Please sign in to comment.