diff --git a/lib/pact_broker/api.rb b/lib/pact_broker/api.rb index 8e188548b..9c26a0120 100644 --- a/lib/pact_broker/api.rb +++ b/lib/pact_broker/api.rb @@ -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"} diff --git a/lib/pact_broker/api/decorators/decorator_context.rb b/lib/pact_broker/api/decorators/decorator_context.rb index 420e08df4..717e37e24 100644 --- a/lib/pact_broker/api/decorators/decorator_context.rb +++ b/lib/pact_broker/api/decorators/decorator_context.rb @@ -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 \ No newline at end of file +end diff --git a/lib/pact_broker/api/decorators/webhook_execution_result_decorator.rb b/lib/pact_broker/api/decorators/webhook_execution_result_decorator.rb index ac29bef76..3c38aa177 100644 --- a/lib/pact_broker/api/decorators/webhook_execution_result_decorator.rb +++ b/lib/pact_broker/api/decorators/webhook_execution_result_decorator.rb @@ -35,7 +35,6 @@ def url end end - class HTTPResponseDecorator < BaseDecorator property :status, :getter => lambda { |_| code.to_i } property :headers, exec_context: :decorator @@ -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 diff --git a/lib/pact_broker/api/resources/webhook.rb b/lib/pact_broker/api/resources/webhook.rb index 4d5ce640a..0eb41c486 100644 --- a/lib/pact_broker/api/resources/webhook.rb +++ b/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 @@ -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 @@ -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 diff --git a/lib/pact_broker/api/resources/webhook_execution.rb b/lib/pact_broker/api/resources/webhook_execution.rb index 44e199cda..22d21acfb 100644 --- a/lib/pact_broker/api/resources/webhook_execution.rb +++ b/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"] @@ -23,6 +33,14 @@ 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 @@ -30,7 +48,13 @@ def post_response_body webhook_execution_result 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 @@ -38,11 +62,10 @@ def uuid end def user_options - { - base_url: base_url, + decorator_context( webhook: webhook, show_response: PactBroker.configuration.show_webhook_response? - } + ) end def webhook_options @@ -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 diff --git a/lib/pact_broker/api/resources/webhook_resource_methods.rb b/lib/pact_broker/api/resources/webhook_resource_methods.rb index c6234c05f..388ff9283 100644 --- a/lib/pact_broker/api/resources/webhook_resource_methods.rb +++ b/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 \ No newline at end of file +end diff --git a/lib/pact_broker/api/resources/webhooks.rb b/lib/pact_broker/api/resources/webhooks.rb index dfbc330a1..10c7a29e6 100644 --- a/lib/pact_broker/api/resources/webhooks.rb +++ b/lib/pact_broker/api/resources/webhooks.rb @@ -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"] @@ -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 diff --git a/spec/features/execute_unsaved_webhook_spec.rb b/spec/features/execute_unsaved_webhook_spec.rb new file mode 100644 index 000000000..eaaf8d4a7 --- /dev/null +++ b/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 diff --git a/spec/lib/pact_broker/api/decorators/webhook_execution_result_decorator_spec.rb b/spec/lib/pact_broker/api/decorators/webhook_execution_result_decorator_spec.rb index a96384696..88f1fef92 100644 --- a/spec/lib/pact_broker/api/decorators/webhook_execution_result_decorator_spec.rb +++ b/spec/lib/pact_broker/api/decorators/webhook_execution_result_decorator_spec.rb @@ -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']) } diff --git a/spec/lib/pact_broker/api/resources/webhook_execution_spec.rb b/spec/lib/pact_broker/api/resources/webhook_execution_spec.rb index bb6b318c0..43738632e 100644 --- a/spec/lib/pact_broker/api/resources/webhook_execution_spec.rb +++ b/spec/lib/pact_broker/api/resources/webhook_execution_spec.rb @@ -55,6 +55,15 @@ 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 @@ -62,7 +71,7 @@ module Resources 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