Skip to content

Commit 273078b

Browse files
committed
feat: use the latest matching pact or verification to test webhook execution, or a placeholder if either is not found
1 parent 69d5cb0 commit 273078b

File tree

17 files changed

+294
-85
lines changed

17 files changed

+294
-85
lines changed

lib/pact_broker/api/decorators/webhook_decorator.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class WebhookEventDecorator < BaseDecorator
3939

4040
link :'pb:execute' do | options |
4141
{
42-
title: "Test the execution of the webhook by sending a POST request to this URL",
42+
title: "Test the execution of the webhook with the latest matching pact or verification by sending a POST request to this URL",
4343
href: webhook_execution_url(represented, options[:base_url])
4444
}
4545
end

lib/pact_broker/api/resources/webhook_execution.rb

+1-5
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def allowed_methods
1414
end
1515

1616
def process_post
17-
webhook_execution_result = webhook_service.execute_webhook_now webhook, pact
17+
webhook_execution_result = webhook_service.test_execution(webhook)
1818
response.headers['Content-Type'] = 'application/hal+json;charset=utf-8'
1919
response.body = post_response_body webhook_execution_result
2020
if webhook_execution_result.success?
@@ -39,10 +39,6 @@ def webhook
3939
@webhook ||= webhook_service.find_by_uuid uuid
4040
end
4141

42-
def pact
43-
@pact ||= pact_service.find_latest_pact consumer_name: webhook.consumer_name, provider_name: webhook.provider_name
44-
end
45-
4642
def uuid
4743
identifier_from_path[:uuid]
4844
end

lib/pact_broker/doc/views/webhooks.markdown

+1-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ Pact Broker Github repository.
133133

134134
### Testing
135135

136-
To test a webhook, navigate to the webhook in the HAL browser, then make a POST request to the "execute" relation. The response or error will be shown in the window.
136+
To test a webhook, navigate to the webhook in the HAL browser, then make a POST request to the "pb:execute" relation. The latest matching pact/verification will be used in the template, or a placeholder if none exists. The response or error will be shown in the window.
137137

138138
### Deleting
139139

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
require 'pact_broker/domain/pact'
2+
3+
module PactBroker
4+
module Pacts
5+
class PlaceholderPact < PactBroker::Domain::Pact
6+
def initialize
7+
consumer = OpenStruct.new(name: "placeholder-consumer")
8+
@provider = OpenStruct.new(name: "placeholder-provider")
9+
@consumer_version = OpenStruct.new(number: "1", pacticipant: consumer, tags: [OpenStruct.new(name: "master")])
10+
@consumer_version_number = @consumer_version.number
11+
@created_at = DateTime.now
12+
@revision_number = 1
13+
@pact_version_sha = "5d445a4612743728dfd99ccd4210423c052bb9db"
14+
end
15+
end
16+
end
17+
end

lib/pact_broker/pacts/repository.rb

+14
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,20 @@ def find_latest_pact(consumer_name, provider_name, tag = nil)
121121
query.latest.all.collect(&:to_domain_with_content)[0]
122122
end
123123

124+
# Allows optional consumer_name and provider_name
125+
def search_for_latest_pact(consumer_name, provider_name, tag = nil)
126+
query = LatestPactPublicationsByConsumerVersion.select_all_qualified
127+
query = query.consumer(consumer_name) if consumer_name
128+
query = query.provider(provider_name) if provider_name
129+
130+
if tag == :untagged
131+
query = query.untagged
132+
elsif tag
133+
query = query.tag(tag)
134+
end
135+
query.latest.all.collect(&:to_domain_with_content)[0]
136+
end
137+
124138
def find_pact consumer_name, consumer_version, provider_name, pact_version_sha = nil
125139
query = if pact_version_sha
126140
AllPactPublications

lib/pact_broker/pacts/service.rb

+4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ def find_latest_pact params
1717
pact_repository.find_latest_pact(params[:consumer_name], params[:provider_name], params[:tag])
1818
end
1919

20+
def search_for_latest_pact params
21+
pact_repository.search_for_latest_pact(params[:consumer_name], params[:provider_name], params[:tag])
22+
end
23+
2024
def find_latest_pacts
2125
pact_repository.find_latest_pacts
2226
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
module PactBroker
2+
module Verifications
3+
class PlaceholderVerification
4+
attr_accessor :id, :number, :success,
5+
:consumer_name, :provider_name, :provider_version,
6+
:provider_version_number, :build_url,
7+
:execution_date, :created_at, :pact_version_sha
8+
9+
def initialize
10+
@provider_name = "placeholder-provider"
11+
@consumer_name = "placeholder-consumer"
12+
@number = 1
13+
@success = true
14+
@pact_version_sha = "5d445a4612743728dfd99ccd4210423c052bb9db"
15+
tags = [OpenStruct.new(name: "master")]
16+
@provider_version = OpenStruct.new(number: "aaaabbbbccccddddeeeeffff1111222233334444", tags: tags)
17+
@provider_version_number = @provider_version.number
18+
@execution_date = DateTime.now
19+
@created_at = DateTime.now
20+
end
21+
end
22+
end
23+
end

