Skip to content

Commit faf31d8

Browse files
Merge pull request #1458 from openaustralia/feature/allow_webhook_to_include_run_stats
Feature: allow webhook to include run stats
2 parents 931d9e7 + 80e2701 commit faf31d8

File tree

5 files changed

+149
-8
lines changed

5 files changed

+149
-8
lines changed

app/views/documentation/webhooks.html.haml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,18 @@
2424
You can send information through the request
2525
by putting it in the webhook URL,
2626
either as the path or as GET parameters.
27+
The following UPPERCASE strings will be replaced with the current run details in the url:
28+
29+
* ADDED: Records added count
30+
* AUTO: "true" if an automatic daily run, otherwise "false"
31+
* COMMIT: Full git revision
32+
* OUTCOME: "success" if run finished successfully, otherwise "failed"
33+
* REMOVED: Records removed count
34+
* REVISION: Abbreviated git revision (e.g. "c9fab2s7")
35+
* RUN_TIME: Seconds taken to run (eg "603" is 10 minutes and 3 seconds)
36+
* SCRAPER: Full scraper name, eg "owner-name/repo-name"
37+
* STATUS_CODE: Process exit code (0 means success)
38+
2739
If there’s something specific you’d like included in the webhook payload
2840
then please [ask a question on our help forum](#{host_origin("help")}/).
2941

app/workers/deliver_webhook_worker.rb

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def perform(webhook_delivery_id)
1313
# TODO: As soon as we've upgraded to a recent version of Ubuntu switch the SSL
1414
# verification back on. It's not crazy-super important that we verify SSL with
1515
# webhooks but it's sure a good idea.
16-
connection = Faraday.new(webhook_delivery.webhook.url, ssl: { verify: false })
16+
connection = Faraday.new(substituted_url(webhook_delivery), ssl: { verify: false })
1717
begin
1818
response = connection.post
1919
webhook_delivery.update(
@@ -26,4 +26,25 @@ def perform(webhook_delivery_id)
2626
Rails.logger.error("Webhook delivery failure on scraper #{webhook_delivery.run.scraper.full_name}: #{e}")
2727
end
2828
end
29+
30+
private
31+
32+
def substituted_url(webhook_delivery)
33+
url = webhook_delivery.webhook.url
34+
run = webhook_delivery.run
35+
{
36+
"ADDED" => run.records_added,
37+
"AUTO" => run.auto?,
38+
"COMMIT" => run.git_revision,
39+
"OUTCOME" => run.finished_successfully? ? "success" : "failed",
40+
"REMOVED" => run.records_removed,
41+
"REVISION" => run.git_revision[0..7],
42+
"RUN_TIME" => run.wall_time.to_i,
43+
"SCRAPER" => run.scraper.full_name,
44+
"STATUS_CODE" => run.status_code
45+
}.each do |name, value|
46+
url = url.gsub(name, value.to_s)
47+
end
48+
url
49+
end
2950
end

spec/lib/morph/language_spec.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
require "spec_helper"
55

66
describe Morph::Language do
7+
before do
8+
# Clear any files from manual testing
9+
Dir.glob("default_files/*/template/data.sqlite").each { |file| File.delete(file) }
10+
end
11+
712
let(:ruby) { described_class.new(:ruby) }
813
let(:python) { described_class.new(:python) }
914
let(:php) { described_class.new(:php) }

spec/spec_helper.rb

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@
7171
# Commented out for the benefit of zeus
7272
# require 'rspec/autorun'
7373

74+
# Commented out so we can run docker tests
75+
# require 'webmock/rspec'
76+
77+
require "webmock"
78+
require "webmock/rspec/matchers"
79+
7480
# Requires supporting ruby files with custom matchers and macros, etc.
7581
# in spec/support/ and its subdirectories.
7682
Dir[Rails.root.join("spec/support/**/*.rb")].sort.each { |f| require f }
@@ -132,6 +138,11 @@
132138
config.include RetryHelper
133139
config.include CaptureHelper
134140
config.include ToolAvailability
141+
# Include helpers like 'webmock/rspec' does without WebMock.enable! / disable which breaks VCR
142+
config.include WebMock::API
143+
config.include WebMock::Matchers
144+
145+
WebMock::AssertionFailure.error_class = RSpec::Expectations::ExpectationNotMetError
135146

136147
config.before(:suite) do
137148
Searchkick.disable_callbacks
@@ -145,11 +156,22 @@
145156
# For tests marked as :docker tests don't use VCR
146157
config.around do |ex|
147158
if ex.metadata.key?(:docker)
159+
# Disable VCR and WebMock checks so docker can retrieve images
160+
VCR.turned_off do
161+
WebMock.allow_net_connect! # Just to be sure!
162+
ex.run
163+
end
164+
elsif ex.metadata.key?(:webmock)
165+
# WebMock checks all requests have been stubbed, and
166+
# clean up WebMock stubs afterwards
148167
VCR.turned_off do
149-
WebMock.allow_net_connect!
168+
WebMock.disallow_net_connect!
169+
# Do NOT surround with WebMock.enable! / disable as it breaks some docker and VCR specs!
150170
ex.run
171+
WebMock.reset!
151172
end
152173
else
174+
# VCR checks all requests are enclosed in a VCR.use_cassette call
153175
ex.run
154176
end
155177
end

spec/workers/deliver_webhook_worker_spec.rb

Lines changed: 87 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,99 @@
33

44
require "spec_helper"
55

6-
describe DeliverWebhookWorker, :vcr do
6+
describe DeliverWebhookWorker do
77
let(:scraper) { create(:scraper) }
8-
let(:run) { create(:run) }
8+
let(:run) do
9+
create(:run,
10+
git_revision: "c9fabbc7a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
11+
records_added: 100,
12+
records_removed: 200,
13+
status_code: 3,
14+
started_at: 400.4.seconds.ago,
15+
finished_at: 0.1.seconds.ago,
16+
auto: true)
17+
end
18+
19+
describe "test to actual site" do
20+
it "works" do
21+
VCR.use_cassette("webhook_delivery") do
22+
webhook = Webhook.create!(scraper: scraper, url: "http://requestb.in/x3pcr8x3")
23+
webhook_delivery = webhook.deliveries.create!(run: run)
24+
described_class.new.perform(webhook_delivery.id)
25+
webhook_delivery.reload
26+
expect(webhook_delivery.response_code).to eq(200)
27+
expect(webhook_delivery.sent_at).to be_within(1.minute).of(DateTime.now)
28+
end
29+
end
30+
end
931

10-
it "works" do
11-
VCR.use_cassette("webhook_delivery") do
12-
webhook = Webhook.create!(scraper: scraper, url: "http://requestb.in/x3pcr8x3")
32+
describe "error handling", :webmock do
33+
it "records response code and time on success" do
34+
webhook = Webhook.create!(scraper: scraper, url: "https://example.com/hook")
1335
webhook_delivery = webhook.deliveries.create!(run: run)
36+
37+
stub_request(:post, "https://example.com/hook").to_return(status: 200)
38+
1439
described_class.new.perform(webhook_delivery.id)
40+
1541
webhook_delivery.reload
16-
expect(webhook_delivery.response_code).to be(200)
42+
expect(webhook_delivery.response_code).to eq(200)
1743
expect(webhook_delivery.sent_at).to be_within(1.minute).of(DateTime.now)
1844
end
45+
46+
it "logs connection failures without raising" do
47+
webhook = Webhook.create!(scraper: scraper, url: "http://example.com/hook")
48+
webhook_delivery = webhook.deliveries.create!(run: run)
49+
50+
stub_request(:post, "http://example.com/hook").to_raise(Faraday::ConnectionFailed.new("Connection refused"))
51+
52+
allow(Rails.logger).to receive(:error)
53+
54+
expect do
55+
described_class.new.perform(webhook_delivery.id)
56+
end.not_to raise_error
57+
58+
expect(Rails.logger).to have_received(:error).with(/Webhook delivery failure/)
59+
end
60+
end
61+
62+
describe "URL substitution", :webmock do
63+
it "substitutes all placeholders correctly" do
64+
substitutions = {
65+
"ADDED" => run.records_added,
66+
"AUTO" => run.auto,
67+
"COMMIT" => run.git_revision,
68+
"OUTCOME" => "failed",
69+
"REMOVED" => run.records_removed,
70+
"REVISION" => "c9fabbc7",
71+
"RUN_TIME" => run.wall_time.to_i,
72+
"SCRAPER" => run.scraper.full_name,
73+
"STATUS_CODE" => run.status_code
74+
}
75+
76+
url_template = "https://example.com/?#{substitutions.keys.map { |k| "#{k.downcase}=#{k}" }.join('&')}"
77+
expected_url = "https://example.com/?#{substitutions.map { |k, v| "#{k.downcase}=#{v}" }.join('&')}"
78+
79+
webhook = Webhook.create!(scraper: scraper, url: url_template)
80+
webhook_delivery = webhook.deliveries.create!(run: run)
81+
82+
stub = stub_request(:post, expected_url).to_return(status: 200)
83+
84+
described_class.new.perform(webhook_delivery.id)
85+
86+
expect(stub).to have_been_requested.once
87+
end
88+
89+
it "uses 'success' for OUTCOME when run succeeds" do
90+
run.update!(status_code: 0)
91+
webhook = Webhook.create!(scraper: scraper, url: "https://example.com/?outcome=OUTCOME")
92+
webhook_delivery = webhook.deliveries.create!(run: run)
93+
94+
stub = stub_request(:post, "https://example.com/?outcome=success").to_return(status: 200)
95+
96+
described_class.new.perform(webhook_delivery.id)
97+
98+
expect(stub).to have_been_requested.once
99+
end
19100
end
20101
end

0 commit comments

Comments
 (0)