webhukhs 0.6.0 → 0.7.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/.standard.yml +2 -0
- data/.yard-lint.yml +295 -0
- data/AGENTS.md +58 -0
- data/Appraisals +4 -0
- data/CHANGELOG.md +7 -0
- data/README.md +9 -7
- data/config/mutant.rb +37 -0
- data/config/mutant.yml +25 -0
- data/gemfiles/rails_master.gemfile +10 -0
- data/lib/webhukhs/base_handler.rb +9 -6
- data/lib/webhukhs/controllers/receive_webhooks_controller.rb +26 -7
- data/lib/webhukhs/engine.rb +1 -0
- data/lib/webhukhs/install_generator.rb +9 -2
- data/lib/webhukhs/jobs/processing_job.rb +13 -12
- data/lib/webhukhs/models/received_webhook.rb +18 -9
- data/lib/webhukhs/version.rb +1 -1
- data/lib/webhukhs.rb +9 -0
- data/test/base_handler_test.rb +125 -0
- data/test/processing_job_test.rb +155 -2
- data/test/received_webhook_test.rb +244 -0
- data/test/test_app.rb +37 -15
- data/test/test_helper.rb +66 -0
- data/test/webhukhs_test.rb +114 -10
- metadata +10 -5
|
@@ -3,35 +3,36 @@
|
|
|
3
3
|
require "active_job/railtie"
|
|
4
4
|
|
|
5
5
|
module Webhukhs
|
|
6
|
+
# Background job that validates and processes a persisted webhook.
|
|
6
7
|
class ProcessingJob < ActiveJob::Base
|
|
8
|
+
# Raised when the job receives an invalid webhook argument.
|
|
7
9
|
class InvalidWebhookArgument < StandardError; end
|
|
8
10
|
|
|
9
|
-
discard_on ActiveJob::DeserializationError do |job, error|
|
|
10
|
-
Rails.error.report(error, context: {
|
|
11
|
-
job_id: job.job_id,
|
|
12
|
-
arguments: job.arguments.map(&:inspect)
|
|
13
|
-
}, severity: :error)
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
discard_on InvalidWebhookArgument do |job, error|
|
|
11
|
+
discard_on ActiveJob::DeserializationError, InvalidWebhookArgument do |job, error|
|
|
17
12
|
Rails.error.report(error, context: {
|
|
18
13
|
job_id: job.job_id,
|
|
19
14
|
arguments: job.arguments.map(&:inspect)
|
|
20
15
|
}, severity: :error)
|
|
21
16
|
end
|
|
22
17
|
|
|
18
|
+
# Runs webhook validation and processing lifecycle.
|
|
19
|
+
#
|
|
20
|
+
# @param webhook [Webhukhs::ReceivedWebhook] webhook record to process
|
|
21
|
+
# @return [void]
|
|
23
22
|
def perform(webhook)
|
|
24
23
|
raise InvalidWebhookArgument, "ProcessingJob received nil webhook argument" if webhook.nil?
|
|
25
|
-
unless webhook.
|
|
24
|
+
unless webhook.instance_of?(ReceivedWebhook)
|
|
26
25
|
raise InvalidWebhookArgument, "ProcessingJob expected Webhukhs::ReceivedWebhook, got #{webhook.class}"
|
|
27
26
|
end
|
|
28
27
|
|
|
29
28
|
webhook_details_for_logs = "Webhukhs::ReceivedWebhook#%s (handler: %s)" % [webhook.id, webhook.handler]
|
|
29
|
+
|
|
30
30
|
webhook.with_lock do
|
|
31
31
|
unless webhook.received?
|
|
32
32
|
logger.info { "#{webhook_details_for_logs} is being processed in a different job or has been processed already, skipping." }
|
|
33
33
|
return
|
|
34
34
|
end
|
|
35
|
+
|
|
35
36
|
webhook.processing!
|
|
36
37
|
end
|
|
37
38
|
|
|
@@ -44,9 +45,9 @@ module Webhukhs
|
|
|
44
45
|
logger.info { "#{webhook_details_for_logs} did not pass validation by the handler. Marking it `failed_validation`." }
|
|
45
46
|
webhook.failed_validation!
|
|
46
47
|
end
|
|
47
|
-
rescue
|
|
48
|
-
webhook.error!
|
|
49
|
-
raise
|
|
48
|
+
rescue
|
|
49
|
+
webhook.error! if webhook.respond_to?(:error!)
|
|
50
|
+
raise
|
|
50
51
|
end
|
|
51
52
|
end
|
|
52
53
|
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "state_machine_enum"
|
|
4
4
|
|
|
5
5
|
module Webhukhs
|
|
6
|
+
# ActiveRecord model that stores received webhook requests for async processing.
|
|
6
7
|
class ReceivedWebhook < ActiveRecord::Base
|
|
7
8
|
self.implicit_order_column = "created_at"
|
|
8
9
|
self.table_name = "received_webhooks"
|
|
@@ -25,12 +26,14 @@ module Webhukhs
|
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
# Store the pertinent data from an ActionDispatch::Request into the webhook.
|
|
28
|
-
#
|
|
29
|
+
#
|
|
30
|
+
# @param action_dispatch_request [ActionDispatch::Request] request to persist
|
|
31
|
+
# @return [void]
|
|
29
32
|
def request=(action_dispatch_request)
|
|
30
33
|
# Filter out all Rack-specific headers such as "rack.input" and the like. We are
|
|
31
34
|
# only interested in the actual HTTP headers presented by the webserver. Mostly...
|
|
32
|
-
headers = action_dispatch_request.env.filter_map do |
|
|
33
|
-
if header_name.
|
|
35
|
+
headers = action_dispatch_request.env.filter_map do |header_name, header_value|
|
|
36
|
+
if header_name.instance_of?(String) && header_name.upcase == header_name && header_value.instance_of?(String)
|
|
34
37
|
[header_name, header_value]
|
|
35
38
|
end
|
|
36
39
|
end.to_h
|
|
@@ -47,10 +50,10 @@ module Webhukhs
|
|
|
47
50
|
# Verify the request body is not too large
|
|
48
51
|
request_body_io = action_dispatch_request.env.fetch("rack.input")
|
|
49
52
|
if request_body_io.size > Webhukhs.configuration.request_body_size_limit
|
|
50
|
-
raise "Cannot accept the webhook as the request body is larger than #{
|
|
53
|
+
raise "Cannot accept the webhook as the request body is larger than #{Webhukhs.configuration.request_body_size_limit} bytes"
|
|
51
54
|
end
|
|
52
55
|
|
|
53
|
-
write_attribute("body", request_body_io.read.
|
|
56
|
+
write_attribute("body", request_body_io.read.b)
|
|
54
57
|
write_attribute("request_headers", headers)
|
|
55
58
|
ensure
|
|
56
59
|
request_body_io.rewind
|
|
@@ -91,25 +94,31 @@ module Webhukhs
|
|
|
91
94
|
#
|
|
92
95
|
# @return [ActionDispatch::Request]
|
|
93
96
|
def request
|
|
94
|
-
ActionDispatch::Request.new(request_headers.merge
|
|
97
|
+
ActionDispatch::Request.new(request_headers.merge("rack.input" => StringIO.new(body.b)))
|
|
95
98
|
end
|
|
96
99
|
|
|
100
|
+
# Instantiates the configured handler for this webhook.
|
|
101
|
+
#
|
|
102
|
+
# @return [Webhukhs::BaseHandler]
|
|
97
103
|
def handler
|
|
98
104
|
handler_module_name.constantize.new
|
|
99
105
|
end
|
|
100
106
|
|
|
101
107
|
private
|
|
102
108
|
|
|
109
|
+
# Assigns a generated id when database does not auto-increment ids.
|
|
110
|
+
#
|
|
111
|
+
# @return [void]
|
|
103
112
|
def ensure_id
|
|
104
113
|
return if id.present?
|
|
105
114
|
# let DB auto-increment handle it
|
|
106
|
-
return if self.class.columns_hash
|
|
115
|
+
return if self.class.columns_hash.fetch("id").type == :integer
|
|
107
116
|
|
|
108
117
|
self.id = if SecureRandom.respond_to?(:uuid_v7)
|
|
109
|
-
#
|
|
118
|
+
# Ruby 3.3+ UUIDv7 keeps inserts more index-friendly than random UUIDv4.
|
|
110
119
|
SecureRandom.uuid_v7
|
|
111
120
|
else
|
|
112
|
-
#
|
|
121
|
+
# Fallback for Rubies without UUIDv7 support.
|
|
113
122
|
SecureRandom.uuid
|
|
114
123
|
end
|
|
115
124
|
end
|
data/lib/webhukhs/version.rb
CHANGED
data/lib/webhukhs.rb
CHANGED
|
@@ -5,16 +5,25 @@ require_relative "webhukhs/engine"
|
|
|
5
5
|
require_relative "webhukhs/jobs/processing_job"
|
|
6
6
|
require "active_support/core_ext/class/attribute"
|
|
7
7
|
|
|
8
|
+
# Public namespace for Webhukhs runtime and configuration.
|
|
8
9
|
module Webhukhs
|
|
10
|
+
# Returns singleton configuration object.
|
|
11
|
+
#
|
|
12
|
+
# @return [Webhukhs::Configuration]
|
|
9
13
|
def self.configuration
|
|
10
14
|
@configuration ||= Configuration.new
|
|
11
15
|
end
|
|
12
16
|
|
|
17
|
+
# Yields global configuration object for setup.
|
|
18
|
+
#
|
|
19
|
+
# @yieldparam configuration [Webhukhs::Configuration]
|
|
20
|
+
# @return [void]
|
|
13
21
|
def self.configure
|
|
14
22
|
yield configuration
|
|
15
23
|
end
|
|
16
24
|
end
|
|
17
25
|
|
|
26
|
+
# Holds runtime configuration for Webhukhs.
|
|
18
27
|
class Webhukhs::Configuration
|
|
19
28
|
class_attribute :processing_job_class, default: Webhukhs::ProcessingJob
|
|
20
29
|
class_attribute :active_handlers, default: {}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class EnqueueProbeJob
|
|
6
|
+
class << self
|
|
7
|
+
attr_accessor :received_webhook
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.perform_later(webhook)
|
|
11
|
+
self.received_webhook = webhook
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class HandleProbeHandler < Webhukhs::BaseHandler
|
|
16
|
+
attr_reader :enqueued_webhook
|
|
17
|
+
|
|
18
|
+
def enqueue(webhook)
|
|
19
|
+
@enqueued_webhook = webhook
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class BaseHandlerTest < ActiveSupport::TestCase
|
|
24
|
+
cover "Webhukhs::BaseHandler*"
|
|
25
|
+
|
|
26
|
+
teardown { Webhukhs::ReceivedWebhook.delete_all }
|
|
27
|
+
|
|
28
|
+
test "process is a no-op by default" do
|
|
29
|
+
assert_nil Webhukhs::BaseHandler.new.process(nil)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
test "defaults to active, valid and exposing errors" do
|
|
33
|
+
handler = Webhukhs::BaseHandler.new
|
|
34
|
+
|
|
35
|
+
assert_equal true, handler.active?
|
|
36
|
+
assert_equal true, handler.valid?(nil)
|
|
37
|
+
assert_equal true, handler.expose_errors_to_sender?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
test "generates unique UUID event ids by default" do
|
|
41
|
+
handler = Webhukhs::BaseHandler.new
|
|
42
|
+
|
|
43
|
+
first_id = handler.extract_event_id_from_request(nil)
|
|
44
|
+
second_id = handler.extract_event_id_from_request(nil)
|
|
45
|
+
|
|
46
|
+
assert_match(/\A[0-9a-f-]{36}\z/, first_id)
|
|
47
|
+
assert_match(/\A[0-9a-f-]{36}\z/, second_id)
|
|
48
|
+
refute_equal first_id, second_id
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
test "handle passes the handler class name into received webhooks" do
|
|
52
|
+
captured_attributes = nil
|
|
53
|
+
request = ActionDispatch::Request.new(
|
|
54
|
+
"rack.input" => StringIO.new("{}"),
|
|
55
|
+
"REQUEST_METHOD" => "POST",
|
|
56
|
+
"CONTENT_TYPE" => "application/json",
|
|
57
|
+
"action_dispatch.request.path_parameters" => {}
|
|
58
|
+
)
|
|
59
|
+
webhook = Object.new
|
|
60
|
+
handler = HandleProbeHandler.new
|
|
61
|
+
|
|
62
|
+
def webhook.save!
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
with_overridden_singleton_method(Webhukhs::ReceivedWebhook, :new, ->(**attributes) {
|
|
66
|
+
captured_attributes = attributes
|
|
67
|
+
webhook
|
|
68
|
+
}) do
|
|
69
|
+
handler.handle(request)
|
|
70
|
+
|
|
71
|
+
assert_same webhook, handler.enqueued_webhook
|
|
72
|
+
assert_equal "HandleProbeHandler", captured_attributes.fetch(:handler_module_name)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
test "handle logs duplicate deliveries" do
|
|
77
|
+
handler = ExtractIdHandler.new
|
|
78
|
+
request_headers = {
|
|
79
|
+
"CONTENT_TYPE" => "application/json",
|
|
80
|
+
"action_dispatch.request.path_parameters" => {}
|
|
81
|
+
}
|
|
82
|
+
payload = {event_id: "duplicate-event"}.to_json
|
|
83
|
+
|
|
84
|
+
first_request = ActionDispatch::Request.new(request_headers.merge("rack.input" => StringIO.new(payload)))
|
|
85
|
+
second_request = ActionDispatch::Request.new(request_headers.merge("rack.input" => StringIO.new(payload)))
|
|
86
|
+
|
|
87
|
+
handler.handle(first_request)
|
|
88
|
+
|
|
89
|
+
with_captured_info_logs(Rails) do |messages|
|
|
90
|
+
handler.handle(second_request)
|
|
91
|
+
|
|
92
|
+
assert_equal ["#{handler.inspect} Webhook duplicate-event is a duplicate delivery and will not be stored."], messages
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
test "enqueue uses configured processing job classes directly" do
|
|
97
|
+
original_job_class = Webhukhs.configuration.processing_job_class
|
|
98
|
+
webhook = Object.new
|
|
99
|
+
|
|
100
|
+
Webhukhs.configuration.processing_job_class = EnqueueProbeJob
|
|
101
|
+
EnqueueProbeJob.received_webhook = nil
|
|
102
|
+
|
|
103
|
+
Webhukhs::BaseHandler.new.enqueue(webhook)
|
|
104
|
+
|
|
105
|
+
assert_same webhook, EnqueueProbeJob.received_webhook
|
|
106
|
+
ensure
|
|
107
|
+
Webhukhs.configuration.processing_job_class = original_job_class
|
|
108
|
+
EnqueueProbeJob.received_webhook = nil
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
test "enqueue constantizes configured processing job class names" do
|
|
112
|
+
original_job_class = Webhukhs.configuration.processing_job_class
|
|
113
|
+
webhook = Object.new
|
|
114
|
+
|
|
115
|
+
Webhukhs.configuration.processing_job_class = "EnqueueProbeJob"
|
|
116
|
+
EnqueueProbeJob.received_webhook = nil
|
|
117
|
+
|
|
118
|
+
Webhukhs::BaseHandler.new.enqueue(webhook)
|
|
119
|
+
|
|
120
|
+
assert_same webhook, EnqueueProbeJob.received_webhook
|
|
121
|
+
ensure
|
|
122
|
+
Webhukhs.configuration.processing_job_class = original_job_class
|
|
123
|
+
EnqueueProbeJob.received_webhook = nil
|
|
124
|
+
end
|
|
125
|
+
end
|
data/test/processing_job_test.rb
CHANGED
|
@@ -1,10 +1,41 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "test_helper"
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
class SkipDuringProcessingHandler < Webhukhs::BaseHandler
|
|
6
|
+
def process(webhook)
|
|
7
|
+
webhook.skipped!
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class LoggingHandler < Webhukhs::BaseHandler
|
|
12
|
+
class_attribute :processed_webhook_ids, default: []
|
|
13
|
+
|
|
14
|
+
def self.reset!
|
|
15
|
+
self.processed_webhook_ids = []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_s = self.class.name
|
|
19
|
+
|
|
20
|
+
def valid?(_request) = true
|
|
21
|
+
|
|
22
|
+
def process(webhook)
|
|
23
|
+
self.class.processed_webhook_ids += [webhook.id]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class InvalidLoggingHandler < LoggingHandler
|
|
28
|
+
def valid?(_request) = false
|
|
29
|
+
end
|
|
5
30
|
|
|
6
31
|
class ProcessingJobTest < ActiveJob::TestCase
|
|
7
|
-
|
|
32
|
+
cover "Webhukhs::ProcessingJob*"
|
|
33
|
+
|
|
34
|
+
teardown do
|
|
35
|
+
Webhukhs::ReceivedWebhook.delete_all
|
|
36
|
+
LoggingHandler.reset!
|
|
37
|
+
InvalidLoggingHandler.reset!
|
|
38
|
+
end
|
|
8
39
|
|
|
9
40
|
test "discards job and reports error when webhook argument is nil" do
|
|
10
41
|
assert_error_reported(Webhukhs::ProcessingJob::InvalidWebhookArgument) do
|
|
@@ -14,6 +45,14 @@ class ProcessingJobTest < ActiveJob::TestCase
|
|
|
14
45
|
assert_no_enqueued_jobs only: Webhukhs::ProcessingJob
|
|
15
46
|
end
|
|
16
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
|
+
end
|
|
55
|
+
|
|
17
56
|
test "discards job and reports error when webhook argument is not a ReceivedWebhook" do
|
|
18
57
|
assert_error_reported(Webhukhs::ProcessingJob::InvalidWebhookArgument) do
|
|
19
58
|
Webhukhs::ProcessingJob.perform_now("not a webhook")
|
|
@@ -22,6 +61,120 @@ class ProcessingJobTest < ActiveJob::TestCase
|
|
|
22
61
|
assert_no_enqueued_jobs only: Webhukhs::ProcessingJob
|
|
23
62
|
end
|
|
24
63
|
|
|
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")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
assert_equal "ProcessingJob expected Webhukhs::ReceivedWebhook, got String", error.message
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
test "processes webhooks under a lock" do
|
|
73
|
+
webhook = Webhukhs::ReceivedWebhook.create!(
|
|
74
|
+
handler_event_id: "lock-test",
|
|
75
|
+
handler_module_name: "WebhookTestHandler",
|
|
76
|
+
status: "received",
|
|
77
|
+
body: {isValid: false}.to_json,
|
|
78
|
+
request_headers: {
|
|
79
|
+
"CONTENT_TYPE" => "application/json",
|
|
80
|
+
"action_dispatch.request.path_parameters" => {}
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
with_lock_called = false
|
|
84
|
+
|
|
85
|
+
webhook.define_singleton_method(:with_lock) do |&block|
|
|
86
|
+
with_lock_called = true
|
|
87
|
+
block.call
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
Webhukhs::ProcessingJob.new.perform(webhook)
|
|
91
|
+
|
|
92
|
+
assert_equal true, with_lock_called
|
|
93
|
+
assert_predicate webhook.reload, :failed_validation?
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
test "logs when processing is skipped because the webhook is not received" do
|
|
97
|
+
webhook = Webhukhs::ReceivedWebhook.create!(
|
|
98
|
+
handler_event_id: "skip-log-test",
|
|
99
|
+
handler_module_name: "LoggingHandler",
|
|
100
|
+
status: "processing",
|
|
101
|
+
body: {}.to_json,
|
|
102
|
+
request_headers: {}
|
|
103
|
+
)
|
|
104
|
+
job = Webhukhs::ProcessingJob.new
|
|
105
|
+
details = "Webhukhs::ReceivedWebhook##{webhook.id} (handler: LoggingHandler)"
|
|
106
|
+
|
|
107
|
+
with_captured_info_logs(Webhukhs::ProcessingJob) do |messages|
|
|
108
|
+
job.perform(webhook)
|
|
109
|
+
|
|
110
|
+
assert_equal ["#{details} is being processed in a different job or has been processed already, skipping."], messages
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
test "logs when processing starts and completes" do
|
|
115
|
+
webhook = Webhukhs::ReceivedWebhook.create!(
|
|
116
|
+
handler_event_id: "success-log-test",
|
|
117
|
+
handler_module_name: "LoggingHandler",
|
|
118
|
+
status: "received",
|
|
119
|
+
body: {}.to_json,
|
|
120
|
+
request_headers: {
|
|
121
|
+
"CONTENT_TYPE" => "application/json",
|
|
122
|
+
"action_dispatch.request.path_parameters" => {}
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
job = Webhukhs::ProcessingJob.new
|
|
126
|
+
details = "Webhukhs::ReceivedWebhook##{webhook.id} (handler: LoggingHandler)"
|
|
127
|
+
|
|
128
|
+
with_captured_info_logs(Webhukhs::ProcessingJob) do |messages|
|
|
129
|
+
job.perform(webhook)
|
|
130
|
+
|
|
131
|
+
assert_equal ["#{details} starting to process", "#{details} processed"], messages
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
assert_predicate webhook.reload, :processed?
|
|
135
|
+
assert_equal [webhook.id], LoggingHandler.processed_webhook_ids
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
test "logs when validation fails" do
|
|
139
|
+
webhook = Webhukhs::ReceivedWebhook.create!(
|
|
140
|
+
handler_event_id: "failed-validation-log-test",
|
|
141
|
+
handler_module_name: "InvalidLoggingHandler",
|
|
142
|
+
status: "received",
|
|
143
|
+
body: {}.to_json,
|
|
144
|
+
request_headers: {
|
|
145
|
+
"CONTENT_TYPE" => "application/json",
|
|
146
|
+
"action_dispatch.request.path_parameters" => {}
|
|
147
|
+
}
|
|
148
|
+
)
|
|
149
|
+
job = Webhukhs::ProcessingJob.new
|
|
150
|
+
details = "Webhukhs::ReceivedWebhook##{webhook.id} (handler: InvalidLoggingHandler)"
|
|
151
|
+
|
|
152
|
+
with_captured_info_logs(Webhukhs::ProcessingJob) do |messages|
|
|
153
|
+
job.perform(webhook)
|
|
154
|
+
|
|
155
|
+
assert_equal ["#{details} did not pass validation by the handler. Marking it `failed_validation`."], messages
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
assert_predicate webhook.reload, :failed_validation?
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
test "does not overwrite webhook state changed by the handler" do
|
|
162
|
+
webhook = Webhukhs::ReceivedWebhook.create!(
|
|
163
|
+
handler_event_id: "skip-test",
|
|
164
|
+
handler_module_name: "SkipDuringProcessingHandler",
|
|
165
|
+
status: "received",
|
|
166
|
+
body: {}.to_json,
|
|
167
|
+
request_headers: {
|
|
168
|
+
"CONTENT_TYPE" => "application/json",
|
|
169
|
+
"action_dispatch.request.path_parameters" => {}
|
|
170
|
+
}
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
Webhukhs::ProcessingJob.new.perform(webhook)
|
|
174
|
+
|
|
175
|
+
assert_predicate webhook.reload, :skipped?
|
|
176
|
+
end
|
|
177
|
+
|
|
25
178
|
test "discards job and reports error when webhook record has been deleted (DeserializationError)" do
|
|
26
179
|
webhook = Webhukhs::ReceivedWebhook.create!(
|
|
27
180
|
handler_event_id: "deserialization-test",
|