lib/pact_broker/verifications/repository.rb

+9
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,15 @@ def find consumer_name, provider_name, pact_version_sha, verification_number
3737
.verification_number(verification_number).single_record
3838
end
3939

40+
def search_for_latest consumer_name, provider_name
41+
query = LatestVerificationsByConsumerVersion
42+
.select_all_qualified
43+
.join(:all_pact_publications, pact_version_id: :pact_version_id)
44+
query = query.consumer(consumer_name) if consumer_name
45+
query = query.provider(provider_name) if provider_name
46+
query.reverse(:execution_date, :id).first
47+
end
48+
4049
def find_latest_verifications_for_consumer_version consumer_name, consumer_version_number
4150
# Use LatestPactPublicationsByConsumerVersion not AllPactPublcations because we don't
4251
# want verifications for shadowed revisions as it would be misleading.

lib/pact_broker/verifications/service.rb

+4
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ def find_latest_verification_for_tags consumer_name, provider_name, consumer_ver
5353
verification_repository.find_latest_verification_for_tags(consumer_name, provider_name, consumer_version_tag_name, provider_version_tag_name)
5454
end
5555

56+
def search_for_latest consumer_name, provider_name
57+
verification_repository.search_for_latest(consumer_name, provider_name)
58+
end
59+
5660
def verification_summary_for_consumer_version params
5761
verifications = find_latest_verifications_for_consumer_version(params)
5862
pacts = pact_service.find_by_consumer_version(params)

lib/pact_broker/webhooks/service.rb

+15
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
require 'pact_broker/repositories'
2+
require 'pact_broker/services'
23
require 'pact_broker/logging'
34
require 'base64'
45
require 'securerandom'
56
require 'pact_broker/webhooks/job'
67
require 'pact_broker/webhooks/triggered_webhook'
78
require 'pact_broker/webhooks/status'
89
require 'pact_broker/webhooks/webhook_event'
10+
require 'pact_broker/verifications/placeholder_verification'
11+
require 'pact_broker/pacts/placeholder_pact'
912

1013
module PactBroker
1114

@@ -16,6 +19,7 @@ class Service
1619
USER = PactBroker::Webhooks::TriggeredWebhook::TRIGGER_TYPE_USER
1720

1821
extend Repositories
22+
extend Services
1923
include Logging
2024

2125
def self.next_uuid
@@ -59,6 +63,17 @@ def self.find_all
5963
webhook_repository.find_all
6064
end
6165

66+
def self.test_execution webhook
67+
options = { failure_log_message: "Webhook execution failed", show_response: PactBroker.configuration.show_webhook_response?}
68+
verification = nil
69+
if webhook.trigger_on_provider_verification_published?
70+
verification = verification_service.search_for_latest(webhook.consumer_name, webhook.provider_name) || PactBroker::Verifications::PlaceholderVerification.new
71+
end
72+
73+
pact = pact_service.search_for_latest_pact(consumer_name: webhook.consumer_name, provider_name: webhook.provider_name) || PactBroker::Pacts::PlaceholderPact.new
74+
webhook.execute(pact, verification, options)
75+
end
76+
6277
def self.execute_webhook_now webhook, pact, verification = nil
6378
triggered_webhook = webhook_repository.create_triggered_webhook(next_uuid, webhook, pact, verification, USER)
6479
options = { failure_log_message: "Webhook execution failed"}

script/seed.rb

+28-51
Original file line numberDiff line numberDiff line change
@@ -8,54 +8,46 @@
88
ENV['RACK_ENV'] = 'development'
99
require 'sequel'
1010
require 'logger'
11-
DATABASE_CREDENTIALS = {logger: Logger.new($stdout), adapter: "sqlite", database: ARGV[0], :encoding => 'utf8'}
11+
DATABASE_CREDENTIALS = {logger: Logger.new($stdout), adapter: "sqlite", database: ARGV[0], :encoding => 'utf8'}.tap { |it| puts it }
1212
#DATABASE_CREDENTIALS = {adapter: "postgres", database: "pact_broker", username: 'pact_broker', password: 'pact_broker', :encoding => 'utf8'}
1313

1414
connection = Sequel.connect(DATABASE_CREDENTIALS)
1515
connection.timezone = :utc
16+
# Uncomment these lines to open a pry session for inspecting the database
17+
# require 'table_print'
18+
# require 'pry'; pry(binding);
19+
# exit
20+
1621
require 'pact_broker/db'
1722
PactBroker::DB.connection = connection
1823
require 'pact_broker'
1924
require 'support/test_data_builder'
2025

