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.
Files changed (178) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/push.yml +2 -2
  3. data/.github/workflows/specs.yml +38 -43
  4. data/.github/workflows/verify-action-pins.yml +1 -1
  5. data/.gitignore +3 -0
  6. data/.rspec +1 -0
  7. data/.ruby-version +1 -1
  8. data/.yard-lint.yml +279 -0
  9. data/CHANGELOG.md +69 -1
  10. data/Gemfile +1 -1
  11. data/README.md +2 -7
  12. data/Rakefile +4 -10
  13. data/bin/clean_localstack +52 -0
  14. data/bin/cli/base.rb +21 -0
  15. data/bin/cli/sqs.rb +61 -2
  16. data/bin/integrations +275 -0
  17. data/bin/scenario +154 -0
  18. data/bin/shoryuken +1 -1
  19. data/lib/{shoryuken/extensions/active_job_extensions.rb → active_job/extensions.rb} +15 -4
  20. data/lib/active_job/queue_adapters/shoryuken_adapter.rb +208 -0
  21. data/lib/active_job/queue_adapters/shoryuken_concurrent_send_adapter.rb +78 -0
  22. data/lib/shoryuken/active_job/current_attributes.rb +139 -0
  23. data/lib/shoryuken/active_job/job_wrapper.rb +28 -0
  24. data/lib/shoryuken/body_parser.rb +8 -0
  25. data/lib/shoryuken/client.rb +14 -0
  26. data/lib/shoryuken/default_exception_handler.rb +9 -0
  27. data/lib/shoryuken/default_worker_registry.rb +29 -1
  28. data/lib/shoryuken/environment_loader.rb +78 -8
  29. data/lib/shoryuken/errors.rb +33 -0
  30. data/lib/shoryuken/fetcher.rb +37 -1
  31. data/lib/shoryuken/helpers/atomic_boolean.rb +19 -5
  32. data/lib/shoryuken/helpers/timer_task.rb +80 -0
  33. data/lib/shoryuken/launcher.rb +53 -0
  34. data/lib/shoryuken/logging/base.rb +26 -0
  35. data/lib/shoryuken/logging/pretty.rb +25 -0
  36. data/lib/shoryuken/logging/without_timestamp.rb +25 -0
  37. data/lib/shoryuken/logging.rb +39 -25
  38. data/lib/shoryuken/manager.rb +70 -1
  39. data/lib/shoryuken/message.rb +114 -1
  40. data/lib/shoryuken/middleware/chain.rb +139 -43
  41. data/lib/shoryuken/middleware/entry.rb +30 -0
  42. data/lib/shoryuken/middleware/server/active_record.rb +8 -0
  43. data/lib/shoryuken/middleware/server/auto_delete.rb +10 -0
  44. data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +27 -1
  45. data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +29 -0
  46. data/lib/shoryuken/middleware/server/timing.rb +11 -0
  47. data/lib/shoryuken/options.rb +129 -6
  48. data/lib/shoryuken/polling/base_strategy.rb +1 -0
  49. data/lib/shoryuken/polling/strict_priority.rb +39 -0
  50. data/lib/shoryuken/polling/weighted_round_robin.rb +42 -0
  51. data/lib/shoryuken/processor.rb +32 -1
  52. data/lib/shoryuken/queue.rb +93 -4
  53. data/lib/shoryuken/runner.rb +45 -4
  54. data/lib/shoryuken/util.rb +26 -1
  55. data/lib/shoryuken/version.rb +2 -1
  56. data/lib/shoryuken/worker/default_executor.rb +21 -1
  57. data/lib/shoryuken/worker/inline_executor.rb +24 -0
  58. data/lib/shoryuken/worker.rb +193 -0
  59. data/lib/shoryuken/worker_registry.rb +33 -0
  60. data/lib/shoryuken.rb +18 -6
  61. data/renovate.json +29 -2
  62. data/shoryuken.gemspec +2 -1
  63. data/spec/integration/.rspec +1 -0
  64. data/spec/integration/active_job/adapter_configuration/configuration_spec.rb +26 -0
  65. data/spec/integration/active_job/bulk_enqueue/bulk_enqueue_spec.rb +53 -0
  66. data/spec/integration/active_job/current_attributes/bulk_enqueue_spec.rb +50 -0
  67. data/spec/integration/active_job/current_attributes/complex_types_spec.rb +55 -0
  68. data/spec/integration/active_job/current_attributes/empty_context_spec.rb +41 -0
  69. data/spec/integration/active_job/current_attributes/full_context_spec.rb +63 -0
  70. data/spec/integration/active_job/current_attributes/partial_context_spec.rb +57 -0
  71. data/spec/integration/active_job/custom_attributes/number_attributes_spec.rb +37 -0
  72. data/spec/integration/active_job/custom_attributes/string_attributes_spec.rb +39 -0
  73. data/spec/integration/active_job/error_handling/job_wrapper_spec.rb +53 -0
  74. data/spec/integration/active_job/fifo_and_attributes/deduplication_spec.rb +86 -0
  75. data/spec/integration/active_job/retry/discard_on_spec.rb +43 -0
  76. data/spec/integration/active_job/retry/retry_on_spec.rb +36 -0
  77. data/spec/integration/active_job/roundtrip/roundtrip_spec.rb +52 -0
  78. data/spec/integration/active_job/scheduled/scheduled_spec.rb +76 -0
  79. data/spec/integration/active_record_middleware/active_record_middleware_spec.rb +84 -0
  80. data/spec/integration/auto_delete/auto_delete_spec.rb +53 -0
  81. data/spec/integration/auto_extend_visibility/auto_extend_visibility_spec.rb +57 -0
  82. data/spec/integration/aws_config/aws_config_spec.rb +59 -0
  83. data/spec/integration/batch_processing/batch_processing_spec.rb +37 -0
  84. data/spec/integration/body_parser/json_parser_spec.rb +45 -0
  85. data/spec/integration/body_parser/proc_parser_spec.rb +54 -0
  86. data/spec/integration/body_parser/text_parser_spec.rb +43 -0
  87. data/spec/integration/concurrent_processing/concurrent_processing_spec.rb +45 -0
  88. data/spec/integration/dead_letter_queue/dead_letter_queue_spec.rb +91 -0
  89. data/spec/integration/exception_handlers/exception_handlers_spec.rb +69 -0
  90. data/spec/integration/exponential_backoff/exponential_backoff_spec.rb +67 -0
  91. data/spec/integration/fifo_ordering/fifo_ordering_spec.rb +44 -0
  92. data/spec/integration/large_payloads/large_payloads_spec.rb +30 -0
  93. data/spec/integration/launcher/launcher_spec.rb +40 -0
  94. data/spec/integration/message_attributes/message_attributes_spec.rb +54 -0
  95. data/spec/integration/message_operations/message_operations_spec.rb +59 -0
  96. data/spec/integration/middleware_chain/empty_chain_spec.rb +11 -0
  97. data/spec/integration/middleware_chain/execution_order_spec.rb +33 -0
  98. data/spec/integration/middleware_chain/removal_spec.rb +31 -0
  99. data/spec/integration/middleware_chain/short_circuit_spec.rb +40 -0
  100. data/spec/integration/polling_strategies/polling_strategies_spec.rb +46 -0
  101. data/spec/integration/queue_operations/queue_operations_spec.rb +84 -0
  102. data/spec/integration/rails/rails_72/Gemfile +6 -0
  103. data/spec/integration/rails/rails_72/activejob_adapter_spec.rb +98 -0
  104. data/spec/integration/rails/rails_80/Gemfile +6 -0
  105. data/spec/integration/rails/rails_80/activejob_adapter_spec.rb +98 -0
  106. data/spec/integration/rails/rails_80/continuation_spec.rb +79 -0
  107. data/spec/integration/rails/rails_81/Gemfile +6 -0
  108. data/spec/integration/rails/rails_81/activejob_adapter_spec.rb +98 -0
  109. data/spec/integration/rails/rails_81/continuation_spec.rb +79 -0
  110. data/spec/integration/retry_behavior/retry_behavior_spec.rb +45 -0
  111. data/spec/integration/spec_helper.rb +7 -0
  112. data/spec/integration/strict_priority_polling/strict_priority_polling_spec.rb +58 -0
  113. data/spec/integration/visibility_timeout/visibility_timeout_spec.rb +37 -0
  114. data/spec/integration/worker_enqueueing/worker_enqueueing_spec.rb +60 -0
  115. data/spec/integration/worker_groups/worker_groups_spec.rb +79 -0
  116. data/spec/integration/worker_lifecycle/worker_lifecycle_spec.rb +33 -0
  117. data/spec/integrations_helper.rb +243 -0
  118. data/spec/lib/active_job/extensions_spec.rb +149 -0
  119. data/spec/lib/active_job/queue_adapters/shoryuken_adapter_spec.rb +29 -0
  120. data/spec/{shoryuken/extensions/active_job_concurrent_send_adapter_spec.rb → lib/active_job/queue_adapters/shoryuken_concurrent_send_adapter_spec.rb} +3 -3
  121. data/spec/{shoryuken/extensions/active_job_wrapper_spec.rb → lib/shoryuken/active_job/job_wrapper_spec.rb} +4 -4
  122. data/spec/{shoryuken → lib/shoryuken}/environment_loader_spec.rb +1 -1
  123. data/spec/{shoryuken → lib/shoryuken}/helpers/hash_utils_spec.rb +14 -14
  124. data/spec/{shoryuken → lib/shoryuken}/helpers/string_utils_spec.rb +3 -3
  125. data/spec/lib/shoryuken/helpers/timer_task_spec.rb +298 -0
  126. data/spec/{shoryuken → lib/shoryuken}/helpers_integration_spec.rb +9 -9
  127. data/spec/{shoryuken → lib/shoryuken}/launcher_spec.rb +22 -0
  128. data/spec/lib/shoryuken/logging_spec.rb +242 -0
  129. data/spec/lib/shoryuken/message_spec.rb +109 -0
  130. data/spec/lib/shoryuken/middleware/entry_spec.rb +68 -0
  131. data/spec/lib/shoryuken/middleware/server/active_record_spec.rb +133 -0
  132. data/spec/{shoryuken → lib/shoryuken}/middleware/server/auto_extend_visibility_spec.rb +50 -0
  133. data/spec/{shoryuken → lib/shoryuken}/options_spec.rb +2 -2
  134. data/spec/{shoryuken → lib/shoryuken}/util_spec.rb +1 -1
  135. data/spec/lib/shoryuken/version_spec.rb +17 -0
  136. data/spec/lib/shoryuken/worker_registry_spec.rb +63 -0
  137. data/spec/shared_examples_for_active_job.rb +29 -9
  138. data/spec/spec_helper.rb +34 -3
  139. metadata +230 -91
  140. data/.devcontainer/Dockerfile +0 -17
  141. data/.devcontainer/base.Dockerfile +0 -43
  142. data/.devcontainer/devcontainer.json +0 -35
  143. data/Appraisals +0 -23
  144. data/gemfiles/.gitignore +0 -1
  145. data/gemfiles/rails_7_0.gemfile +0 -19
  146. data/gemfiles/rails_7_1.gemfile +0 -19
  147. data/gemfiles/rails_7_2.gemfile +0 -19
  148. data/gemfiles/rails_8_0.gemfile +0 -19
  149. data/lib/shoryuken/extensions/active_job_adapter.rb +0 -110
  150. data/lib/shoryuken/extensions/active_job_concurrent_send_adapter.rb +0 -50
  151. data/spec/integration/launcher_spec.rb +0 -127
  152. data/spec/shoryuken/extensions/active_job_adapter_spec.rb +0 -8
  153. data/spec/shoryuken/extensions/active_job_base_spec.rb +0 -85
  154. /data/spec/{shoryuken → lib/shoryuken}/body_parser_spec.rb +0 -0
  155. /data/spec/{shoryuken → lib/shoryuken}/client_spec.rb +0 -0
  156. /data/spec/{shoryuken → lib/shoryuken}/default_exception_handler_spec.rb +0 -0
  157. /data/spec/{shoryuken → lib/shoryuken}/default_worker_registry_spec.rb +0 -0
  158. /data/spec/{shoryuken → lib/shoryuken}/fetcher_spec.rb +0 -0
  159. /data/spec/{shoryuken → lib/shoryuken}/helpers/atomic_boolean_spec.rb +0 -0
  160. /data/spec/{shoryuken → lib/shoryuken}/helpers/atomic_counter_spec.rb +0 -0
  161. /data/spec/{shoryuken → lib/shoryuken}/helpers/atomic_hash_spec.rb +0 -0
  162. /data/spec/{shoryuken → lib/shoryuken}/inline_message_spec.rb +0 -0
  163. /data/spec/{shoryuken → lib/shoryuken}/manager_spec.rb +0 -0
  164. /data/spec/{shoryuken → lib/shoryuken}/middleware/chain_spec.rb +0 -0
  165. /data/spec/{shoryuken → lib/shoryuken}/middleware/server/auto_delete_spec.rb +0 -0
  166. /data/spec/{shoryuken → lib/shoryuken}/middleware/server/exponential_backoff_retry_spec.rb +0 -0
  167. /data/spec/{shoryuken → lib/shoryuken}/middleware/server/timing_spec.rb +0 -0
  168. /data/spec/{shoryuken → lib/shoryuken}/polling/base_strategy_spec.rb +0 -0
  169. /data/spec/{shoryuken → lib/shoryuken}/polling/queue_configuration_spec.rb +0 -0
  170. /data/spec/{shoryuken → lib/shoryuken}/polling/strict_priority_spec.rb +0 -0
  171. /data/spec/{shoryuken → lib/shoryuken}/polling/weighted_round_robin_spec.rb +0 -0
  172. /data/spec/{shoryuken → lib/shoryuken}/processor_spec.rb +0 -0
  173. /data/spec/{shoryuken → lib/shoryuken}/queue_spec.rb +0 -0
  174. /data/spec/{shoryuken → lib/shoryuken}/runner_spec.rb +0 -0
  175. /data/spec/{shoryuken → lib/shoryuken}/worker/default_executor_spec.rb +0 -0
  176. /data/spec/{shoryuken → lib/shoryuken}/worker/inline_executor_spec.rb +0 -0
  177. /data/spec/{shoryuken → lib/shoryuken}/worker_spec.rb +0 -0
  178. /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 'shoryuken/extensions/active_job_adapter'
5
- require 'shoryuken/extensions/active_job_concurrent_send_adapter'
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 'shoryuken/extensions/active_job_extensions'
5
- require 'shoryuken/extensions/active_job_adapter'
4
+ require 'active_job/extensions'
5
+ require 'active_job/queue_adapters/shoryuken_adapter'
6
6
 
7
- RSpec.describe ActiveJob::QueueAdapters::ShoryukenAdapter::JobWrapper do
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
- ArgumentError,
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