Skip to content

Commit

Permalink
feat(consumer or provider webhooks): allow a webhook to be defined fo…
Browse files Browse the repository at this point in the history
…r either a consumer OR provider
  • Loading branch information
bethesque committed Jun 16, 2018
1 parent 803c025 commit 37a62be
Show file tree
Hide file tree
Showing 15 changed files with 423 additions and 116 deletions.
48 changes: 48 additions & 0 deletions db/migrations/20180113_make_webhook_pacticipant_ids_optional.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
Sequel.migration do
up do
alter_table(:webhooks) do
set_column_allow_null(:consumer_id)
set_column_allow_null(:provider_id)
end

alter_table(:triggered_webhooks) do
set_column_allow_null(:consumer_id)
set_column_allow_null(:provider_id)
end

create_or_replace_view(:latest_triggered_webhooks,
"select tw.*
from triggered_webhooks tw
inner join latest_triggered_webhook_ids ltwi
on tw.consumer_id = ltwi.consumer_id
and tw.provider_id = ltwi.provider_id
and tw.webhook_uuid = ltwi.webhook_uuid
and tw.created_at = ltwi.latest_triggered_webhook_created_at
union
select tw.*
from triggered_webhooks tw
inner join latest_triggered_webhook_ids ltwi
on tw.consumer_id = ltwi.consumer_id
and tw.webhook_uuid = ltwi.webhook_uuid
and tw.created_at = ltwi.latest_triggered_webhook_created_at
where tw.provider_id is null
and ltwi.provider_id is null
union
select tw.*
from triggered_webhooks tw
inner join latest_triggered_webhook_ids ltwi
on tw.provider_id = ltwi.provider_id
and tw.webhook_uuid = ltwi.webhook_uuid
and tw.created_at = ltwi.latest_triggered_webhook_created_at
where tw.consumer_id is null
and ltwi.consumer_id is null"
)
end

down do
end
end
2 changes: 2 additions & 0 deletions lib/pact_broker/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ module PactBroker

# Webhooks
add ['webhooks', 'provider', :provider_name, 'consumer', :consumer_name ], Api::Resources::PactWebhooks, {resource_name: "pact_webhooks"}
add ['webhooks', 'provider', :provider_name], Api::Resources::PactWebhooks, {resource_name: "provider_webhooks"}
add ['webhooks', 'consumer', :consumer_name], Api::Resources::PactWebhooks, {resource_name: "consumer_webhooks"}
add ['webhooks', 'provider', :provider_name, 'consumer', :consumer_name, 'status' ], Api::Resources::PactWebhooksStatus, {resource_name: "pact_webhooks_status"}

add ['webhooks', :uuid ], Api::Resources::Webhook, {resource_name: "webhook"}
Expand Down
34 changes: 20 additions & 14 deletions lib/pact_broker/api/decorators/webhook_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,26 +38,32 @@ class WebhookEventDecorator < BaseDecorator


link :'pb:consumer' do | options |
{
title: "Consumer",
name: represented.consumer.name,
href: pacticipant_url(options.fetch(:base_url), represented.consumer)
}
if represented.consumer
{
title: "Consumer",
name: represented.consumer.name,
href: pacticipant_url(options.fetch(:base_url), represented.consumer)
}
end
end

link :'pb:provider' do | options |
{
title: "Provider",
name: represented.provider.name,
href: pacticipant_url(options.fetch(:base_url), represented.provider)
}
if represented.provider
{
title: "Provider",
name: represented.provider.name,
href: pacticipant_url(options.fetch(:base_url), represented.provider)
}
end
end

link :'pb:pact-webhooks' do | options |
{
title: "All webhooks for consumer #{represented.consumer.name} and provider #{represented.provider.name}",
href: webhooks_for_pact_url(represented.consumer, represented.provider, options[:base_url])
}
if represented.consumer && represented.provider
{
title: "All webhooks for consumer #{represented.consumer.name} and provider #{represented.provider.name}",
href: webhooks_for_pact_url(represented.consumer, represented.provider, options[:base_url])
}
end
end

link :'pb:webhooks' do | options |
Expand Down
7 changes: 4 additions & 3 deletions lib/pact_broker/api/resources/pact_webhooks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ def content_types_accepted
end

