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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.yard-lint.yml +1 -0
  3. data/AGENTS.md +9 -11
  4. data/Appraisals +2 -2
  5. data/CHANGELOG.md +13 -0
  6. data/README.md +98 -10
  7. data/Rakefile +7 -0
  8. data/example/Gemfile +5 -6
  9. data/example/Gemfile.lock +260 -141
  10. data/example/app/webhooks/acme_handler.rb +16 -0
  11. data/example/app/webhooks/flaky_handler.rb +8 -0
  12. data/example/app/webhooks/signed_handler.rb +10 -0
  13. data/example/config/initializers/webhukhs.rb +4 -3
  14. data/example/config/routes.rb +3 -0
  15. data/example/db/migrate/20240601000000_add_headers_to_webhukhs_webhooks.rb +7 -0
  16. data/example/db/schema.rb +2 -1
  17. data/example/db/seeds.rb +71 -7
  18. data/gemfiles/{rails_7.1.gemfile → rails_7.2.gemfile} +1 -4
  19. data/gemfiles/rails_7.2.gemfile.lock +477 -0
  20. data/gemfiles/rails_8.0.gemfile +0 -3
  21. data/gemfiles/rails_8.0.gemfile.lock +74 -4
  22. data/gemfiles/rails_8.1.gemfile +0 -3
  23. data/gemfiles/rails_8.1.gemfile.lock +74 -4
  24. data/gemfiles/rails_master.gemfile +0 -3
  25. data/lib/webhukhs/admin/application_controller.rb +32 -0
  26. data/lib/webhukhs/admin/assets/admin.css +476 -0
  27. data/lib/webhukhs/admin/assets/admin.js +111 -0
  28. data/lib/webhukhs/admin/config/routes.rb +6 -0
  29. data/lib/webhukhs/admin/engine.rb +20 -0
  30. data/lib/webhukhs/admin/received_webhook_presenter.rb +63 -0
  31. data/lib/webhukhs/admin/received_webhooks_controller.rb +60 -0
  32. data/lib/webhukhs/admin/view_helper.rb +50 -0
  33. data/lib/webhukhs/admin/views/layouts/webhukhs/admin/application.html.erb +22 -0
  34. data/lib/webhukhs/admin/views/webhukhs/admin/received_webhooks/index.html.erb +50 -0
  35. data/lib/webhukhs/admin/views/webhukhs/admin/received_webhooks/show.html.erb +55 -0
  36. data/lib/webhukhs/admin.rb +17 -0
  37. data/lib/webhukhs/engine.rb +0 -4
  38. data/lib/webhukhs/models/received_webhook.rb +3 -0
  39. data/lib/webhukhs/version.rb +1 -1
  40. data/test/admin_test.rb +334 -0
  41. data/test/base_handler_test.rb +3 -3
  42. data/test/browser/admin_click_through.rb +208 -0
  43. data/test/processing_job_test.rb +15 -28
  44. data/test/received_webhook_test.rb +16 -0
  45. data/test/test_app.rb +2 -0
  46. data/test/test_helper.rb +9 -6
  47. data/test/webhukhs_test.rb +20 -62
  48. metadata +165 -8
  49. data/example/.ruby-version +0 -1
  50. 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
@@ -37,36 +37,25 @@ class ProcessingJobTest < ActiveJob::TestCase
37
37
  InvalidLoggingHandler.reset!
38
38
  end
39
39
 
40
- test "discards job and reports error when webhook argument is nil" do
41
- assert_error_reported(Webhukhs::ProcessingJob::InvalidWebhookArgument) do
42
- Webhukhs::ProcessingJob.perform_now(nil)
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 "discards job and reports error when webhook argument is not a ReceivedWebhook" do
57
- assert_error_reported(Webhukhs::ProcessingJob::InvalidWebhookArgument) do
58
- Webhukhs::ProcessingJob.perform_now("not a webhook")
59
- end
60
-
61
- assert_no_enqueued_jobs only: Webhukhs::ProcessingJob
62
- end
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
- test "raises a specific error message when webhook argument is not a ReceivedWebhook" do
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
- assert_equal true, with_lock_called
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
@@ -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 "applies the configured error context before handling requests" do
68
- original_error_context = Webhukhs.configuration.error_context
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
- assert_equal false, response.parsed_body["ok"]
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
- post "/webhukhs/class_handler", params: {isValid: false, outputToFilename: "/tmp/unused"}.to_json, headers: {"CONTENT_TYPE" => "application/json"}
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