Skip to content

Commit 356c023

Browse files
committed
feat(verification webhooks): add events to webhooks to allow differentiation between pact and verification webhooks
1 parent 528034c commit 356c023

15 files changed

+175
-41
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
require_relative 'migration_helper'
2+
3+
Sequel.migration do
4+
change do
5+
create_table(:webhook_events, charset: 'utf8') do
6+
primary_key :id
7+
foreign_key :webhook_id, :webhooks, on_delete: :cascade
8+
String :name
9+
DateTime :created_at, null: false
10+
DateTime :updated_at, null: false
11+
index [:id, :name], unique: true, name: 'uq_webhook_id_name'
12+
end
13+
end
14+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
require_relative 'migration_helper'
2+
3+
Sequel.migration do
4+
up do
5+
from(:webhooks).each do | webhook |
6+
from(:webhook_events).insert(
7+
webhook_id: webhook[:id],
8+
name: 'contract_changed',
9+
created_at: DateTime.now,
10+
updated_at: DateTime.now
11+
)
12+
end
13+
end
14+
15+
down do
16+
17+
end
18+
end

lib/pact_broker/api/contracts/webhook_contract.rb

+10-1
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ module PactBroker
55
module Api
66
module Contracts
77
class WebhookContract < Reform::Form
8-
property :request
98

109
validation do
1110
configure do
1211
config.messages_file = File.expand_path("../../../locale/en.yml", __FILE__)
1312
end
1413

1514
required(:request).filled
15+
required(:events).maybe(min_size?: 1)
1616
end
1717

1818
property :request do
@@ -39,6 +39,15 @@ def valid_url?(value)
3939
required(:url).filled(:valid_url?)
4040
end
4141
end
42+
43+
collection :events do
44+
property :name
45+
46+
validation do
47+
required(:name).filled
48+
end
49+
end
50+
4251
end
4352
end
4453
end

lib/pact_broker/api/decorators/webhook_decorator.rb

+27-5
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,24 @@
22
require 'pact_broker/api/decorators/webhook_request_decorator'
33
require 'pact_broker/api/decorators/timestamps'
44
require 'pact_broker/domain/webhook_request'
5+
require 'pact_broker/webhooks/webhook_event'
56
require 'pact_broker/api/decorators/basic_pacticipant_decorator'
7+
require_relative 'pact_pacticipant_decorator'
8+
require_relative 'pacticipant_decorator'
69

710
module PactBroker
811
module Api
912
module Decorators
1013
class WebhookDecorator < BaseDecorator
1114

12-
property :request, :class => PactBroker::Domain::WebhookRequest, :extend => WebhookRequestDecorator
15+
class WebhookEventDecorator < BaseDecorator
16+
property :name
17+
end
1318

14-
include Timestamps
19+
property :request, :class => PactBroker::Domain::WebhookRequest, extend: WebhookRequestDecorator
20+
collection :events, :class => PactBroker::Webhooks::WebhookEvent, extend: WebhookEventDecorator
1521

16-
property :consumer, :extend => PactBroker::Api::Decorators::BasicPacticipantDecorator, :embedded => true, writeable: false
17-
property :provider, :extend => PactBroker::Api::Decorators::BasicPacticipantDecorator, :embedded => true, writeable: false
22+
include Timestamps
1823

1924
link :self do | options |
2025
{
@@ -31,9 +36,26 @@ class WebhookDecorator < BaseDecorator
3136
}
3237
end
3338

39+
40+
link :'pb:consumer' do | options |
41+
{
42+
title: "Consumer",
43+
name: represented.consumer.name,
44+
href: pacticipant_url(options.fetch(:base_url), represented.consumer)
45+
}
46+
end
47+
48+
link :'pb:provider' do | options |
49+
{
50+
title: "Provider",
51+
name: represented.provider.name,
52+
href: pacticipant_url(options.fetch(:base_url), represented.provider)
53+
}
54+
end
55+
3456
link :'pb:pact-webhooks' do | options |
3557
{
36-
title: "All webhooks for the pact between #{represented.consumer.name} and #{represented.provider.name}",
58+
title: "All webhooks for consumer #{represented.consumer.name} and provider #{represented.provider.name}",
3759
href: webhooks_for_pact_url(represented.consumer, represented.provider, options[:base_url])
3860
}
3961
end

lib/pact_broker/domain/webhook.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class Webhook
1010
include Messages
1111
include Logging
1212