def resource_exists?
consumer && provider
(identifier_from_path[:consumer_name].nil? || consumer) &&
(identifier_from_path[:provider_name].nil? || provider)
end

def malformed_request?
Expand Down Expand Up @@ -77,11 +78,11 @@ def next_uuid
end

def consumer
@consumer ||= find_pacticipant(identifier_from_path[:consumer_name], "consumer")
@consumer ||= identifier_from_path[:consumer_name] && find_pacticipant(identifier_from_path[:consumer_name], "consumer")
end

def provider
@provider ||= find_pacticipant(identifier_from_path[:provider_name], "provider")
@provider ||= identifier_from_path[:provider_name] && find_pacticipant(identifier_from_path[:provider_name], "provider")
end

def find_pacticipant name, role
Expand Down
8 changes: 7 additions & 1 deletion lib/pact_broker/domain/webhook.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@ def initialize attributes = {}
end

def description
"A webhook for the pact between #{consumer.name} and #{provider.name}"
if consumer && provider
"A webhook for the pact between #{consumer.name} and #{provider.name}"
elsif provider
"A webhook for all pacts with provider #{provider.name}"
else
"A webhook for all pacts with consumer #{consumer.name}"
end
end

def request_description
Expand Down
41 changes: 38 additions & 3 deletions lib/pact_broker/webhooks/repository.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,38 @@ def find_all
Webhook.all.collect(&:to_domain)
end

def find_for_pact pact
find_by_consumer_and_provider(pact.consumer, pact.provider) +
find_by_consumer_and_provider(nil, pact.provider) +
find_by_consumer_and_provider(pact.consumer, nil)
end

def find_by_consumer_and_provider consumer, provider
Webhook.where(consumer_id: consumer.id, provider_id: provider.id).collect(&:to_domain)
criteria = {
consumer_id: (consumer ? consumer.id : nil),
provider_id: (provider ? provider.id : nil)
}
Webhook.where(criteria).collect(&:to_domain)
end

def find_for_pact_and_event_name pact, event_name
find_by_consumer_and_or_provider_and_event_name(pact.consumer, pact.provider, event_name)
end

def find_by_consumer_and_or_provider_and_event_name consumer, provider, event_name
find_by_consumer_and_provider_and_event_name(consumer, provider, event_name) +
find_by_consumer_and_provider_and_event_name(nil, provider, event_name) +
find_by_consumer_and_provider_and_event_name(consumer, nil, event_name)
end

def find_by_consumer_and_provider_and_event_name consumer, provider, event_name
criteria = {
consumer_id: (consumer ? consumer.id : nil),
provider_id: (provider ? provider.id : nil)
}
Webhook
.select_all_qualified
.where(consumer_id: consumer.id, provider_id: provider.id)
.where(criteria)
.join(:webhook_events, { webhook_id: :id })
.where(Sequel[:webhook_events][:name] => event_name)
.collect(&:to_domain)
Expand Down Expand Up @@ -136,9 +160,20 @@ def delete_triggered_webhooks_by_pact_publication_ids pact_publication_ids
DeprecatedExecution.where(pact_publication_id: pact_publication_ids).delete
end

def find_latest_triggered_webhooks_for_pact pact
find_latest_triggered_webhooks(pact.consumer, pact.provider) +
find_latest_triggered_webhooks(nil, pact.provider) +
find_latest_triggered_webhooks(pact.consumer, nil)
end

def find_latest_triggered_webhooks consumer, provider
# The manual grouping is just to get rid of any webhooks that triggered at the same time
criteria = {
consumer_id: consumer ? consumer.id : nil,
provider_id: provider ? provider.id : nil
}
LatestTriggeredWebhook
.where(consumer: consumer, provider: provider)
.where(criteria)
.order(:id)
.all
end
Expand Down
12 changes: 12 additions & 0 deletions lib/pact_broker/webhooks/service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ def self.update_triggered_webhook_status triggered_webhook, status
webhook_repository.update_triggered_webhook_status triggered_webhook, status
end

def self.find_for_pact pact
webhook_repository.find_for_pact(pact)
end

def self.find_by_consumer_and_or_provider consumer, provider
webhook_repository.find_by_consumer_and_or_provider(consumer, provider)
end