21-
# Uncomment these lines to open a pry session for inspecting the database
22-
# require 'table_print'
23-
# require 'pry'; pry(binding);
24-
# exit
2526

2627
require 'database/table_dependency_calculator'
2728
PactBroker::Database::TableDependencyCalculator.call(connection).each do | table_name |
2829
connection[table_name].delete
2930
end
3031

31-
class TestDataBuilder
32-
def method_missing *args
33-
self
34-
end
35-
36-
def publish_pact params = {}
37-
create_pact params
38-
end
39-
end
40-
41-
# latest verifications
42-
# TestDataBuilder.new
43-
# .create_consumer("Foo")
44-
# .create_provider("Bar")
45-
# .create_consumer_version("1.2.3")
46-
# .create_pact
47-
# .create_verification(provider_version: "4.5.6", success: true)
48-
# .create_provider("Wiffle")
49-
# .create_pact
50-
# .create_verification(provider_version: "5.6.7", success: false)
51-
# .create_provider("Meep")
52-
# .create_pact
32+
# .create_webhook(method: 'GET', url: 'https://localhost:9393?url=${pactbroker.pactUrl}', body: '${pactbroker.pactUrl}')
5333

34+
webhook_body = {
35+
'pactUrl' => '${pactbroker.pactUrl}',
36+
'verificationResultUrl' => '${pactbroker.verificationResultUrl}',
37+
'consumerVersionNumber' => '${pactbroker.consumerVersionNumber}',
38+
'providerVersionNumber' => '${pactbroker.providerVersionNumber}',
39+
'providerVersionTags' => '${pactbroker.providerVersionTags}',
40+
'consumerVersionTags' => '${pactbroker.consumerVersionTags}',
41+
'consumerName' => '${pactbroker.consumerName}',
42+
'providerName' => '${pactbroker.providerName}',
43+
'githubVerificationStatus' => '${pactbroker.githubVerificationStatus}'
44+
}
5445

55-
# .create_webhook(method: 'GET', url: 'https://localhost:9393?url=${pactbroker.pactUrl}', body: '${pactbroker.pactUrl}')
5646
TestDataBuilder.new
5747
.create_global_webhook(method: 'GET', url: "http://example.org?consumer=${pactbroker.consumerName}&provider=${pactbroker.providerName}")
5848
.create_certificate(path: 'spec/fixtures/certificates/self-signed.badssl.com.pem')
49+
.create_consumer("Bethtest")
50+
.create_verification_webhook(method: 'POST', url: "http://localhost:9292", body: webhook_body)
5951
.create_consumer("Foo")
6052
.create_label("microservice")
6153
.create_provider("Bar")
@@ -65,33 +57,33 @@ def publish_pact params = {}
6557
.create_provider_webhook(method: 'GET', url: 'https://theage.com.au')
6658
.create_webhook(method: 'GET', url: 'https://self-signed.badssl.com')
6759
.create_consumer_version("1.2.100")
68-
.publish_pact
60+
.create_pact
6961
.create_verification(provider_version: "1.4.234", success: true, execution_date: DateTime.now - 15)
7062
.revise_pact
7163
.create_consumer_version("1.2.101")
7264
.create_consumer_version_tag('prod')
73-
.publish_pact
65+
.create_pact
7466
.create_verification(provider_version: "9.9.10", success: false, execution_date: DateTime.now - 15)
7567
.create_consumer_version("1.2.102")
76-
.publish_pact(created_at: (Date.today - 7).to_datetime)
68+
.create_pact(created_at: (Date.today - 7).to_datetime)
7769
.create_verification(provider_version: "9.9.9", success: true, execution_date: DateTime.now - 14)
7870
.create_provider("Animals")
7971
.create_webhook(method: 'GET', url: 'http://localhost:9393/')
80-
.publish_pact(created_at: (Time.now - 140).to_datetime)
72+
.create_pact(created_at: (Time.now - 140).to_datetime)
8173
.create_verification(provider_version: "2.0.366", execution_date: Date.today - 2) #changed
8274
.create_provider("Wiffles")
83-
.publish_pact
75+
.create_pact
8476
.create_verification(provider_version: "3.6.100", success: false, execution_date: Date.today - 7)
8577
.create_provider("Hello World App")
8678
.create_consumer_version("1.2.107")
87-
.publish_pact(created_at: (Date.today - 1).to_datetime)
79+
.create_pact(created_at: (Date.today - 1).to_datetime)
8880
.create_consumer("The Android App")
8981
.create_provider("The back end")
9082
.create_webhook(method: 'GET', url: 'http://localhost:9393/')
9183
.create_consumer_version("1.2.106")
9284
.create_consumer_version_tag("production")
9385
.create_consumer_version_tag("feat-x")
94-
.publish_pact
86+
.create_pact
9587
.create_consumer("Some other app")
9688
.create_provider("A service")
9789
.create_webhook(method: 'GET', url: 'http://localhost:9393/')
@@ -101,20 +93,5 @@ def publish_pact params = {}
10193
.create_triggered_webhook(status: 'failure')
10294
.create_webhook_execution
10395
.create_consumer_version("1.2.106")
104-
.publish_pact(created_at: (Date.today - 26).to_datetime)
96+
.create_pact(created_at: (Date.today - 26).to_datetime)
10597
.create_verification(provider_version: "4.8.152", execution_date: DateTime.now)
106-
107-
# TestDataBuilder.new
108-
# .create_pact_with_hierarchy("A", "1", "B")
109-
# .create_consumer_version_tag("master")
110-
# .create_consumer_version_tag("prod")
111-
# .create_verification(provider_version: "1")
112-
# .create_consumer_version("2")
113-
# .create_consumer_version_tag("master")
114-
# .create_pact
115-
# .create_verification(provider_version: "2")
116-
117-
# TestDataBuilder.new
118-
# .create_pact_with_hierarchy("Foo", "1", "Bar")
119-
# .create_webhook(method: 'GET', url: 'http://localhost:9393', events: [{ name: 'provider_verification_published' }, {name: ''}])
120-

