Skip to content

Commit 88ba2ac

Browse files
committed
feat(webhook status): ensure triggered webhook and webhook execution objects are saved to database even when webhook fails and response code is 500
1 parent 7f982b3 commit 88ba2ac

File tree

10 files changed

+152
-21
lines changed

10 files changed

+152
-21
lines changed

lib/pact_broker/api/decorators/pact_webhooks_status_decorator.rb

+26-7
Original file line numberDiff line numberDiff line change
@@ -60,29 +60,40 @@ class PactWebhooksStatusDecorator < BaseDecorator
6060
end
6161

6262
link :'pb:pact-version' do | context |
63-
{
64-
href: pact_url(context[:base_url], pact),
65-
title: "Pact",
66-
name: pact.name
67-
}
63+
if pact
64+
{
65+
href: pact_url(context[:base_url], pact),
66+
title: "Pact",
67+
name: pact.name
68+
}
69+
else
70+
nil
71+
end
6872
end
6973

7074
link :'pb:consumer' do | context |
7175
{
72-
href: pacticipant_url(context[:base_url], OpenStruct.new(name: context[:consumer_name])),
76+
href: pacticipant_url(context[:base_url], fake_consumer(context)),
7377
title: "Consumer",
7478
name: context[:consumer_name]
7579
}
7680
end
7781

7882
link :'pb:provider' do | context |
7983
{
80-
href: pacticipant_url(context[:base_url], OpenStruct.new(name: context[:provider_name])),
84+
href: pacticipant_url(context[:base_url], fake_provider(context)),
8185
title: "Provider",
8286
name: context[:provider_name]
8387
}
8488
end
8589

90+
link :'pb:pact-webhooks' do | context |
91+
{
92+
title: "Webhooks for the pact between #{context[:consumer_name]} and #{context[:provider_name]}",
93+
href: webhooks_for_pact_url(fake_consumer(context), fake_provider(context), context.fetch(:base_url))
94+
}
95+
end
96+
8697
def summary
8798
counts = represented.group_by(&:status).each_with_object({}) do | (status, triggered_webhooks), counts |
8899
counts[status] = triggered_webhooks.count
@@ -97,6 +108,14 @@ def triggered_webhooks_with_error_logs
97108
def pact
98109
represented.any? ? represented.first.pact_publication : nil
99110
end
111+
112+
def fake_consumer context
113+
OpenStruct.new(name: context[:consumer_name])
114+
end
115+
116+
def fake_provider context
117+
OpenStruct.new(name: context[:provider_name])
118+
end
100119
end
101120
end
102121
end

lib/pact_broker/api/decorators/webhook_decorator.rb

+7-7
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ class WebhookDecorator < BaseDecorator
2424

2525
end
2626

27+
link :'pb:execute' do | options |
28+
{
29+
title: "Test the execution of the webhook by sending a POST request to this URL",
30+
href: webhook_execution_url(represented, options[:base_url])
31+
}
32+
end
33+
2734
link :'pb:pact-webhooks' do | options |
2835
{
2936
title: "All webhooks for the pact between #{represented.consumer.name} and #{represented.provider.name}",
@@ -38,13 +45,6 @@ class WebhookDecorator < BaseDecorator
3845
}
3946
end
4047

41-
link :'pb:execute' do | options |
42-
{
43-
title: "Test the execution of the webhook by sending a POST request to this URL",
44-
href: webhook_execution_url(represented, options[:base_url])
45-
}
46-
47-
end
4848

4949
end
5050
end

lib/pact_broker/api/resources/webhook_execution.rb

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
require 'pact_broker/services'
22
require 'pact_broker/api/decorators/webhook_execution_result_decorator'
3+
require 'pact_broker/constants'
34

45
module PactBroker
56
module Api
@@ -15,7 +16,12 @@ def process_post
1516
webhook_execution_result = webhook_service.execute_webhook_now webhook, pact
1617
response.headers['Content-Type'] = 'application/hal+json;charset=utf-8'
1718
response.body = post_response_body webhook_execution_result
18-
webhook_execution_result.success? ? true : 500
19+
if webhook_execution_result.success?
20+
true
21+
else
22+
response.headers[PactBroker::DO_NOT_ROLLBACK] = 'true'
23+
500
24+
end
1925
end
2026

2127
def resource_exists?

lib/pact_broker/constants.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
module PactBroker
22

33
CONSUMER_VERSION_HEADER = 'X-Pact-Consumer-Version'.freeze
4-
4+
DO_NOT_ROLLBACK = 'X-Pact-Broker-Do-Not-Rollback'.freeze
55
end

lib/pact_broker/webhooks/service.rb

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ def self.execute_webhook_now webhook, pact
6262
else
6363
webhook_repository.update_triggered_webhook_status triggered_webhook, TriggeredWebhook::STATUS_FAILURE
6464
end
65+
webhook_execution_result
6566
end
6667

6768
def self.execute_triggered_webhook_now triggered_webhook

lib/rack/pact_broker/database_transaction.rb

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
require 'pact_broker/version'
1+
require 'pact_broker/constants'
22
require 'sequel'
33

44
module Rack
@@ -33,10 +33,16 @@ def call_with_transaction env
3333
response = nil
3434
@database_connection.transaction do
3535
response = @app.call(env)
36-
raise Sequel::Rollback if response.first == 500
36+
if response.first == 500
37+
raise Sequel::Rollback unless do_not_rollback?(response)
38+
end
3739
end
3840
response
3941
end
42+
43+
def do_not_rollback? response
44+
response[1].delete(::PactBroker::DO_NOT_ROLLBACK)
45+
end
4046
end
4147
end
4248
end