def self.find_by_consumer_and_provider consumer, provider
webhook_repository.find_by_consumer_and_provider consumer, provider
end
Expand Down Expand Up @@ -109,6 +117,10 @@ def self.run_later webhooks, pact, verification, event_name
end
end

def self.find_latest_triggered_webhooks_for_pact pact
webhook_repository.find_latest_triggered_webhooks_for_pact pact
end

def self.find_latest_triggered_webhooks consumer, provider
webhook_repository.find_latest_triggered_webhooks consumer, provider
end
Expand Down
4 changes: 2 additions & 2 deletions lib/pact_broker/webhooks/webhook.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ def self.from_domain webhook, consumer, provider
new(
properties_hash_from_domain(webhook).merge(uuid: webhook.uuid)
).tap do | db_webhook |
db_webhook.consumer_id = consumer.id
db_webhook.provider_id = provider.id
db_webhook.consumer_id = consumer.id if consumer
db_webhook.provider_id = provider.id if provider
end
end

Expand Down
101 changes: 64 additions & 37 deletions spec/features/create_webhook_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,74 +6,101 @@
TestDataBuilder.new.create_pact_with_hierarchy("Some Consumer", "1", "Some Provider")
end

let(:path) { "/webhooks/provider/Some%20Provider/consumer/Some%20Consumer" }
let(:headers) { {'CONTENT_TYPE' => 'application/json'} }
let(:response_body) { JSON.parse(last_response.body, symbolize_names: true)}
let(:webhook_json) { webhook_hash.to_json }
let(:webhook_hash) do
{
events: [{
name: 'something_happened'
}],
request: {
method: 'POST',
url: 'http://example.org',
headers: {
:"Content-Type" => "application/json"
},
body: {
a: 'body'
}
}
}
end

subject { post path, webhook_json, headers }

context "with invalid attributes" do
context "for a consumer and provider" do
let(:path) { "/webhooks/provider/Some%20Provider/consumer/Some%20Consumer" }

let(:webhook_hash) { {} }
context "with invalid attributes" do
let(:webhook_hash) { {} }

it "returns a 400" do
subject
expect(last_response.status).to be 400
end
it "returns a 400" do
subject
expect(last_response.status).to be 400
end

it "returns a 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(response_body[:errors]).to_not be_empty
it "returns the validation errors" do
subject
expect(response_body[:errors]).to_not be_empty
end
end

end

context "with valid attributes" do

let(:webhook_hash) do
{
events: [{
name: 'something_happened'
}],
request: {
method: 'POST',
url: 'https://example.org',
headers: {
:"Content-Type" => "application/json"
},
body: {
a: 'body'
}
}
}
context "with valid attributes" do
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 match(%r{http://example.org/webhooks/.+})
end

it "returns a JSON Content Type" do
subject
expect(last_response.headers['Content-Type']).to eq 'application/hal+json;charset=utf-8'
end

it "returns the newly created webhook" do
subject
expect(response_body).to include webhook_hash
end
end
end

let(:webhook_json) { webhook_hash.to_json }
context "for a provider" do
let(:path) { "/webhooks/provider/Some%20Provider" }

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

it "returns the Location header" do
it "creates a webhook without a consumer" do
subject
expect(last_response.headers['Location']).to match(%r{http://example.org/webhooks/.+})
expect(PactBroker::Webhooks::Webhook.first.provider).to_not be nil
expect(PactBroker::Webhooks::Webhook.first.consumer).to be nil
end
end

it "returns a JSON Content Type" do
context "for a consumer" do
let(:path) { "/webhooks/consumer/Some%20Consumer" }

it "returns a 201 response" do
subject
expect(last_response.headers['Content-Type']).to eq 'application/hal+json;charset=utf-8'
expect(last_response.status).to be 201
end

it "returns the newly created webhook" do
it "creates a webhook without a provider" do
subject
expect(response_body).to include webhook_hash
expect(PactBroker::Webhooks::Webhook.first.consumer).to_not be nil
expect(PactBroker::Webhooks::Webhook.first.provider).to be nil
end
end
end
Loading

0 comments on commit 37a62be

Please sign in to comment.