From b15ba85c1bc956712ed34aaca5d62bb4eb61b7cd Mon Sep 17 00:00:00 2001 From: Beth Skurrie Date: Sat, 16 Jun 2018 16:14:12 +1000 Subject: [PATCH] feat: allow webhook with optional consumer and/or provider to be created by posting to /webhooks --- lib/pact_broker/api/resources/all_webhooks.rb | 56 ++++++++- lib/pact_broker/domain/webhook.rb | 4 +- spec/features/create_webhook_spec.rb | 28 ++++- .../api/resources/all_webhooks_spec.rb | 110 ++++++++++++++++++ spec/lib/pact_broker/domain/webhook_spec.rb | 7 ++ 5 files changed, 200 insertions(+), 5 deletions(-) diff --git a/lib/pact_broker/api/resources/all_webhooks.rb b/lib/pact_broker/api/resources/all_webhooks.rb index da199612a..46584d517 100644 --- a/lib/pact_broker/api/resources/all_webhooks.rb +++ b/lib/pact_broker/api/resources/all_webhooks.rb @@ -1,6 +1,8 @@ require 'pact_broker/services' require 'pact_broker/api/resources/base_resource' require 'pact_broker/api/decorators/webhooks_decorator' +require 'pact_broker/api/decorators/webhook_decorator' +require 'pact_broker/api/contracts/webhook_contract' module PactBroker module Api @@ -11,17 +13,69 @@ def content_types_provided [["application/hal+json", :to_json]] end + def content_types_accepted + [["application/json", :from_json]] + end + def allowed_methods - ["GET"] + ["GET", "POST"] + end + + def create_path + webhook_url next_uuid, base_url + end + + def post_is_create? + true + end + + def malformed_request? + if request.post? + return invalid_json? || validation_errors?(webhook) + end + false end def to_json Decorators::WebhooksDecorator.new(webhooks).to_json(user_options: decorator_context(resource_title: "Webhooks")) end + def from_json + saved_webhook = webhook_service.create next_uuid, webhook, consumer, provider + response.body = Decorators::WebhookDecorator.new(saved_webhook).to_json(user_options: { base_url: base_url }) + end + + private + + def validation_errors? webhook + errors = webhook_service.errors(webhook) + + unless errors.empty? + set_json_validation_error_messages(errors.messages) + end + + !errors.empty? + end + + def consumer + webhook.consumer ? pacticipant_service.find_pacticipant_by_name(webhook.consumer.name) : nil + end + + def provider + webhook.provider ? pacticipant_service.find_pacticipant_by_name(webhook.provider.name) : nil + end + def webhooks webhook_service.find_all end + + def webhook + @webhook ||= Decorators::WebhookDecorator.new(PactBroker::Domain::Webhook.new).from_json(request_body) + end + + def next_uuid + @next_uuid ||= webhook_service.next_uuid + end end end end diff --git a/lib/pact_broker/domain/webhook.rb b/lib/pact_broker/domain/webhook.rb index 349ad01f8..9baa347c0 100644 --- a/lib/pact_broker/domain/webhook.rb +++ b/lib/pact_broker/domain/webhook.rb @@ -29,8 +29,10 @@ def description "A webhook for the pact between #{consumer.name} and #{provider.name}" elsif provider "A webhook for all pacts with provider #{provider.name}" - else + elsif consumer "A webhook for all pacts with consumer #{consumer.name}" + else + "A webhook for all pacts" end end diff --git a/spec/features/create_webhook_spec.rb b/spec/features/create_webhook_spec.rb index 24d9e421e..74c146451 100644 --- a/spec/features/create_webhook_spec.rb +++ b/spec/features/create_webhook_spec.rb @@ -27,7 +27,7 @@ } end - subject { post path, webhook_json, headers } + subject { post(path, webhook_json, headers) } context "for a consumer and provider" do let(:path) { "/webhooks/provider/Some%20Provider/consumer/Some%20Consumer" } @@ -75,7 +75,11 @@ end context "for a provider" do - let(:path) { "/webhooks/provider/Some%20Provider" } + let(:path) { "/webhooks" } + + before do + webhook_hash[:provider] = { name: "Some Provider" } + end it "returns a 201 response" do subject @@ -90,7 +94,10 @@ end context "for a consumer" do - let(:path) { "/webhooks/consumer/Some%20Consumer" } + let(:path) { "/webhooks" } + before do + webhook_hash[:consumer] = { name: "Some Consumer" } + end it "returns a 201 response" do subject @@ -103,4 +110,19 @@ expect(PactBroker::Webhooks::Webhook.first.provider).to be nil end end + + context "with no consumer or provider" do + let(:path) { "/webhooks" } + + it "returns a 201 response" do + subject + expect(last_response.status).to be 201 + end + + it "creates a webhook without a provider" do + subject + expect(PactBroker::Webhooks::Webhook.first.consumer).to be nil + expect(PactBroker::Webhooks::Webhook.first.provider).to be nil + end + end end diff --git a/spec/lib/pact_broker/api/resources/all_webhooks_spec.rb b/spec/lib/pact_broker/api/resources/all_webhooks_spec.rb index aa1c44638..a9da26041 100644 --- a/spec/lib/pact_broker/api/resources/all_webhooks_spec.rb +++ b/spec/lib/pact_broker/api/resources/all_webhooks_spec.rb @@ -7,6 +7,116 @@ module Resources describe AllWebhooks do + let(:webhook_service) { PactBroker::Webhooks::Service } + let(:uuid) { '1483234k24DKFGJ45K' } + let(:path) { "/webhooks" } + let(:headers) { {'CONTENT_TYPE' => 'application/json'} } + let(:webhook) { double('webhook', consumer: parsed_consumer, provider: parsed_provider) } + let(:parsed_provider) { instance_double(PactBroker::Domain::Pacticipant, name: "Some Provider") } + let(:parsed_consumer) { instance_double(PactBroker::Domain::Pacticipant, name: "Some Consumer") } + let(:consumer) { double('consumer', name: "Some Consumer") } + let(:provider) { double('provider', name: "Some Provider") } + let(:saved_webhook) { double('saved_webhook')} + let(:webhook_decorator) { instance_double(Decorators::WebhookDecorator, from_json: webhook) } + + before do + allow(PactBroker::Pacticipants::Service).to receive(:find_pacticipant_by_name).with("Some Provider").and_return(provider) + allow(PactBroker::Pacticipants::Service).to receive(:find_pacticipant_by_name).with("Some Consumer").and_return(consumer) + allow(Decorators::WebhookDecorator).to receive(:new).and_return(webhook_decorator) + end + + describe "POST" do + let(:webhook_json) do + { + some: 'json' + }.to_json + end + + let(:next_uuid) { '123k2nvkkwjrwk34' } + let(:valid) { true } + let(:errors) { double("errors", empty?: valid, messages: ['messages']) } + + before do + allow(webhook_service).to receive(:create).and_return(saved_webhook) + allow(webhook_service).to receive(:next_uuid).and_return(next_uuid) + allow(webhook_service).to receive(:errors).and_return(errors) + allow(PactBroker::Domain::Webhook).to receive(:new).and_return(webhook) + end + + subject { post path, webhook_json, headers } + + context "with malformed JSON" do + let(:webhook_json) { "{" } + + it "returns a 400 error" do + subject + expect(last_response.status).to eq 400 + end + end + + context "with invalid attributes" do + + let(:valid) { false } + + it "returns a 400" do + subject + expect(last_response.status).to be 400 + end + + it "returns a HAL JSON content type" do + subject + expect(last_response.headers['Content-Type']).to eq 'application/hal+json;charset=utf-8' + end + + it "returns the validation errors" do + subject + expect(JSON.parse(last_response.body, symbolize_names: true)).to eq errors: ['messages'] + end + + end + + context "with valid attributes" do + + let(:webhook_response_json) { { some: 'webhook' }.to_json } + + before do + allow_any_instance_of(Decorators::WebhookDecorator).to receive(:to_json).and_return(webhook_response_json) + allow(webhook_decorator).to receive(:to_json).and_return(webhook_response_json) + end + + it "saves the webhook" do + expect(webhook_service).to receive(:create).with(next_uuid, webhook, consumer, provider) + subject + end + + it "returns a 201 response" do + subject + expect(last_response.status).to be 201 + end + + it "returns the Location header" do + subject + expect(last_response.headers['Location']).to include(next_uuid) + end + + it "returns a HAL JSON content type" do + subject + expect(last_response.headers['Content-Type']).to eq 'application/hal+json;charset=utf-8' + end + + it "generates the JSON response body" do + expect(Decorators::WebhookDecorator).to receive(:new).with(saved_webhook).and_return(webhook_decorator) + expect(webhook_decorator).to receive(:to_json).with(user_options: { base_url: 'http://example.org' }) + subject + end + + it "returns the JSON representation of the webhook" do + subject + expect(last_response.body).to eq webhook_response_json + end + end + end + describe "GET" do subject { get "/webhooks" } diff --git a/spec/lib/pact_broker/domain/webhook_spec.rb b/spec/lib/pact_broker/domain/webhook_spec.rb index eadafdf4c..082dc3dec 100644 --- a/spec/lib/pact_broker/domain/webhook_spec.rb +++ b/spec/lib/pact_broker/domain/webhook_spec.rb @@ -30,6 +30,13 @@ module Domain it { is_expected.to eq "A webhook for all pacts with provider Provider" } end + + context "with neither a consumer nor a provider" do + let(:consumer) { nil } + let(:provider) { nil } + + it { is_expected.to eq "A webhook for all pacts" } + end end describe "execute" do