shoryuken 7.0.0.alpha2 → 7.0.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/.github/workflows/push.yml +2 -2
- data/.github/workflows/specs.yml +38 -43
- data/.github/workflows/verify-action-pins.yml +1 -1
- data/.gitignore +3 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -1
- data/.yard-lint.yml +279 -0
- data/CHANGELOG.md +69 -1
- data/Gemfile +1 -1
- data/README.md +2 -7
- data/Rakefile +4 -10
- data/bin/clean_localstack +52 -0
- data/bin/cli/base.rb +21 -0
- data/bin/cli/sqs.rb +61 -2
- data/bin/integrations +275 -0
- data/bin/scenario +154 -0
- data/bin/shoryuken +1 -1
- data/lib/{shoryuken/extensions/active_job_extensions.rb → active_job/extensions.rb} +15 -4
- data/lib/active_job/queue_adapters/shoryuken_adapter.rb +208 -0
- data/lib/active_job/queue_adapters/shoryuken_concurrent_send_adapter.rb +78 -0
- data/lib/shoryuken/active_job/current_attributes.rb +139 -0
- data/lib/shoryuken/active_job/job_wrapper.rb +28 -0
- data/lib/shoryuken/body_parser.rb +8 -0
- data/lib/shoryuken/client.rb +14 -0
- data/lib/shoryuken/default_exception_handler.rb +9 -0
- data/lib/shoryuken/default_worker_registry.rb +29 -1
- data/lib/shoryuken/environment_loader.rb +78 -8
- data/lib/shoryuken/errors.rb +33 -0
- data/lib/shoryuken/fetcher.rb +37 -1
- data/lib/shoryuken/helpers/atomic_boolean.rb +19 -5
- data/lib/shoryuken/helpers/timer_task.rb +80 -0
- data/lib/shoryuken/launcher.rb +53 -0
- data/lib/shoryuken/logging/base.rb +26 -0
- data/lib/shoryuken/logging/pretty.rb +25 -0
- data/lib/shoryuken/logging/without_timestamp.rb +25 -0
- data/lib/shoryuken/logging.rb +39 -25
- data/lib/shoryuken/manager.rb +70 -1
- data/lib/shoryuken/message.rb +114 -1
- data/lib/shoryuken/middleware/chain.rb +139 -43
- data/lib/shoryuken/middleware/entry.rb +30 -0
- data/lib/shoryuken/middleware/server/active_record.rb +8 -0
- data/lib/shoryuken/middleware/server/auto_delete.rb +10 -0
- data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +27 -1
- data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +29 -0
- data/lib/shoryuken/middleware/server/timing.rb +11 -0
- data/lib/shoryuken/options.rb +129 -6
- data/lib/shoryuken/polling/base_strategy.rb +1 -0
- data/lib/shoryuken/polling/strict_priority.rb +39 -0
- data/lib/shoryuken/polling/weighted_round_robin.rb +42 -0
- data/lib/shoryuken/processor.rb +32 -1
- data/lib/shoryuken/queue.rb +93 -4
- data/lib/shoryuken/runner.rb +45 -4
- data/lib/shoryuken/util.rb +26 -1
- data/lib/shoryuken/version.rb +2 -1
- data/lib/shoryuken/worker/default_executor.rb +21 -1
- data/lib/shoryuken/worker/inline_executor.rb +24 -0
- data/lib/shoryuken/worker.rb +193 -0
- data/lib/shoryuken/worker_registry.rb +33 -0
- data/lib/shoryuken.rb +18 -6
- data/renovate.json +29 -2
- data/shoryuken.gemspec +2 -1
- data/spec/integration/.rspec +1 -0
- data/spec/integration/active_job/adapter_configuration/configuration_spec.rb +26 -0
- data/spec/integration/active_job/bulk_enqueue/bulk_enqueue_spec.rb +53 -0
- data/spec/integration/active_job/current_attributes/bulk_enqueue_spec.rb +50 -0
- data/spec/integration/active_job/current_attributes/complex_types_spec.rb +55 -0
- data/spec/integration/active_job/current_attributes/empty_context_spec.rb +41 -0
- data/spec/integration/active_job/current_attributes/full_context_spec.rb +63 -0
- data/spec/integration/active_job/current_attributes/partial_context_spec.rb +57 -0
- data/spec/integration/active_job/custom_attributes/number_attributes_spec.rb +37 -0
- data/spec/integration/active_job/custom_attributes/string_attributes_spec.rb +39 -0
- data/spec/integration/active_job/error_handling/job_wrapper_spec.rb +53 -0
- data/spec/integration/active_job/fifo_and_attributes/deduplication_spec.rb +86 -0
- data/spec/integration/active_job/retry/discard_on_spec.rb +43 -0
- data/spec/integration/active_job/retry/retry_on_spec.rb +36 -0
- data/spec/integration/active_job/roundtrip/roundtrip_spec.rb +52 -0
- data/spec/integration/active_job/scheduled/scheduled_spec.rb +76 -0
- data/spec/integration/active_record_middleware/active_record_middleware_spec.rb +84 -0
- data/spec/integration/auto_delete/auto_delete_spec.rb +53 -0
- data/spec/integration/auto_extend_visibility/auto_extend_visibility_spec.rb +57 -0
- data/spec/integration/aws_config/aws_config_spec.rb +59 -0
- data/spec/integration/batch_processing/batch_processing_spec.rb +37 -0
- data/spec/integration/body_parser/json_parser_spec.rb +45 -0
- data/spec/integration/body_parser/proc_parser_spec.rb +54 -0
- data/spec/integration/body_parser/text_parser_spec.rb +43 -0
- data/spec/integration/concurrent_processing/concurrent_processing_spec.rb +45 -0
- data/spec/integration/dead_letter_queue/dead_letter_queue_spec.rb +91 -0
- data/spec/integration/exception_handlers/exception_handlers_spec.rb +69 -0
- data/spec/integration/exponential_backoff/exponential_backoff_spec.rb +67 -0
- data/spec/integration/fifo_ordering/fifo_ordering_spec.rb +44 -0
- data/spec/integration/large_payloads/large_payloads_spec.rb +30 -0
- data/spec/integration/launcher/launcher_spec.rb +40 -0
- data/spec/integration/message_attributes/message_attributes_spec.rb +54 -0
- data/spec/integration/message_operations/message_operations_spec.rb +59 -0
- data/spec/integration/middleware_chain/empty_chain_spec.rb +11 -0
- data/spec/integration/middleware_chain/execution_order_spec.rb +33 -0
- data/spec/integration/middleware_chain/removal_spec.rb +31 -0
- data/spec/integration/middleware_chain/short_circuit_spec.rb +40 -0
- data/spec/integration/polling_strategies/polling_strategies_spec.rb +46 -0
- data/spec/integration/queue_operations/queue_operations_spec.rb +84 -0
- data/spec/integration/rails/rails_72/Gemfile +6 -0
- data/spec/integration/rails/rails_72/activejob_adapter_spec.rb +98 -0
- data/spec/integration/rails/rails_80/Gemfile +6 -0
- data/spec/integration/rails/rails_80/activejob_adapter_spec.rb +98 -0
- data/spec/integration/rails/rails_80/continuation_spec.rb +79 -0
- data/spec/integration/rails/rails_81/Gemfile +6 -0
- data/spec/integration/rails/rails_81/activejob_adapter_spec.rb +98 -0
- data/spec/integration/rails/rails_81/continuation_spec.rb +79 -0
- data/spec/integration/retry_behavior/retry_behavior_spec.rb +45 -0
- data/spec/integration/spec_helper.rb +7 -0
- data/spec/integration/strict_priority_polling/strict_priority_polling_spec.rb +58 -0
- data/spec/integration/visibility_timeout/visibility_timeout_spec.rb +37 -0
- data/spec/integration/worker_enqueueing/worker_enqueueing_spec.rb +60 -0
- data/spec/integration/worker_groups/worker_groups_spec.rb +79 -0
- data/spec/integration/worker_lifecycle/worker_lifecycle_spec.rb +33 -0
- data/spec/integrations_helper.rb +243 -0
- data/spec/lib/active_job/extensions_spec.rb +149 -0
- data/spec/lib/active_job/queue_adapters/shoryuken_adapter_spec.rb +29 -0
- data/spec/{shoryuken/extensions/active_job_concurrent_send_adapter_spec.rb → lib/active_job/queue_adapters/shoryuken_concurrent_send_adapter_spec.rb} +3 -3
- data/spec/{shoryuken/extensions/active_job_wrapper_spec.rb → lib/shoryuken/active_job/job_wrapper_spec.rb} +4 -4
- data/spec/{shoryuken → lib/shoryuken}/environment_loader_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/helpers/hash_utils_spec.rb +14 -14
- data/spec/{shoryuken → lib/shoryuken}/helpers/string_utils_spec.rb +3 -3
- data/spec/lib/shoryuken/helpers/timer_task_spec.rb +298 -0
- data/spec/{shoryuken → lib/shoryuken}/helpers_integration_spec.rb +9 -9
- data/spec/{shoryuken → lib/shoryuken}/launcher_spec.rb +22 -0
- data/spec/lib/shoryuken/logging_spec.rb +242 -0
- data/spec/lib/shoryuken/message_spec.rb +109 -0
- data/spec/lib/shoryuken/middleware/entry_spec.rb +68 -0
- data/spec/lib/shoryuken/middleware/server/active_record_spec.rb +133 -0
- data/spec/{shoryuken → lib/shoryuken}/middleware/server/auto_extend_visibility_spec.rb +50 -0
- data/spec/{shoryuken → lib/shoryuken}/options_spec.rb +2 -2
- data/spec/{shoryuken → lib/shoryuken}/util_spec.rb +1 -1
- data/spec/lib/shoryuken/version_spec.rb +17 -0
- data/spec/lib/shoryuken/worker_registry_spec.rb +63 -0
- data/spec/shared_examples_for_active_job.rb +29 -9
- data/spec/spec_helper.rb +34 -3
- metadata +230 -91
- data/.devcontainer/Dockerfile +0 -17
- data/.devcontainer/base.Dockerfile +0 -43
- data/.devcontainer/devcontainer.json +0 -35
- data/Appraisals +0 -23
- data/gemfiles/.gitignore +0 -1
- data/gemfiles/rails_7_0.gemfile +0 -19
- data/gemfiles/rails_7_1.gemfile +0 -19
- data/gemfiles/rails_7_2.gemfile +0 -19
- data/gemfiles/rails_8_0.gemfile +0 -19
- data/lib/shoryuken/extensions/active_job_adapter.rb +0 -110
- data/lib/shoryuken/extensions/active_job_concurrent_send_adapter.rb +0 -50
- data/spec/integration/launcher_spec.rb +0 -127
- data/spec/shoryuken/extensions/active_job_adapter_spec.rb +0 -8
- data/spec/shoryuken/extensions/active_job_base_spec.rb +0 -85
- /data/spec/{shoryuken → lib/shoryuken}/body_parser_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/client_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/default_exception_handler_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/default_worker_registry_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/fetcher_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/helpers/atomic_boolean_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/helpers/atomic_counter_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/helpers/atomic_hash_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/inline_message_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/manager_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/middleware/chain_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/middleware/server/auto_delete_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/middleware/server/exponential_backoff_retry_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/middleware/server/timing_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/polling/base_strategy_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/polling/queue_configuration_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/polling/strict_priority_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/polling/weighted_round_robin_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/processor_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/queue_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/runner_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/worker/default_executor_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/worker/inline_executor_spec.rb +0 -0
- /data/spec/{shoryuken → lib/shoryuken}/worker_spec.rb +0 -0
- /data/spec/{shoryuken_spec.rb → lib/shoryuken_spec.rb} +0 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Integration test helper for process-isolated testing
|
|
4
|
+
|
|
5
|
+
# Enable Ruby warnings to catch deprecations and potential issues
|
|
6
|
+
Warning[:performance] = true if RUBY_VERSION >= '3.3'
|
|
7
|
+
Warning[:deprecated] = true
|
|
8
|
+
$VERBOSE = true
|
|
9
|
+
|
|
10
|
+
require 'warning'
|
|
11
|
+
|
|
12
|
+
# Process warnings and raise on unexpected ones from our code
|
|
13
|
+
Warning.process do |warning|
|
|
14
|
+
# Only check warnings from our code (not dependencies)
|
|
15
|
+
next unless warning.include?(Dir.pwd)
|
|
16
|
+
|
|
17
|
+
# Filter out warnings we don't care about in specs
|
|
18
|
+
next if warning.include?('_spec')
|
|
19
|
+
|
|
20
|
+
# We redefine methods to simulate various scenarios in tests
|
|
21
|
+
next if warning.include?('previous definition of')
|
|
22
|
+
next if warning.include?('method redefined')
|
|
23
|
+
|
|
24
|
+
# Ignore vendor directory
|
|
25
|
+
next if warning.include?('vendor/')
|
|
26
|
+
|
|
27
|
+
# Ignore bundle path
|
|
28
|
+
next if warning.include?('bundle/')
|
|
29
|
+
|
|
30
|
+
raise "Warning in your code: #{warning}"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
require 'timeout'
|
|
34
|
+
require 'json'
|
|
35
|
+
require 'securerandom'
|
|
36
|
+
require 'aws-sdk-sqs'
|
|
37
|
+
require 'shoryuken'
|
|
38
|
+
require 'singleton'
|
|
39
|
+
|
|
40
|
+
# Thread-safe data collector for integration tests
|
|
41
|
+
# Inspired by Karafka's DataCollector pattern
|
|
42
|
+
# Usage: DT[:key] << value, DT[:key].size, DT.clear
|
|
43
|
+
class DataCollector
|
|
44
|
+
include Singleton
|
|
45
|
+
|
|
46
|
+
MUTEX = Mutex.new
|
|
47
|
+
private_constant :MUTEX
|
|
48
|
+
|
|
49
|
+
attr_reader :queues, :data
|
|
50
|
+
|
|
51
|
+
class << self
|
|
52
|
+
def queue
|
|
53
|
+
instance.queue
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def queues
|
|
57
|
+
instance.queues
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def data
|
|
61
|
+
instance.data
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def [](key)
|
|
65
|
+
MUTEX.synchronize { data[key] }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def []=(key, value)
|
|
69
|
+
MUTEX.synchronize { data[key] = value }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def uuids(amount)
|
|
73
|
+
Array.new(amount) { uuid }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def uuid
|
|
77
|
+
"it-#{SecureRandom.uuid[0, 8]}"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def clear
|
|
81
|
+
MUTEX.synchronize { instance.clear }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def key?(key)
|
|
85
|
+
instance.data.key?(key)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def initialize
|
|
90
|
+
@mutex = Mutex.new
|
|
91
|
+
@queues = Array.new(100) { "it-#{SecureRandom.hex(6)}" }
|
|
92
|
+
@data = Hash.new do |hash, key|
|
|
93
|
+
@mutex.synchronize do
|
|
94
|
+
break hash[key] if hash.key?(key)
|
|
95
|
+
|
|
96
|
+
hash[key] = []
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def queue
|
|
102
|
+
queues.first
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def clear
|
|
106
|
+
@mutex.synchronize do
|
|
107
|
+
@queues.clear
|
|
108
|
+
@queues.concat(Array.new(100) { "it-#{SecureRandom.hex(6)}" })
|
|
109
|
+
@data.clear
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Short alias for DataCollector
|
|
115
|
+
DT = DataCollector
|
|
116
|
+
|
|
117
|
+
module IntegrationsHelper
|
|
118
|
+
class TestFailure < StandardError; end
|
|
119
|
+
|
|
120
|
+
# Assertions
|
|
121
|
+
def assert(condition, message = "Assertion failed")
|
|
122
|
+
raise TestFailure, message unless condition
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def assert_equal(expected, actual, message = nil)
|
|
126
|
+
message ||= "Expected #{expected.inspect}, got #{actual.inspect}"
|
|
127
|
+
assert(expected == actual, message)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def assert_includes(collection, item, message = nil)
|
|
131
|
+
message ||= "Expected #{collection.inspect} to include #{item.inspect}"
|
|
132
|
+
assert(collection.include?(item), message)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def refute(condition, message = "Refutation failed")
|
|
136
|
+
assert(!condition, message)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Configure ActiveJob with Shoryuken adapter
|
|
140
|
+
def setup_active_job
|
|
141
|
+
require 'active_job'
|
|
142
|
+
require 'active_job/queue_adapters/shoryuken_adapter'
|
|
143
|
+
require 'active_job/extensions'
|
|
144
|
+
|
|
145
|
+
ActiveJob::Base.queue_adapter = :shoryuken
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Configure Shoryuken to use LocalStack for real SQS integration tests
|
|
149
|
+
def setup_localstack
|
|
150
|
+
Aws.config[:stub_responses] = false
|
|
151
|
+
|
|
152
|
+
sqs_client = Aws::SQS::Client.new(
|
|
153
|
+
region: 'us-east-1',
|
|
154
|
+
endpoint: 'http://localhost:4566',
|
|
155
|
+
access_key_id: 'fake',
|
|
156
|
+
secret_access_key: 'fake'
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
Shoryuken.options[:concurrency] = 25
|
|
160
|
+
Shoryuken.options[:delay] = 0
|
|
161
|
+
Shoryuken.options[:timeout] = 8
|
|
162
|
+
|
|
163
|
+
executor = Concurrent::CachedThreadPool.new(auto_terminate: true)
|
|
164
|
+
Shoryuken.define_singleton_method(:launcher_executor) { executor }
|
|
165
|
+
|
|
166
|
+
Shoryuken.configure_client { |config| config.sqs_client = sqs_client }
|
|
167
|
+
Shoryuken.configure_server { |config| config.sqs_client = sqs_client }
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Queue helpers
|
|
171
|
+
def create_test_queue(queue_name, attributes: {})
|
|
172
|
+
Shoryuken::Client.sqs.create_queue(queue_name: queue_name, attributes: attributes)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def delete_test_queue(queue_name)
|
|
176
|
+
queue_url = Shoryuken::Client.sqs.get_queue_url(queue_name: queue_name).queue_url
|
|
177
|
+
Shoryuken::Client.sqs.delete_queue(queue_url: queue_url)
|
|
178
|
+
rescue Aws::SQS::Errors::NonExistentQueue
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def create_fifo_queue(queue_name)
|
|
182
|
+
create_test_queue(queue_name, attributes: {
|
|
183
|
+
'FifoQueue' => 'true',
|
|
184
|
+
'ContentBasedDeduplication' => 'true'
|
|
185
|
+
})
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Poll until condition met
|
|
189
|
+
def poll_queues_until(timeout: 15)
|
|
190
|
+
launcher = Shoryuken::Launcher.new
|
|
191
|
+
launcher.start
|
|
192
|
+
Timeout.timeout(timeout) { sleep 0.5 until yield }
|
|
193
|
+
ensure
|
|
194
|
+
launcher.stop
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Simple mock object
|
|
198
|
+
def double(_name = nil)
|
|
199
|
+
Object.new
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Job capture for ActiveJob tests
|
|
203
|
+
class JobCapture
|
|
204
|
+
attr_reader :jobs
|
|
205
|
+
|
|
206
|
+
def initialize
|
|
207
|
+
@jobs = []
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def start_capturing
|
|
211
|
+
@jobs.clear
|
|
212
|
+
capture = self
|
|
213
|
+
|
|
214
|
+
queue_mock = Object.new
|
|
215
|
+
queue_mock.define_singleton_method(:fifo?) { false }
|
|
216
|
+
queue_mock.define_singleton_method(:send_message) do |params|
|
|
217
|
+
capture.jobs << {
|
|
218
|
+
queue: params[:queue_name] || :default,
|
|
219
|
+
message_body: params[:message_body],
|
|
220
|
+
delay_seconds: params[:delay_seconds],
|
|
221
|
+
message_attributes: params[:message_attributes]
|
|
222
|
+
}
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
Shoryuken::Client.define_singleton_method(:queues) do |queue_name = nil|
|
|
226
|
+
queue_mock.define_singleton_method(:name) { queue_name } if queue_name
|
|
227
|
+
queue_name ? queue_mock : { default: queue_mock }
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
Shoryuken.define_singleton_method(:register_worker) { |*| nil }
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def last_job
|
|
234
|
+
@jobs.last
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def job_count
|
|
238
|
+
@jobs.size
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
include IntegrationsHelper
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
# Skip this spec if ActiveSupport is not available, as the extensions require it
|
|
6
|
+
if defined?(ActiveSupport)
|
|
7
|
+
require 'active_job/extensions'
|
|
8
|
+
|
|
9
|
+
RSpec.describe Shoryuken::ActiveJob do
|
|
10
|
+
describe Shoryuken::ActiveJob::SQSSendMessageParametersAccessor do
|
|
11
|
+
let(:job_class) do
|
|
12
|
+
Class.new do
|
|
13
|
+
include Shoryuken::ActiveJob::SQSSendMessageParametersAccessor
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
let(:job_instance) { job_class.new }
|
|
18
|
+
|
|
19
|
+
describe 'included behavior' do
|
|
20
|
+
it 'adds sqs_send_message_parameters accessor' do
|
|
21
|
+
expect(job_instance).to respond_to(:sqs_send_message_parameters)
|
|
22
|
+
expect(job_instance).to respond_to(:sqs_send_message_parameters=)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'allows setting and getting sqs_send_message_parameters' do
|
|
26
|
+
params = { message_group_id: 'group1', message_deduplication_id: 'dedup1' }
|
|
27
|
+
job_instance.sqs_send_message_parameters = params
|
|
28
|
+
expect(job_instance.sqs_send_message_parameters).to eq(params)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe Shoryuken::ActiveJob::SQSSendMessageParametersSupport do
|
|
34
|
+
let(:base_class) do
|
|
35
|
+
Class.new do
|
|
36
|
+
attr_accessor :sqs_send_message_parameters
|
|
37
|
+
|
|
38
|
+
def initialize(*arguments)
|
|
39
|
+
# Mock ActiveJob::Base initialization
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def enqueue(options = {})
|
|
43
|
+
# Mock ActiveJob::Base enqueue method that returns remaining options
|
|
44
|
+
options
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
let(:job_class) do
|
|
50
|
+
Class.new(base_class) do
|
|
51
|
+
prepend Shoryuken::ActiveJob::SQSSendMessageParametersSupport
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
describe '#initialize' do
|
|
56
|
+
it 'initializes sqs_send_message_parameters to empty hash' do
|
|
57
|
+
job = job_class.new('arg1', 'arg2')
|
|
58
|
+
expect(job.sqs_send_message_parameters).to eq({})
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'calls super with the provided arguments' do
|
|
62
|
+
expect_any_instance_of(base_class).to receive(:initialize).with('arg1', 'arg2')
|
|
63
|
+
job_class.new('arg1', 'arg2')
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it 'handles ruby2_keywords compatibility' do
|
|
67
|
+
# Test that ruby2_keywords is called if available
|
|
68
|
+
if respond_to?(:ruby2_keywords, true)
|
|
69
|
+
expect(job_class.method(:new)).to respond_to(:ruby2_keywords) if RUBY_VERSION >= '2.7'
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
describe '#enqueue' do
|
|
75
|
+
let(:job_instance) { job_class.new }
|
|
76
|
+
|
|
77
|
+
it 'extracts SQS-specific options and merges them into sqs_send_message_parameters' do
|
|
78
|
+
options = {
|
|
79
|
+
wait: 5 * 60, # 5 minutes in seconds
|
|
80
|
+
message_attributes: { 'type' => 'important' },
|
|
81
|
+
message_system_attributes: { 'source' => 'api' },
|
|
82
|
+
message_deduplication_id: 'dedup123',
|
|
83
|
+
message_group_id: 'group456',
|
|
84
|
+
other_option: 'value'
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
remaining_options = job_instance.enqueue(options)
|
|
88
|
+
|
|
89
|
+
expect(job_instance.sqs_send_message_parameters).to eq({
|
|
90
|
+
message_attributes: { 'type' => 'important' },
|
|
91
|
+
message_system_attributes: { 'source' => 'api' },
|
|
92
|
+
message_deduplication_id: 'dedup123',
|
|
93
|
+
message_group_id: 'group456'
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
expect(remaining_options).to eq({
|
|
97
|
+
wait: 300,
|
|
98
|
+
other_option: 'value'
|
|
99
|
+
})
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it 'handles empty options gracefully' do
|
|
103
|
+
remaining_options = job_instance.enqueue({})
|
|
104
|
+
expect(job_instance.sqs_send_message_parameters).to eq({})
|
|
105
|
+
expect(remaining_options).to eq({})
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it 'merges new SQS options with existing ones' do
|
|
109
|
+
job_instance.sqs_send_message_parameters = { message_group_id: 'existing_group' }
|
|
110
|
+
|
|
111
|
+
options = { message_deduplication_id: 'new_dedup' }
|
|
112
|
+
job_instance.enqueue(options)
|
|
113
|
+
|
|
114
|
+
expect(job_instance.sqs_send_message_parameters).to eq({
|
|
115
|
+
message_group_id: 'existing_group',
|
|
116
|
+
message_deduplication_id: 'new_dedup'
|
|
117
|
+
})
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it 'overwrites existing SQS options when the same key is provided' do
|
|
121
|
+
job_instance.sqs_send_message_parameters = { message_group_id: 'old_group' }
|
|
122
|
+
|
|
123
|
+
options = { message_group_id: 'new_group' }
|
|
124
|
+
job_instance.enqueue(options)
|
|
125
|
+
|
|
126
|
+
expect(job_instance.sqs_send_message_parameters).to eq({
|
|
127
|
+
message_group_id: 'new_group'
|
|
128
|
+
})
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
describe 'module constants' do
|
|
134
|
+
it 'defines SQSSendMessageParametersAccessor' do
|
|
135
|
+
expect(Shoryuken::ActiveJob::SQSSendMessageParametersAccessor).to be_a(Module)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
it 'defines SQSSendMessageParametersSupport' do
|
|
139
|
+
expect(Shoryuken::ActiveJob::SQSSendMessageParametersSupport).to be_a(Module)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
else
|
|
144
|
+
RSpec.describe 'Shoryuken::ActiveJob (skipped - ActiveSupport not available)' do
|
|
145
|
+
it 'skips tests when ActiveSupport is not available' do
|
|
146
|
+
skip('ActiveSupport not available in test environment')
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'shared_examples_for_active_job'
|
|
4
|
+
require 'active_job/queue_adapters/shoryuken_adapter'
|
|
5
|
+
require 'active_support/core_ext/numeric/time'
|
|
6
|
+
|
|
7
|
+
RSpec.describe ActiveJob::QueueAdapters::ShoryukenAdapter do
|
|
8
|
+
include_examples 'active_job_adapters'
|
|
9
|
+
|
|
10
|
+
describe '#enqueue_after_transaction_commit?' do
|
|
11
|
+
it 'returns true to support Rails 7.2+ transaction commit behavior' do
|
|
12
|
+
adapter = described_class.new
|
|
13
|
+
expect(adapter.enqueue_after_transaction_commit?).to eq(true)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe '.instance' do
|
|
18
|
+
it 'returns the same instance (singleton pattern)' do
|
|
19
|
+
instance1 = described_class.instance
|
|
20
|
+
instance2 = described_class.instance
|
|
21
|
+
expect(instance1).to be(instance2)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'returns a ShoryukenAdapter instance' do
|
|
25
|
+
expect(described_class.instance).to be_a(described_class)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'shared_examples_for_active_job'
|
|
4
|
-
require '
|
|
5
|
-
require '
|
|
4
|
+
require 'active_job/queue_adapters/shoryuken_adapter'
|
|
5
|
+
require 'active_job/queue_adapters/shoryuken_concurrent_send_adapter'
|
|
6
6
|
|
|
7
7
|
RSpec.describe ActiveJob::QueueAdapters::ShoryukenConcurrentSendAdapter do
|
|
8
8
|
include_examples 'active_job_adapters'
|
|
@@ -37,4 +37,4 @@ RSpec.describe ActiveJob::QueueAdapters::ShoryukenConcurrentSendAdapter do
|
|
|
37
37
|
subject.enqueue(job, options)
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
|
-
end
|
|
40
|
+
end
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'active_job'
|
|
4
|
-
require '
|
|
5
|
-
require '
|
|
4
|
+
require 'active_job/extensions'
|
|
5
|
+
require 'active_job/queue_adapters/shoryuken_adapter'
|
|
6
6
|
|
|
7
|
-
RSpec.describe ActiveJob::
|
|
7
|
+
RSpec.describe Shoryuken::ActiveJob::JobWrapper do
|
|
8
8
|
subject { described_class.new }
|
|
9
9
|
|
|
10
10
|
describe '#perform' do
|
|
@@ -18,4 +18,4 @@ RSpec.describe ActiveJob::QueueAdapters::ShoryukenAdapter::JobWrapper do
|
|
|
18
18
|
subject.perform sqs_msg, job_hash
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
|
-
end
|
|
21
|
+
end
|
|
@@ -24,7 +24,7 @@ RSpec.describe Shoryuken::EnvironmentLoader do
|
|
|
24
24
|
context "when given queues don't exist" do
|
|
25
25
|
specify do
|
|
26
26
|
expect { subject.load }.to raise_error(
|
|
27
|
-
|
|
27
|
+
Shoryuken::Errors::QueueNotFoundError,
|
|
28
28
|
<<-MSG.gsub(/^\s+/, '')
|
|
29
29
|
The specified queue(s) stubbed_queue do not exist.
|
|
30
30
|
Try 'shoryuken sqs create QUEUE-NAME' for creating a queue with default settings.
|
|
@@ -7,21 +7,21 @@ RSpec.describe Shoryuken::Helpers::HashUtils do
|
|
|
7
7
|
it 'converts string keys to symbols' do
|
|
8
8
|
input = { 'key1' => 'value1', 'key2' => 'value2' }
|
|
9
9
|
expected = { key1: 'value1', key2: 'value2' }
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
expect(described_class.deep_symbolize_keys(input)).to eq(expected)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
it 'leaves symbol keys unchanged' do
|
|
15
15
|
input = { key1: 'value1', key2: 'value2' }
|
|
16
16
|
expected = { key1: 'value1', key2: 'value2' }
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
expect(described_class.deep_symbolize_keys(input)).to eq(expected)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
it 'handles mixed key types' do
|
|
22
22
|
input = { 'string_key' => 'value1', :symbol_key => 'value2' }
|
|
23
23
|
expected = { string_key: 'value1', symbol_key: 'value2' }
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
expect(described_class.deep_symbolize_keys(input)).to eq(expected)
|
|
26
26
|
end
|
|
27
27
|
|
|
@@ -35,7 +35,7 @@ RSpec.describe Shoryuken::Helpers::HashUtils do
|
|
|
35
35
|
},
|
|
36
36
|
'top_level' => 'value'
|
|
37
37
|
}
|
|
38
|
-
|
|
38
|
+
|
|
39
39
|
expected = {
|
|
40
40
|
level1: {
|
|
41
41
|
level2: {
|
|
@@ -45,7 +45,7 @@ RSpec.describe Shoryuken::Helpers::HashUtils do
|
|
|
45
45
|
},
|
|
46
46
|
top_level: 'value'
|
|
47
47
|
}
|
|
48
|
-
|
|
48
|
+
|
|
49
49
|
expect(described_class.deep_symbolize_keys(input)).to eq(expected)
|
|
50
50
|
end
|
|
51
51
|
|
|
@@ -58,7 +58,7 @@ RSpec.describe Shoryuken::Helpers::HashUtils do
|
|
|
58
58
|
'metadata' => nil
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
-
|
|
61
|
+
|
|
62
62
|
expected = {
|
|
63
63
|
config: {
|
|
64
64
|
timeout: 30,
|
|
@@ -67,7 +67,7 @@ RSpec.describe Shoryuken::Helpers::HashUtils do
|
|
|
67
67
|
metadata: nil
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
-
|
|
70
|
+
|
|
71
71
|
expect(described_class.deep_symbolize_keys(input)).to eq(expected)
|
|
72
72
|
end
|
|
73
73
|
|
|
@@ -78,7 +78,7 @@ RSpec.describe Shoryuken::Helpers::HashUtils do
|
|
|
78
78
|
it 'handles hash with empty nested hash' do
|
|
79
79
|
input = { 'key' => {} }
|
|
80
80
|
expected = { key: {} }
|
|
81
|
-
|
|
81
|
+
|
|
82
82
|
expect(described_class.deep_symbolize_keys(input)).to eq(expected)
|
|
83
83
|
end
|
|
84
84
|
|
|
@@ -94,10 +94,10 @@ RSpec.describe Shoryuken::Helpers::HashUtils do
|
|
|
94
94
|
# Create a key that will raise an exception when converted to symbol
|
|
95
95
|
problematic_key = Object.new
|
|
96
96
|
allow(problematic_key).to receive(:to_sym).and_raise(StandardError)
|
|
97
|
-
|
|
97
|
+
|
|
98
98
|
input = { problematic_key => 'value', 'normal_key' => 'normal_value' }
|
|
99
99
|
result = described_class.deep_symbolize_keys(input)
|
|
100
|
-
|
|
100
|
+
|
|
101
101
|
# The problematic key should remain as-is, normal key should be symbolized
|
|
102
102
|
expect(result[problematic_key]).to eq('value')
|
|
103
103
|
expect(result[:normal_key]).to eq('normal_value')
|
|
@@ -106,9 +106,9 @@ RSpec.describe Shoryuken::Helpers::HashUtils do
|
|
|
106
106
|
it 'does not modify the original hash' do
|
|
107
107
|
input = { 'key' => { 'nested' => 'value' } }
|
|
108
108
|
original_input = input.dup
|
|
109
|
-
|
|
109
|
+
|
|
110
110
|
described_class.deep_symbolize_keys(input)
|
|
111
|
-
|
|
111
|
+
|
|
112
112
|
expect(input).to eq(original_input)
|
|
113
113
|
end
|
|
114
114
|
|
|
@@ -125,7 +125,7 @@ RSpec.describe Shoryuken::Helpers::HashUtils do
|
|
|
125
125
|
'mailers' => { 'concurrency' => 2 }
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
|
-
|
|
128
|
+
|
|
129
129
|
expected = {
|
|
130
130
|
database: {
|
|
131
131
|
host: 'localhost',
|
|
@@ -137,7 +137,7 @@ RSpec.describe Shoryuken::Helpers::HashUtils do
|
|
|
137
137
|
mailers: { concurrency: 2 }
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
|
-
|
|
140
|
+
|
|
141
141
|
expect(described_class.deep_symbolize_keys(input)).to eq(expected)
|
|
142
142
|
end
|
|
143
143
|
end
|
|
@@ -74,7 +74,7 @@ RSpec.describe Shoryuken::Helpers::StringUtils do
|
|
|
74
74
|
unless Object.const_defined?('MyApp')
|
|
75
75
|
Object.const_set('MyApp', Module.new)
|
|
76
76
|
end
|
|
77
|
-
|
|
77
|
+
|
|
78
78
|
unless MyApp.const_defined?('EmailWorker')
|
|
79
79
|
MyApp.const_set('EmailWorker', Class.new)
|
|
80
80
|
end
|
|
@@ -107,9 +107,9 @@ RSpec.describe Shoryuken::Helpers::StringUtils do
|
|
|
107
107
|
it 'handles single character constant names' do
|
|
108
108
|
# Define a single character constant for testing
|
|
109
109
|
Object.const_set('A', Class.new) unless Object.const_defined?('A')
|
|
110
|
-
|
|
110
|
+
|
|
111
111
|
expect(described_class.constantize('A')).to eq(A)
|
|
112
|
-
|
|
112
|
+
|
|
113
113
|
# Clean up
|
|
114
114
|
Object.send(:remove_const, 'A') if Object.const_defined?('A')
|
|
115
115
|
end
|