spec/features/execute_webhook_spec.rb

+8-18
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,12 @@
3636
expect(subject.status).to be 200
3737
end
3838

39-
it "saves a TriggeredWebhook" do
40-
expect { subject }.to change { PactBroker::Webhooks::TriggeredWebhook.count }.by(1)
39+
it "does not save a TriggeredWebhook" do
40+
expect { subject }.to_not change { PactBroker::Webhooks::TriggeredWebhook.count }
4141
end
4242

43-
it "saves a WebhookExecution" do
44-
expect { subject }.to change { PactBroker::Webhooks::Execution.count }.by(1)
43+
it "does not save a WebhookExecution" do
44+
expect { subject }.to_not change { PactBroker::Webhooks::Execution.count }
4545
end
4646

4747
context "when a webhook host whitelist is not configured" do
@@ -52,11 +52,6 @@
5252
it "does not show any response details" do
5353
expect(subject.body).to_not include response_body
5454
end
55-
56-
it "does not log any response details" do
57-
subject
58-
expect(PactBroker::Webhooks::Execution.order(:id).last.logs).to_not include response_body
59-
end
6055
end
6156

6257
context "when a webhook host whitelist is configured" do
@@ -67,11 +62,6 @@
6762
it "includes response details" do
6863
expect(subject.body).to include response_body
6964
end
70-
71-
it "logs the response details" do
72-
subject
73-
expect(PactBroker::Webhooks::Execution.order(:id).last.logs).to include response_body
74-
end
7565
end
7666
end
7767

@@ -98,12 +88,12 @@
9888
expect(subject.status).to be 500
9989
end
10090

101-
it "saves a TriggeredWebhook" do
102-
expect { subject }.to change { PactBroker::Webhooks::TriggeredWebhook.count }.by(1)
91+
it "does not save a TriggeredWebhook" do
92+
expect { subject }.to_not change { PactBroker::Webhooks::TriggeredWebhook.count }
10393
end
10494

105-
it "saves a WebhookExecution" do
106-
expect { subject }.to change { PactBroker::Webhooks::Execution.count }.by(1)
95+
it "does not save a WebhookExecution" do
96+
expect { subject }.to_not change { PactBroker::Webhooks::Execution.count }
10797
end
10898
end
10999
end

spec/lib/pact_broker/api/resources/webhook_execution_spec.rb

+2-8
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,12 @@ module Resources
3535
let(:provider_name) { "bar" }
3636

3737
before do
38-
allow(PactBroker::Webhooks::Service).to receive(:execute_webhook_now).and_return(execution_result)
38+
allow(PactBroker::Webhooks::Service).to receive(:test_execution).and_return(execution_result)
3939
allow(PactBroker::Api::Decorators::WebhookExecutionResultDecorator).to receive(:new).and_return(decorator)
40-
allow(PactBroker::Pacts::Service).to receive(:find_latest_pact).and_return(pact)
41-
end
42-
43-
it "finds the latest pact for the webhook" do
44-
expect(PactBroker::Pacts::Service).to receive(:find_latest_pact).with(consumer_name: consumer_name, provider_name: provider_name)
45-
subject
4640
end
4741

4842
it "executes the webhook" do
49-
expect(PactBroker::Webhooks::Service).to receive(:execute_webhook_now).with(webhook, pact)
43+
expect(PactBroker::Webhooks::Service).to receive(:test_execution).with(webhook)
5044
subject
5145
end
5246

0 commit comments

Comments
 (0)