13-
attr_accessor :uuid, :consumer, :provider, :request, :created_at, :updated_at
13+
attr_accessor :uuid, :consumer, :provider, :request, :created_at, :updated_at, :events
1414
attr_reader :attributes
1515

1616
def initialize attributes = {}
@@ -19,6 +19,7 @@ def initialize attributes = {}
1919
@request = attributes[:request]
2020
@consumer = attributes[:consumer]
2121
@provider = attributes[:provider]
22+
@events = attributes[:events]
2223
@created_at = attributes[:created_at]
2324
@updated_at = attributes[:updated_at]
2425
end

lib/pact_broker/webhooks/repository.rb

+8
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
require 'pact_broker/domain/pacticipant'
44
require 'pact_broker/db'
55
require 'pact_broker/webhooks/webhook'
6+
require 'pact_broker/webhooks/webhook_event'
67
require 'pact_broker/webhooks/triggered_webhook'
78
require 'pact_broker/webhooks/latest_triggered_webhook'
89
require 'pact_broker/webhooks/execution'
@@ -21,6 +22,9 @@ def create uuid, webhook, consumer, provider
2122
webhook.request.headers.each_pair do | name, value |
2223
db_webhook.add_header PactBroker::Webhooks::WebhookHeader.from_domain(name, value, db_webhook.id)
2324
end
25+
(webhook.events || []).each do | webhook_event |
26+
db_webhook.add_event(webhook_event)
27+
end
2428
find_by_uuid db_webhook.uuid
2529
end
2630

@@ -32,9 +36,13 @@ def update_by_uuid uuid, webhook
3236
existing_webhook = Webhook.find(uuid: uuid)
3337
existing_webhook.update_from_domain(webhook).save
3438
existing_webhook.headers.collect(&:delete)
39+
existing_webhook.events.collect(&:delete)
3540
webhook.request.headers.each_pair do | name, value |
3641
existing_webhook.add_header PactBroker::Webhooks::WebhookHeader.from_domain(name, value, existing_webhook.id)
3742
end
43+
(webhook.events || []).each do | webhook_event |
44+
existing_webhook.add_event(webhook_event)
45+
end
3846
find_by_uuid uuid
3947
end
4048

lib/pact_broker/webhooks/webhook.rb

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ class Webhook < Sequel::Model
99
set_primary_key :id
1010
associate(:many_to_one, :provider, :class => "PactBroker::Domain::Pacticipant", :key => :provider_id, :primary_key => :id)
1111
associate(:many_to_one, :consumer, :class => "PactBroker::Domain::Pacticipant", :key => :consumer_id, :primary_key => :id)
12+
13+
one_to_many :events, :class => "PactBroker::Webhooks::WebhookEvent", :reciprocal => :webhook
1214
one_to_many :headers, :class => "PactBroker::Webhooks::WebhookHeader", :reciprocal => :webhook
1315

1416
dataset_module do
@@ -41,6 +43,7 @@ def to_domain
4143
uuid: uuid,
4244
consumer: consumer,
4345
provider: provider,
46+
events: events,
4447
request: Domain::WebhookRequest.new(request_attributes),
4548
created_at: created_at,
4649
updated_at: updated_at)
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
require 'sequel'
2+
require 'pact_broker/repositories/helpers'
3+
4+
module PactBroker
5+
module Webhooks
6+
class WebhookEvent < Sequel::Model
7+
8+
dataset_module do
9+
include PactBroker::Repositories::Helpers
10+
end
11+
12+
end
13+
14+
WebhookEvent.plugin :timestamps, update_on_create: true
15+
end
16+
end

spec/features/create_webhook_spec.rb

+4-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
describe "Creating a webhook" do
44

55
before do
6-
TestDataBuilder.new.create_pact_with_hierarchy("Some Consumer", "1", "Some Provider").and_return(:pact)
6+
TestDataBuilder.new.create_pact_with_hierarchy("Some Consumer", "1", "Some Provider")
77
end
88

99
let(:path) { "/webhooks/provider/Some%20Provider/consumer/Some%20Consumer" }
@@ -38,6 +38,9 @@
3838