spec/features/execute_webhook_spec.rb

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
require 'support/test_data_builder'
2+
require 'webmock/rspec'
3+
require 'rack/pact_broker/database_transaction'
4+
5+
describe "Execute a webhook" do
6+
7+
let(:td) { TestDataBuilder.new }
8+
9+
before do
10+
td.create_pact_with_hierarchy("Some Consumer", "1", "Some Provider")
11+
.create_webhook(method: 'POST')
12+
end
13+
14+
let(:path) { "/webhooks/#{td.webhook.uuid}/execute" }
15+
let(:response_body) { JSON.parse(last_response.body, symbolize_names: true)}
16+
17+
subject { post path; last_response }
18+
19+
context "when the execution is successful" do
20+
let!(:request) do
21+
stub_request(:post, /http/).to_return(:status => 200)
22+
end
23+
24+
it "performs the HTTP request" do
25+
subject
26+
expect(request).to have_been_made
27+
end
28+
29+
it "returns a 200 response" do
30+
puts subject.body
31+
expect(subject.status).to be 200
32+
end
33+
34+
it "saves a TriggeredWebhook" do
35+
expect { subject }.to change { PactBroker::Webhooks::TriggeredWebhook.count }.by(1)
36+
end
37+
38+
it "saves a WebhookExecution" do
39+
expect { subject }.to change { PactBroker::Webhooks::Execution.count }.by(1)
40+
end
41+
end
42+
43+
context "when an error occurs", no_db_clean: true do
44+
let(:app) { Rack::PactBroker::DatabaseTransaction.new(PactBroker::API, PactBroker::DB.connection) }
45+
46+
let!(:request) do
47+
stub_request(:post, /http/).to_raise(Errno::ECONNREFUSED)
48+
end
49+
50+
before do
51+
PactBroker::Database.truncate
52+
td.create_pact_with_hierarchy("Some Consumer", "1", "Some Provider")
53+
.create_webhook(method: 'POST')
54+
end
55+
56+
after do
57+
PactBroker::Database.truncate
58+
end
59+
60+
subject { post path; last_response }
61+
62+
it "returns a 500 response" do
63+
expect(subject.status).to be 500
64+
end
65+
66+
it "saves a TriggeredWebhook" do
67+
expect { subject }.to change { PactBroker::Webhooks::TriggeredWebhook.count }.by(1)
68+
end
69+
70+
it "saves a WebhookExecution" do
71+
expect { subject }.to change { PactBroker::Webhooks::Execution.count }.by(1)
72+
end
73+
end
74+
end

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

+10-1
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,10 @@ module Decorators
3636
let(:retrying) { false }
3737
let(:status) { PactBroker::Webhooks::TriggeredWebhook::STATUS_SUCCESS }
3838
let(:logs_url) { "http://example.org/webhooks/4321/trigger/1234/logs" }
39+
let(:triggered_webhooks) { [triggered_webhook] }
3940

4041
let(:json) do
41-
PactWebhooksStatusDecorator.new([triggered_webhook]).to_json(user_options: user_options)
42+
PactWebhooksStatusDecorator.new(triggered_webhooks).to_json(user_options: user_options)
4243
end
4344

4445
subject { JSON.parse json, symbolize_names: true }
@@ -112,6 +113,14 @@ module Decorators
112113
expect(subject[:summary]).to eq({successful: 0, failed: 0, notRun: 1})
113114
end
114115
end
116+
117+
context "when there are no triggered webhooks" do
118+
let(:triggered_webhooks) { [] }
119+
120+
it "doesn't blow up" do
121+
subject
122+
end
123+
end
115124
end
116125
end
117126
end

spec/lib/rack/pact_broker/database_transaction_spec.rb

+12-1
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ module PactBroker
1313
::PactBroker::Database.truncate
1414
end
1515

16+
let(:headers) { {} }
17+
1618
let(:api) do
17-
->(env) { ::PactBroker::Domain::Pacticipant.create(name: 'Foo'); [500, {}, []] }
19+
->(env) { ::PactBroker::Domain::Pacticipant.create(name: 'Foo'); [500, headers, []] }
1820
end
1921

2022
let(:app) do
@@ -39,6 +41,15 @@ module PactBroker
3941
end
4042
end
4143
end
44+
45+
context "when there is an error but the resource sets the no rollback header" do
46+
let(:headers) { {::PactBroker::DO_NOT_ROLLBACK => 'true'} }
47+
let(:http_method) { :post }
48+
49+
it "does not roll back" do
50+
expect { subject }.to change { ::PactBroker::Domain::Pacticipant.count }.by(1)
51+
end
52+
end
4253
end
4354
end
4455
end

tasks/database.rb

+6-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,12 @@ def recreate
7777
def truncate
7878
TABLES.each do | table_name |
7979
if database.table_exists?(table_name)
80-
database[table_name].delete
80+
begin
81+
database[table_name].delete
82+
rescue SQLite3::ConstraintException => e
83+
puts "Could not delete the following records from #{table_name}: #{database[table_name].select_all}"
84+
raise e
85+
end
8186
end
8287
end
8388
end

0 commit comments

Comments
 (0)