webhukhs 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.yard-lint.yml +1 -0
- data/AGENTS.md +9 -11
- data/Appraisals +2 -2
- data/CHANGELOG.md +13 -0
- data/README.md +98 -10
- data/Rakefile +7 -0
- data/example/Gemfile +5 -6
- data/example/Gemfile.lock +260 -141
- data/example/app/webhooks/acme_handler.rb +16 -0
- data/example/app/webhooks/flaky_handler.rb +8 -0
- data/example/app/webhooks/signed_handler.rb +10 -0
- data/example/config/initializers/webhukhs.rb +4 -3
- data/example/config/routes.rb +3 -0
- data/example/db/migrate/20240601000000_add_headers_to_webhukhs_webhooks.rb +7 -0
- data/example/db/schema.rb +2 -1
- data/example/db/seeds.rb +71 -7
- data/gemfiles/{rails_7.1.gemfile → rails_7.2.gemfile} +1 -4
- data/gemfiles/rails_7.2.gemfile.lock +477 -0
- data/gemfiles/rails_8.0.gemfile +0 -3
- data/gemfiles/rails_8.0.gemfile.lock +74 -4
- data/gemfiles/rails_8.1.gemfile +0 -3
- data/gemfiles/rails_8.1.gemfile.lock +74 -4
- data/gemfiles/rails_master.gemfile +0 -3
- data/lib/webhukhs/admin/application_controller.rb +32 -0
- data/lib/webhukhs/admin/assets/admin.css +476 -0
- data/lib/webhukhs/admin/assets/admin.js +111 -0
- data/lib/webhukhs/admin/config/routes.rb +6 -0
- data/lib/webhukhs/admin/engine.rb +20 -0
- data/lib/webhukhs/admin/received_webhook_presenter.rb +63 -0
- data/lib/webhukhs/admin/received_webhooks_controller.rb +60 -0
- data/lib/webhukhs/admin/view_helper.rb +50 -0
- data/lib/webhukhs/admin/views/layouts/webhukhs/admin/application.html.erb +22 -0
- data/lib/webhukhs/admin/views/webhukhs/admin/received_webhooks/index.html.erb +50 -0
- data/lib/webhukhs/admin/views/webhukhs/admin/received_webhooks/show.html.erb +55 -0
- data/lib/webhukhs/admin.rb +17 -0
- data/lib/webhukhs/engine.rb +0 -4
- data/lib/webhukhs/models/received_webhook.rb +3 -0
- data/lib/webhukhs/version.rb +1 -1
- data/test/admin_test.rb +334 -0
- data/test/base_handler_test.rb +3 -3
- data/test/browser/admin_click_through.rb +208 -0
- data/test/processing_job_test.rb +15 -28
- data/test/received_webhook_test.rb +16 -0
- data/test/test_app.rb +2 -0
- data/test/test_helper.rb +9 -6
- data/test/webhukhs_test.rb +20 -62
- metadata +165 -8
- data/example/.ruby-version +0 -1
- data/gemfiles/rails_7.1.gemfile.lock +0 -412
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
require "ferrum"
|
|
5
|
+
require "puma"
|
|
6
|
+
require "puma/server"
|
|
7
|
+
|
|
8
|
+
# Clicks through the admin in a real Chrome, so that what the request tests cannot see - the JS, the
|
|
9
|
+
# CSP letting it run, assets resolving under the mount point - is exercised too. Not part of `rake test`,
|
|
10
|
+
# run it with `rake test:browser`.
|
|
11
|
+
class AdminClickThrough < ActiveSupport::TestCase
|
|
12
|
+
# The app answers from Puma threads with connections of their own, which would not see rows
|
|
13
|
+
# created inside a test transaction
|
|
14
|
+
self.use_transactional_tests = false
|
|
15
|
+
|
|
16
|
+
PAGE_SIZE = Webhukhs::Admin::ReceivedWebhooksController::PAGE_SIZE
|
|
17
|
+
|
|
18
|
+
def self.server
|
|
19
|
+
@server ||= Puma::Server.new(WebhukhsTestApp, nil, min_threads: 0, max_threads: 4, log_writer: Puma::LogWriter.null).tap do |server|
|
|
20
|
+
server.add_tcp_listener("127.0.0.1", 0)
|
|
21
|
+
server.run
|
|
22
|
+
Minitest.after_run { server.stop(true) }
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
setup do
|
|
27
|
+
@base_url = "http://127.0.0.1:#{self.class.server.connected_ports.first}/admin"
|
|
28
|
+
@console_errors = []
|
|
29
|
+
# GitHub's Ubuntu runners do not let Chrome set up its sandbox
|
|
30
|
+
browser_options = ENV["CI"] ? {"no-sandbox" => nil} : {}
|
|
31
|
+
@browser = Ferrum::Browser.new(timeout: 10, js_errors: true, browser_options: browser_options)
|
|
32
|
+
@browser.on("Log.entryAdded") { |params| @console_errors << params.dig("entry", "text") if params.dig("entry", "level") == "error" }
|
|
33
|
+
@browser.on("Runtime.consoleAPICalled") { |params| @console_errors << params["args"].map { |arg| arg["value"] }.join(" ") if params["type"] == "error" }
|
|
34
|
+
@browser.page.command("Log.enable")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
teardown do
|
|
38
|
+
failed = @browser.network.traffic.filter_map do |exchange|
|
|
39
|
+
"#{exchange.response.status} #{exchange.request.url}" if exchange.response && exchange.response.status >= 400
|
|
40
|
+
end
|
|
41
|
+
@browser.quit
|
|
42
|
+
Webhukhs::ReceivedWebhook.delete_all
|
|
43
|
+
|
|
44
|
+
assert_empty failed, "Requests failed"
|
|
45
|
+
assert_empty @console_errors, "The browser logged errors"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def create_webhook(**attributes)
|
|
49
|
+
Webhukhs::ReceivedWebhook.create!(
|
|
50
|
+
handler_module_name: "WebhookTestHandler",
|
|
51
|
+
handler_event_id: SecureRandom.uuid,
|
|
52
|
+
status: "processed",
|
|
53
|
+
body: '{"event":"thing.happened"}',
|
|
54
|
+
**attributes
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def visit(path)
|
|
59
|
+
@browser.go_to(@base_url + path)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def wait_for(what, timeout: 5)
|
|
63
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
|
|
64
|
+
until (result = yield)
|
|
65
|
+
flunk "Timed out waiting for #{what}" if Process.clock_gettime(Process::CLOCK_MONOTONIC) > deadline
|
|
66
|
+
sleep 0.05
|
|
67
|
+
end
|
|
68
|
+
result
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def click_and_wait_for_page(node)
|
|
72
|
+
url = @browser.current_url
|
|
73
|
+
label = node.text.strip
|
|
74
|
+
# Ferrum clicks at the node's coordinates as they are, and a click below the fold hits nothing
|
|
75
|
+
node.scroll_into_view
|
|
76
|
+
node.click
|
|
77
|
+
wait_for("#{label.inspect} to leave #{url}") { @browser.current_url != url }
|
|
78
|
+
# The URL changes as soon as the navigation commits, while the new document may still be parsing
|
|
79
|
+
wait_for("the page after #{label.inspect} to load") { @browser.evaluate("document.readyState") == "complete" }
|
|
80
|
+
@browser.network.wait_for_idle
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# In one evaluate, so that auto-refresh swapping the table cannot pull the nodes out from under us
|
|
84
|
+
def texts(selector)
|
|
85
|
+
@browser.evaluate("Array.from(document.querySelectorAll(#{selector.to_json}), (node) => node.textContent.trim())")
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
test "lists, sorts, filters and pages through webhooks" do
|
|
89
|
+
PAGE_SIZE.times { |i| create_webhook(handler_event_id: "evt-#{i}", created_at: i.minutes.ago) }
|
|
90
|
+
create_webhook(handler_event_id: "the-failed-one", status: "error", created_at: 2.days.ago)
|
|
91
|
+
|
|
92
|
+
visit "/received"
|
|
93
|
+
assert_equal "Received webhooks", @browser.at_css("h1").text
|
|
94
|
+
# The stylesheet must have made it through the CSP - unstyled, the header would not be a flexbox
|
|
95
|
+
assert_equal "flex", @browser.evaluate("getComputedStyle(document.querySelector('.admin-header')).display")
|
|
96
|
+
assert_equal PAGE_SIZE, @browser.css("tbody tr").length
|
|
97
|
+
assert_equal "evt-0", texts("tbody tr td:nth-child(5)").first
|
|
98
|
+
|
|
99
|
+
click_and_wait_for_page(@browser.at_xpath("//th/a[contains(., 'Received')]"))
|
|
100
|
+
assert_includes @browser.current_url, "direction=asc"
|
|
101
|
+
assert_equal "the-failed-one", texts("tbody tr td:nth-child(5)").first
|
|
102
|
+
|
|
103
|
+
# Not Ferrum's Node#select: it blanks the select by assigning it `undefined` first, which
|
|
104
|
+
# left it blank on the Chromium snapshot CI got
|
|
105
|
+
@browser.execute(<<~JS)
|
|
106
|
+
const select = document.querySelector("select[name=status]")
|
|
107
|
+
select.value = "error"
|
|
108
|
+
select.dispatchEvent(new Event("change", {bubbles: true}))
|
|
109
|
+
JS
|
|
110
|
+
assert_equal "error", @browser.evaluate("document.querySelector('select[name=status]').value")
|
|
111
|
+
click_and_wait_for_page(@browser.at_css("form.filters input[type=submit]"))
|
|
112
|
+
assert_equal ["the-failed-one"], texts("tbody tr td:nth-child(5)")
|
|
113
|
+
# The sort survives filtering
|
|
114
|
+
assert_includes @browser.current_url, "direction=asc"
|
|
115
|
+
|
|
116
|
+
click_and_wait_for_page(@browser.at_xpath("//form//a[text()='Clear']"))
|
|
117
|
+
assert_equal "Any status", @browser.evaluate("document.querySelector('select[name=status]').selectedOptions[0].text")
|
|
118
|
+
|
|
119
|
+
click_and_wait_for_page(@browser.at_css("a[rel=next]"))
|
|
120
|
+
assert_equal 1, @browser.css("tbody tr").length
|
|
121
|
+
click_and_wait_for_page(@browser.at_css("a[rel=prev]"))
|
|
122
|
+
assert_equal PAGE_SIZE, @browser.css("tbody tr").length
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
test "opens a webhook by clicking anywhere on its row" do
|
|
126
|
+
webhook = create_webhook(handler_event_id: "evt-clicked")
|
|
127
|
+
|
|
128
|
+
visit "/received"
|
|
129
|
+
# The status cell is not a link, but the row link stretched over it takes the click
|
|
130
|
+
click_and_wait_for_page(@browser.at_css("tbody tr td:nth-child(4)"))
|
|
131
|
+
|
|
132
|
+
assert @browser.current_url.end_with?("/received/#{webhook.id}"), @browser.current_url
|
|
133
|
+
assert_includes @browser.at_css("h1").text, "WebhookTestHandler"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
test "switches between the pretty and the as-sent body" do
|
|
137
|
+
webhook = create_webhook(body: '{"event":"thing.happened","data":{"id":1}}')
|
|
138
|
+
|
|
139
|
+
visit "/received/#{webhook.id}"
|
|
140
|
+
pretty = -> { @browser.evaluate("document.getElementById('body-pretty').hidden") == false }
|
|
141
|
+
raw = -> { @browser.evaluate("document.getElementById('body-raw').hidden") == false }
|
|
142
|
+
assert pretty.call
|
|
143
|
+
refute raw.call
|
|
144
|
+
|
|
145
|
+
@browser.at_css("#body-tab-raw").click
|
|
146
|
+
wait_for("the as-sent tab") { raw.call }
|
|
147
|
+
refute pretty.call
|
|
148
|
+
assert_equal "true", @browser.at_css("#body-tab-raw").attribute("aria-selected")
|
|
149
|
+
assert_equal '{"event":"thing.happened","data":{"id":1}}', @browser.at_css("#body-raw code").text
|
|
150
|
+
|
|
151
|
+
@browser.at_css("#body-tab-raw").focus
|
|
152
|
+
@browser.keyboard.type(:Left)
|
|
153
|
+
wait_for("the arrow key to go back to the pretty tab") { pretty.call }
|
|
154
|
+
assert_equal "body-tab-pretty", @browser.evaluate("document.activeElement.id")
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
test "copies to the clipboard" do
|
|
158
|
+
webhook = create_webhook(handler_event_id: "evt-to-copy")
|
|
159
|
+
|
|
160
|
+
visit "/received/#{webhook.id}"
|
|
161
|
+
# Headless Chrome has no clipboard to grant, so we only check that the right text gets handed to it
|
|
162
|
+
@browser.execute("navigator.clipboard.writeText = (text) => { window.copied = text; return Promise.resolve() }")
|
|
163
|
+
|
|
164
|
+
@browser.at_css("button.btn-copy[data-copy='evt-to-copy']").click
|
|
165
|
+
wait_for("the copy button to flash") { @browser.at_css("button.btn-copy.copied[data-copy='evt-to-copy']") }
|
|
166
|
+
assert_equal "evt-to-copy", @browser.evaluate("window.copied")
|
|
167
|
+
|
|
168
|
+
@browser.at_css("button.btn-copy-panel").click
|
|
169
|
+
wait_for("the body to be copied") { @browser.evaluate("window.copied") != "evt-to-copy" }
|
|
170
|
+
assert_equal JSON.pretty_generate({"event" => "thing.happened"}), @browser.evaluate("window.copied")
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
test "shows bodies that are not JSON, or too large to display" do
|
|
174
|
+
form = create_webhook(body: "event=paid&amount=12.50")
|
|
175
|
+
large = create_webhook(body: "x" * (Webhukhs::Admin::ReceivedWebhookPresenter::DISPLAY_LIMIT + 1))
|
|
176
|
+
|
|
177
|
+
visit "/received/#{form.id}"
|
|
178
|
+
assert_equal "This body is not valid JSON, so it is shown as it was sent", @browser.at_css(".flash.alert").text
|
|
179
|
+
assert_equal "event=paid&amount=12.50", @browser.at_css("pre.body-raw code").text
|
|
180
|
+
assert_empty @browser.css("[role=tab]")
|
|
181
|
+
|
|
182
|
+
visit "/received/#{large.id}"
|
|
183
|
+
assert_equal "The body was 256 KB, display is truncated", @browser.at_css(".flash.alert").text
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
test "picks up new webhooks while auto-refresh is on, and remembers the switch" do
|
|
187
|
+
create_webhook(handler_event_id: "evt-before")
|
|
188
|
+
|
|
189
|
+
visit "/received"
|
|
190
|
+
switch = -> { @browser.evaluate("document.getElementById('auto-refresh').checked") }
|
|
191
|
+
refute switch.call
|
|
192
|
+
@browser.at_css("label.switch").click
|
|
193
|
+
wait_for("the switch to turn on") { switch.call }
|
|
194
|
+
|
|
195
|
+
create_webhook(handler_event_id: "evt-arrived")
|
|
196
|
+
# One poll interval, with some slack
|
|
197
|
+
wait_for("the new webhook to show up", timeout: 8) { texts("tbody tr td:nth-child(5)").include?("evt-arrived") }
|
|
198
|
+
assert_equal ["evt-arrived", "evt-before"], texts("tbody tr td:nth-child(5)")
|
|
199
|
+
|
|
200
|
+
visit "/received"
|
|
201
|
+
assert switch.call, "The switch should stay on after a reload"
|
|
202
|
+
# After a page load only the timer refetches, so this one has to come in through a poll
|
|
203
|
+
create_webhook(handler_event_id: "evt-polled")
|
|
204
|
+
wait_for("the polled webhook to show up", timeout: 8) { texts("tbody tr td:nth-child(5)").include?("evt-polled") }
|
|
205
|
+
@browser.at_css("label.switch").click
|
|
206
|
+
wait_for("the switch to turn off") { !switch.call }
|
|
207
|
+
end
|
|
208
|
+
end
|
data/test/processing_job_test.rb
CHANGED
|
@@ -37,36 +37,25 @@ class ProcessingJobTest < ActiveJob::TestCase
|
|
|
37
37
|
InvalidLoggingHandler.reset!
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
test "discards job and reports
|
|
41
|
-
|
|
42
|
-
Webhukhs::ProcessingJob
|
|
40
|
+
test "discards job and reports invalid webhook arguments" do
|
|
41
|
+
[nil, "not a webhook"].each do |argument|
|
|
42
|
+
assert_error_reported(Webhukhs::ProcessingJob::InvalidWebhookArgument) do
|
|
43
|
+
Webhukhs::ProcessingJob.perform_now(argument)
|
|
44
|
+
end
|
|
43
45
|
end
|
|
44
|
-
|
|
45
|
-
assert_no_enqueued_jobs only: Webhukhs::ProcessingJob
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
test "raises a specific error message when webhook argument is nil" do
|
|
49
|
-
error = assert_raises(Webhukhs::ProcessingJob::InvalidWebhookArgument) do
|
|
50
|
-
Webhukhs::ProcessingJob.new.perform(nil)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
assert_equal "ProcessingJob received nil webhook argument", error.message
|
|
54
46
|
end
|
|
55
47
|
|
|
56
|
-
test "
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
48
|
+
test "raises specific invalid webhook argument messages" do
|
|
49
|
+
{
|
|
50
|
+
nil => "ProcessingJob received nil webhook argument",
|
|
51
|
+
"not a webhook" => "ProcessingJob expected Webhukhs::ReceivedWebhook, got String"
|
|
52
|
+
}.each do |argument, message|
|
|
53
|
+
error = assert_raises(Webhukhs::ProcessingJob::InvalidWebhookArgument) do
|
|
54
|
+
Webhukhs::ProcessingJob.new.perform(argument)
|
|
55
|
+
end
|
|
63
56
|
|
|
64
|
-
|
|
65
|
-
error = assert_raises(Webhukhs::ProcessingJob::InvalidWebhookArgument) do
|
|
66
|
-
Webhukhs::ProcessingJob.new.perform("not a webhook")
|
|
57
|
+
assert_equal message, error.message
|
|
67
58
|
end
|
|
68
|
-
|
|
69
|
-
assert_equal "ProcessingJob expected Webhukhs::ReceivedWebhook, got String", error.message
|
|
70
59
|
end
|
|
71
60
|
|
|
72
61
|
test "processes webhooks under a lock" do
|
|
@@ -89,7 +78,7 @@ class ProcessingJobTest < ActiveJob::TestCase
|
|
|
89
78
|
|
|
90
79
|
Webhukhs::ProcessingJob.new.perform(webhook)
|
|
91
80
|
|
|
92
|
-
|
|
81
|
+
assert_true with_lock_called
|
|
93
82
|
assert_predicate webhook.reload, :failed_validation?
|
|
94
83
|
end
|
|
95
84
|
|
|
@@ -189,7 +178,5 @@ class ProcessingJobTest < ActiveJob::TestCase
|
|
|
189
178
|
assert_error_reported(ActiveJob::DeserializationError) do
|
|
190
179
|
perform_enqueued_jobs
|
|
191
180
|
end
|
|
192
|
-
|
|
193
|
-
assert_no_enqueued_jobs only: Webhukhs::ProcessingJob
|
|
194
181
|
end
|
|
195
182
|
end
|
|
@@ -65,6 +65,22 @@ class ReceivedWebhookTest < ActiveSupport::TestCase
|
|
|
65
65
|
)
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
+
test "request stores the whole body even if the body io was already read" do
|
|
69
|
+
body_io = StringIO.new("event=paid")
|
|
70
|
+
body_io.read
|
|
71
|
+
request = ActionDispatch::Request.new(
|
|
72
|
+
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
|
|
73
|
+
"rack.input" => body_io,
|
|
74
|
+
"action_dispatch.request.path_parameters" => {}
|
|
75
|
+
)
|
|
76
|
+
webhook = Webhukhs::ReceivedWebhook.new
|
|
77
|
+
|
|
78
|
+
webhook.request = request
|
|
79
|
+
|
|
80
|
+
assert_equal "event=paid".b, webhook.body
|
|
81
|
+
assert_equal 0, body_io.pos
|
|
82
|
+
end
|
|
83
|
+
|
|
68
84
|
test "request writer coerces the stored body to binary before type casting" do
|
|
69
85
|
body_io = StringIO.new("payload")
|
|
70
86
|
request = ActionDispatch::Request.new(
|
data/test/test_app.rb
CHANGED
|
@@ -46,6 +46,7 @@ Test.establish_database_connection
|
|
|
46
46
|
Test.define_test_schema
|
|
47
47
|
|
|
48
48
|
require_relative "../lib/webhukhs"
|
|
49
|
+
require_relative "../lib/webhukhs/admin"
|
|
49
50
|
require_relative "test-webhook-handlers/webhook_test_handler"
|
|
50
51
|
require_relative "test-webhook-handlers/inactive_handler"
|
|
51
52
|
require_relative "test-webhook-handlers/invalid_handler"
|
|
@@ -80,6 +81,7 @@ class WebhukhsTestApp < Rails::Application
|
|
|
80
81
|
routes.append do
|
|
81
82
|
mount Webhukhs::Engine, at: "/webhukhs"
|
|
82
83
|
post "/per-user-webhukhs/:user_id/:service_id" => "webhukhs/receive_webhooks#create"
|
|
84
|
+
mount Webhukhs::Admin::Engine, at: "/admin"
|
|
83
85
|
end
|
|
84
86
|
end
|
|
85
87
|
|
data/test/test_helper.rb
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
|
-
require_relative "test_app"
|
|
2
|
-
require "rails/test_help"
|
|
3
|
-
require "mutant/minitest/coverage"
|
|
4
|
-
require "active_support/logger"
|
|
5
|
-
require "stringio"
|
|
6
|
-
|
|
7
1
|
if ENV["SIMPLECOV"]
|
|
8
2
|
require "simplecov"
|
|
9
3
|
|
|
10
4
|
SimpleCov.start do
|
|
11
5
|
add_filter "/spec/"
|
|
6
|
+
add_filter "/test/"
|
|
7
|
+
add_filter "/lib/webhukhs/install_generator.rb"
|
|
12
8
|
minimum_coverage 100
|
|
13
9
|
minimum_coverage_by_file 100
|
|
14
10
|
end
|
|
15
11
|
end
|
|
16
12
|
|
|
13
|
+
require_relative "test_app"
|
|
14
|
+
require "rails/test_help"
|
|
15
|
+
require "minitest/strict"
|
|
16
|
+
require "mutant/minitest/coverage"
|
|
17
|
+
require "active_support/logger"
|
|
18
|
+
require "stringio"
|
|
19
|
+
|
|
17
20
|
class ActiveSupport::TestCase
|
|
18
21
|
# Same as "assert_changes" in Rails but for countable entities.
|
|
19
22
|
# @return [*] return value of the block
|
data/test/webhukhs_test.rb
CHANGED
|
@@ -25,10 +25,6 @@ class TestWebhukhs < ActionDispatch::IntegrationTest
|
|
|
25
25
|
|
|
26
26
|
teardown { Webhukhs::ReceivedWebhook.delete_all }
|
|
27
27
|
|
|
28
|
-
def test_that_it_has_a_version_number
|
|
29
|
-
refute_nil ::Webhukhs::VERSION
|
|
30
|
-
end
|
|
31
|
-
|
|
32
28
|
def webhook_body
|
|
33
29
|
<<~JSON
|
|
34
30
|
{
|
|
@@ -64,32 +60,8 @@ class TestWebhukhs < ActionDispatch::IntegrationTest
|
|
|
64
60
|
Webhukhs.instance_variable_set(:@configuration, original_configuration)
|
|
65
61
|
end
|
|
66
62
|
|
|
67
|
-
test "
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
Webhukhs.configuration.error_context = {component: "webhooks"}
|
|
71
|
-
|
|
72
|
-
report = assert_error_reported(RuntimeError) do
|
|
73
|
-
post "/webhukhs/failing-with-concealed-errors", params: webhook_body, headers: {"CONTENT_TYPE" => "application/json"}
|
|
74
|
-
|
|
75
|
-
assert_response 200
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
assert_equal "webhooks", report.context[:component]
|
|
79
|
-
ensure
|
|
80
|
-
Webhukhs.configuration.error_context = original_error_context
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
test "ensure webhook is processed only once during creation" do
|
|
84
|
-
tf = Tempfile.new
|
|
85
|
-
body = {isValid: true, outputToFilename: tf.path}
|
|
86
|
-
body_json = body.to_json
|
|
87
|
-
|
|
88
|
-
assert_enqueued_jobs 1, only: Webhukhs::ProcessingJob do
|
|
89
|
-
post "/webhukhs/test", params: body_json, headers: {"CONTENT_TYPE" => "application/json"}
|
|
90
|
-
assert_response 200
|
|
91
|
-
assert_equal({"ok" => true, "error" => nil}, response.parsed_body)
|
|
92
|
-
end
|
|
63
|
+
test "loads engine generators" do
|
|
64
|
+
assert_nothing_raised { Webhukhs::Engine.load_generators }
|
|
93
65
|
end
|
|
94
66
|
|
|
95
67
|
test "accepts a webhook, stores and processes it" do
|
|
@@ -99,20 +71,25 @@ class TestWebhukhs < ActionDispatch::IntegrationTest
|
|
|
99
71
|
|
|
100
72
|
post "/webhukhs/test", params: body_json, headers: {"CONTENT_TYPE" => "application/json"}
|
|
101
73
|
assert_response 200
|
|
74
|
+
assert_equal({"ok" => true, "error" => nil}, response.parsed_body)
|
|
102
75
|
|
|
103
76
|
webhook = Webhukhs::ReceivedWebhook.last!
|
|
104
77
|
|
|
105
|
-
assert_predicate webhook, :received?
|
|
106
|
-
assert_equal "WebhookTestHandler", webhook.handler_module_name
|
|
107
|
-
assert_equal webhook.status, "received"
|
|
108
|
-
assert_equal webhook.body, body_json
|
|
109
|
-
|
|
110
78
|
perform_enqueued_jobs
|
|
111
79
|
assert_predicate webhook.reload, :processed?
|
|
112
80
|
tf.rewind
|
|
113
81
|
assert_equal tf.read, body_json
|
|
114
82
|
end
|
|
115
83
|
|
|
84
|
+
test "stores the body of a form-encoded webhook" do
|
|
85
|
+
# Rails parses the form into params before the handler sees the request, which leaves the body
|
|
86
|
+
# stream at its end
|
|
87
|
+
post "/webhukhs/test", params: "event=paid&amount=12.50", headers: {"CONTENT_TYPE" => "application/x-www-form-urlencoded"}
|
|
88
|
+
assert_response 200
|
|
89
|
+
|
|
90
|
+
assert_equal "event=paid&amount=12.50", Webhukhs::ReceivedWebhook.last!.body
|
|
91
|
+
end
|
|
92
|
+
|
|
116
93
|
test "accepts a webhook but does not process it if it is invalid" do
|
|
117
94
|
tf = Tempfile.new
|
|
118
95
|
body = {isValid: false, outputToFilename: tf.path}
|
|
@@ -123,11 +100,6 @@ class TestWebhukhs < ActionDispatch::IntegrationTest
|
|
|
123
100
|
|
|
124
101
|
webhook = Webhukhs::ReceivedWebhook.last!
|
|
125
102
|
|
|
126
|
-
assert_predicate webhook, :received?
|
|
127
|
-
assert_equal "WebhookTestHandler", webhook.handler_module_name
|
|
128
|
-
assert_equal webhook.status, "received"
|
|
129
|
-
assert_equal webhook.body, body_json
|
|
130
|
-
|
|
131
103
|
perform_enqueued_jobs
|
|
132
104
|
assert_predicate webhook.reload, :failed_validation?
|
|
133
105
|
|
|
@@ -145,11 +117,6 @@ class TestWebhukhs < ActionDispatch::IntegrationTest
|
|
|
145
117
|
|
|
146
118
|
webhook = Webhukhs::ReceivedWebhook.last!
|
|
147
119
|
|
|
148
|
-
assert_predicate webhook, :received?
|
|
149
|
-
assert_equal "WebhookTestHandler", webhook.handler_module_name
|
|
150
|
-
assert_equal webhook.status, "received"
|
|
151
|
-
assert_equal webhook.body, body_json
|
|
152
|
-
|
|
153
120
|
assert_raises(StandardError) { perform_enqueued_jobs }
|
|
154
121
|
assert_predicate webhook.reload, :error?
|
|
155
122
|
|
|
@@ -210,16 +177,22 @@ class TestWebhukhs < ActionDispatch::IntegrationTest
|
|
|
210
177
|
end
|
|
211
178
|
|
|
212
179
|
test "returns a 200 status and error message if the handler does not expose errors" do
|
|
180
|
+
original_error_context = Webhukhs.configuration.error_context
|
|
181
|
+
Webhukhs.configuration.error_context = {component: "webhooks"}
|
|
182
|
+
|
|
213
183
|
report = assert_error_reported(RuntimeError) do
|
|
214
184
|
post "/webhukhs/failing-with-concealed-errors", params: webhook_body, headers: {"CONTENT_TYPE" => "application/json"}
|
|
215
185
|
|
|
216
186
|
assert_response 200
|
|
217
|
-
|
|
187
|
+
assert_false response.parsed_body["ok"]
|
|
218
188
|
assert_equal "Internal error (oops)", response.parsed_body["error"]
|
|
219
189
|
end
|
|
220
190
|
|
|
221
191
|
assert_predicate report, :handled?
|
|
222
192
|
assert_equal :error, report.severity
|
|
193
|
+
assert_equal "webhooks", report.context[:component]
|
|
194
|
+
ensure
|
|
195
|
+
Webhukhs.configuration.error_context = original_error_context
|
|
223
196
|
end
|
|
224
197
|
|
|
225
198
|
test "returns a 500 status and error message if the handler does not expose errors" do
|
|
@@ -256,10 +229,7 @@ class TestWebhukhs < ActionDispatch::IntegrationTest
|
|
|
256
229
|
original_active_handlers = Webhukhs.configuration.active_handlers
|
|
257
230
|
Webhukhs.configuration.active_handlers = original_active_handlers.merge(class_handler: WebhookTestHandler)
|
|
258
231
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
assert_response 200
|
|
262
|
-
assert_equal({"ok" => true, "error" => nil}, response.parsed_body)
|
|
232
|
+
assert_instance_of WebhookTestHandler, Webhukhs::ReceiveWebhooksController.new.lookup_handler("class_handler")
|
|
263
233
|
ensure
|
|
264
234
|
Webhukhs.configuration.active_handlers = original_active_handlers
|
|
265
235
|
end
|
|
@@ -304,16 +274,4 @@ class TestWebhukhs < ActionDispatch::IntegrationTest
|
|
|
304
274
|
assert_equal "received", webhook.status
|
|
305
275
|
end
|
|
306
276
|
end
|
|
307
|
-
|
|
308
|
-
test "ensure_id does not override id for integer primary key columns" do
|
|
309
|
-
webhook = Webhukhs::ReceivedWebhook.create!(
|
|
310
|
-
handler_event_id: SecureRandom.uuid,
|
|
311
|
-
handler_module_name: "WebhookTestHandler",
|
|
312
|
-
status: "received",
|
|
313
|
-
body: {isValid: true}.to_json
|
|
314
|
-
)
|
|
315
|
-
|
|
316
|
-
assert_kind_of Integer, webhook.id
|
|
317
|
-
assert webhook.id > 0
|
|
318
|
-
end
|
|
319
277
|
end
|