3939
let(:webhook_hash) do
4040
{
41+
events: [{
42+
name: 'something_happened'
43+
}],
4144
request: {
4245
method: 'POST',
4346
url: 'http://example.org',

spec/fixtures/webhook_valid.json

+3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
{
2+
"events": [{
3+
"name": "something_happened"
4+
}],
25
"request": {
36
"method": "POST",
47
"url": "http://some.url",

spec/lib/pact_broker/api/contracts/webhook_contract_spec.rb

+20
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,26 @@ def valid_webhook_with
3737
end
3838
end
3939

40+
context "with no events defined" do
41+
let(:json) { {}.to_json }
42+
43+
it "contains an error for missing request, I wish I could work out how not to have the second error" do
44+
expect(subject.errors[:events]).to eq ["is missing", "size cannot be less than 1"]
45+
end
46+
end
47+
48+
context "with empty events" do
49+
let(:json) do
50+
valid_webhook_with do |hash|
51+
hash['events'] = []
52+
end
53+
end
54+
55+
it "contains an error for missing request" do
56+
expect(subject.errors[:events]).to eq ["size cannot be less than 1"]
57+
end
58+
end
59+
4060
context "with no method" do
4161
let(:json) do
4262
valid_webhook_with do |hash|

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

+18-19
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ module Decorators
2121

2222
let(:consumer) { Domain::Pacticipant.new(name: 'Consumer') }
2323
let(:provider) { Domain::Pacticipant.new(name: 'Provider') }
24+
let(:event) { Webhooks::WebhookEvent.new(name: 'something_happened') }
2425
let(:created_at) { DateTime.now }
2526
let(:updated_at) { created_at + 1 }
2627

@@ -30,6 +31,7 @@ module Decorators
3031
uuid: 'some-uuid',
3132
consumer: consumer,
3233
provider: provider,
34+
events: [event],
3335
created_at: created_at,
3436
updated_at: updated_at
3537
)
@@ -44,26 +46,14 @@ module Decorators
4446
expect(parsed_json[:request]).to eq request
4547
end
4648

47-
it 'includes an embedded consumer' do
48-
expect(parsed_json[:_embedded][:consumer]).to eq ({
49-
name: 'Consumer',
50-
_links: {
51-
self: {
52-
href: 'http://example.org/pacticipants/Consumer'
53-
}
54-
}
55-
})
49+
it 'includes a link to the consumer' do
50+
expect(parsed_json[:_links][:'pb:consumer'][:name]).to eq 'Consumer'
51+
expect(parsed_json[:_links][:'pb:consumer'][:href]).to eq 'http://example.org/pacticipants/Consumer'
5652
end
5753

58-
it 'includes an embedded provider' do
59-
expect(parsed_json[:_embedded][:provider]).to eq ({
60-
name: 'Provider',
61-
_links: {
62-
self: {
63-
href: 'http://example.org/pacticipants/Provider'
64-
}
65-
}
66-
})
54+
it 'includes a link to the provider' do
55+
expect(parsed_json[:_links][:'pb:provider'][:name]).to eq 'Provider'
56+
expect(parsed_json[:_links][:'pb:provider'][:href]).to eq 'http://example.org/pacticipants/Provider'
6757
end
6858

6959
it 'includes a link to itself' do
@@ -83,6 +73,10 @@ module Decorators
8373
expect(parsed_json[:_links][:'pb:execute'][:href]).to eq 'http://example.org/webhooks/some-uuid/execute'
8474
end
8575

76+
it 'includes the events' do
77+
expect(parsed_json[:events].first).to eq name: 'something_happened'
78+
end
79+
8680
it 'includes timestamps' do
8781
expect(parsed_json[:createdAt]).to eq created_at.xmlschema
8882
expect(parsed_json[:updatedAt]).to eq updated_at.xmlschema
@@ -97,7 +91,8 @@ module Decorators
9791
end
9892

9993
describe 'from_json' do
100-
let(:hash) { { request: request } }
94+
let(:hash) { { request: request, events: [event] } }
95+
let(:event) { {name: 'something_happened'} }
10196
let(:json) { hash.to_json }
10297
let(:webhook) { Domain::Webhook.new }
10398
let(:parsed_object) { subject.from_json(json) }
@@ -117,6 +112,10 @@ module Decorators
117112
it 'parses the request body' do
118113
expect(parsed_object.request.body).to eq 'some' => 'body'
119114
end
115+
116+
it 'parses the events' do
117+
expect(parsed_object.events.first.name).to eq 'something_happened'
118+
end
120119
end
121120
end
122121
end

0 commit comments

Comments
 (0)