shoryuken 6.2.1 → 7.0.2

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 (205) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/push.yml +36 -0
  3. data/.github/workflows/specs.yml +49 -44
  4. data/.github/workflows/verify-action-pins.yml +16 -0
  5. data/.gitignore +4 -1
  6. data/.rspec +3 -1
  7. data/.rubocop.yml +6 -1
  8. data/.ruby-version +1 -0
  9. data/.yard-lint.yml +279 -0
  10. data/CHANGELOG.md +308 -139
  11. data/Gemfile +1 -8
  12. data/Gemfile.lint +9 -0
  13. data/Gemfile.lint.lock +69 -0
  14. data/README.md +16 -33
  15. data/Rakefile +6 -10
  16. data/bin/clean_sqs +52 -0
  17. data/bin/cli/base.rb +22 -2
  18. data/bin/cli/sqs.rb +74 -7
  19. data/bin/integrations +275 -0
  20. data/bin/scenario +154 -0
  21. data/bin/shoryuken +3 -2
  22. data/docker-compose.yml +6 -0
  23. data/lib/{shoryuken/extensions/active_job_extensions.rb → active_job/extensions.rb} +20 -6
  24. data/lib/active_job/queue_adapters/shoryuken_adapter.rb +208 -0
  25. data/lib/active_job/queue_adapters/shoryuken_concurrent_send_adapter.rb +78 -0
  26. data/lib/shoryuken/active_job/current_attributes.rb +139 -0
  27. data/lib/shoryuken/active_job/job_wrapper.rb +28 -0
  28. data/lib/shoryuken/body_parser.rb +11 -1
  29. data/lib/shoryuken/client.rb +16 -0
  30. data/lib/shoryuken/default_exception_handler.rb +11 -0
  31. data/lib/shoryuken/default_worker_registry.rb +39 -11
  32. data/lib/shoryuken/environment_loader.rb +85 -15
  33. data/lib/shoryuken/errors.rb +36 -0
  34. data/lib/shoryuken/fetcher.rb +41 -3
  35. data/lib/shoryuken/helpers/atomic_boolean.rb +58 -0
  36. data/lib/shoryuken/helpers/atomic_counter.rb +104 -0
  37. data/lib/shoryuken/helpers/atomic_hash.rb +182 -0
  38. data/lib/shoryuken/helpers/hash_utils.rb +56 -0
  39. data/lib/shoryuken/helpers/string_utils.rb +65 -0
  40. data/lib/shoryuken/helpers/timer_task.rb +80 -0
  41. data/lib/shoryuken/inline_message.rb +22 -0
  42. data/lib/shoryuken/launcher.rb +55 -0
  43. data/lib/shoryuken/logging/base.rb +26 -0
  44. data/lib/shoryuken/logging/pretty.rb +25 -0
  45. data/lib/shoryuken/logging/without_timestamp.rb +25 -0
  46. data/lib/shoryuken/logging.rb +43 -15
  47. data/lib/shoryuken/manager.rb +84 -5
  48. data/lib/shoryuken/message.rb +116 -1
  49. data/lib/shoryuken/middleware/chain.rb +141 -43
  50. data/lib/shoryuken/middleware/entry.rb +30 -0
  51. data/lib/shoryuken/middleware/server/active_record.rb +10 -0
  52. data/lib/shoryuken/middleware/server/auto_delete.rb +12 -0
  53. data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +37 -11
  54. data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +34 -3
  55. data/lib/shoryuken/middleware/server/non_retryable_exception.rb +95 -0
  56. data/lib/shoryuken/middleware/server/timing.rb +13 -0
  57. data/lib/shoryuken/options.rb +154 -13
  58. data/lib/shoryuken/polling/base_strategy.rb +127 -0
  59. data/lib/shoryuken/polling/queue_configuration.rb +103 -0
  60. data/lib/shoryuken/polling/strict_priority.rb +41 -0
  61. data/lib/shoryuken/polling/weighted_round_robin.rb +44 -0
  62. data/lib/shoryuken/processor.rb +37 -3
  63. data/lib/shoryuken/queue.rb +99 -8
  64. data/lib/shoryuken/runner.rb +54 -16
  65. data/lib/shoryuken/util.rb +32 -7
  66. data/lib/shoryuken/version.rb +4 -1
  67. data/lib/shoryuken/worker/default_executor.rb +23 -1
  68. data/lib/shoryuken/worker/inline_executor.rb +33 -2
  69. data/lib/shoryuken/worker.rb +224 -0
  70. data/lib/shoryuken/worker_registry.rb +35 -0
  71. data/lib/shoryuken.rb +27 -38
  72. data/renovate.json +62 -0
  73. data/shoryuken.gemspec +8 -4
  74. data/spec/integration/.rspec +1 -0
  75. data/spec/integration/active_job/adapter_configuration/configuration_spec.rb +26 -0
  76. data/spec/integration/active_job/bulk_enqueue/bulk_enqueue_spec.rb +53 -0
  77. data/spec/integration/active_job/current_attributes/bulk_enqueue_spec.rb +50 -0
  78. data/spec/integration/active_job/current_attributes/complex_types_spec.rb +55 -0
  79. data/spec/integration/active_job/current_attributes/empty_context_spec.rb +41 -0
  80. data/spec/integration/active_job/current_attributes/full_context_spec.rb +63 -0
  81. data/spec/integration/active_job/current_attributes/partial_context_spec.rb +57 -0
  82. data/spec/integration/active_job/custom_attributes/number_attributes_spec.rb +37 -0
  83. data/spec/integration/active_job/custom_attributes/string_attributes_spec.rb +39 -0
  84. data/spec/integration/active_job/error_handling/job_wrapper_spec.rb +53 -0
  85. data/spec/integration/active_job/fifo_and_attributes/deduplication_spec.rb +86 -0
  86. data/spec/integration/active_job/keyword_arguments/keyword_arguments_spec.rb +63 -0
  87. data/spec/integration/active_job/retry/discard_on_spec.rb +43 -0
  88. data/spec/integration/active_job/retry/retry_on_spec.rb +36 -0
  89. data/spec/integration/active_job/roundtrip/roundtrip_spec.rb +52 -0
  90. data/spec/integration/active_job/scheduled/scheduled_spec.rb +76 -0
  91. data/spec/integration/active_record_middleware/active_record_middleware_spec.rb +84 -0
  92. data/spec/integration/auto_delete/auto_delete_spec.rb +53 -0
  93. data/spec/integration/auto_extend_visibility/auto_extend_visibility_spec.rb +57 -0
  94. data/spec/integration/aws_config/aws_config_spec.rb +59 -0
  95. data/spec/integration/batch_processing/batch_processing_spec.rb +37 -0
  96. data/spec/integration/body_parser/json_parser_spec.rb +45 -0
  97. data/spec/integration/body_parser/proc_parser_spec.rb +54 -0
  98. data/spec/integration/body_parser/text_parser_spec.rb +43 -0
  99. data/spec/integration/concurrent_processing/concurrent_processing_spec.rb +45 -0
  100. data/spec/integration/custom_group_polling_strategy/custom_group_polling_strategy_spec.rb +87 -0
  101. data/spec/integration/dead_letter_queue/dead_letter_queue_spec.rb +91 -0
  102. data/spec/integration/exception_handlers/exception_handlers_spec.rb +69 -0
  103. data/spec/integration/exponential_backoff/exponential_backoff_spec.rb +67 -0
  104. data/spec/integration/fifo_ordering/fifo_ordering_spec.rb +44 -0
  105. data/spec/integration/large_payloads/large_payloads_spec.rb +30 -0
  106. data/spec/integration/launcher/launcher_spec.rb +40 -0
  107. data/spec/integration/message_attributes/message_attributes_spec.rb +54 -0
  108. data/spec/integration/message_operations/message_operations_spec.rb +59 -0
  109. data/spec/integration/middleware_chain/empty_chain_spec.rb +11 -0
  110. data/spec/integration/middleware_chain/execution_order_spec.rb +33 -0
  111. data/spec/integration/middleware_chain/removal_spec.rb +31 -0
  112. data/spec/integration/middleware_chain/short_circuit_spec.rb +40 -0
  113. data/spec/integration/non_retryable_exception/non_retryable_exception_spec.rb +149 -0
  114. data/spec/integration/polling_strategies/polling_strategies_spec.rb +46 -0
  115. data/spec/integration/queue_operations/queue_operations_spec.rb +84 -0
  116. data/spec/integration/rails/rails_72/Gemfile +6 -0
  117. data/spec/integration/rails/rails_72/activejob_adapter_spec.rb +98 -0
  118. data/spec/integration/rails/rails_80/Gemfile +6 -0
  119. data/spec/integration/rails/rails_80/activejob_adapter_spec.rb +98 -0
  120. data/spec/integration/rails/rails_80/continuation_spec.rb +79 -0
  121. data/spec/integration/rails/rails_81/Gemfile +6 -0
  122. data/spec/integration/rails/rails_81/activejob_adapter_spec.rb +98 -0
  123. data/spec/integration/rails/rails_81/continuation_spec.rb +79 -0
  124. data/spec/integration/retry_behavior/retry_behavior_spec.rb +45 -0
  125. data/spec/integration/spec_helper.rb +7 -0
  126. data/spec/integration/strict_priority_polling/strict_priority_polling_spec.rb +58 -0
  127. data/spec/integration/visibility_timeout/visibility_timeout_spec.rb +37 -0
  128. data/spec/integration/worker_enqueueing/worker_enqueueing_spec.rb +60 -0
  129. data/spec/integration/worker_groups/worker_groups_spec.rb +79 -0
  130. data/spec/integration/worker_lifecycle/worker_lifecycle_spec.rb +33 -0
  131. data/spec/integrations_helper.rb +243 -0
  132. data/spec/lib/active_job/extensions_spec.rb +225 -0
  133. data/spec/lib/active_job/queue_adapters/shoryuken_adapter_spec.rb +29 -0
  134. data/spec/{shoryuken/extensions/active_job_concurrent_send_adapter_spec.rb → lib/active_job/queue_adapters/shoryuken_concurrent_send_adapter_spec.rb} +5 -4
  135. data/spec/{shoryuken/extensions/active_job_wrapper_spec.rb → lib/shoryuken/active_job/job_wrapper_spec.rb} +6 -5
  136. data/spec/{shoryuken → lib/shoryuken}/body_parser_spec.rb +2 -4
  137. data/spec/{shoryuken → lib/shoryuken}/client_spec.rb +1 -1
  138. data/spec/{shoryuken → lib/shoryuken}/default_exception_handler_spec.rb +9 -10
  139. data/spec/{shoryuken → lib/shoryuken}/default_worker_registry_spec.rb +1 -2
  140. data/spec/{shoryuken → lib/shoryuken}/environment_loader_spec.rb +10 -9
  141. data/spec/{shoryuken → lib/shoryuken}/fetcher_spec.rb +23 -26
  142. data/spec/lib/shoryuken/helpers/atomic_boolean_spec.rb +196 -0
  143. data/spec/lib/shoryuken/helpers/atomic_counter_spec.rb +177 -0
  144. data/spec/lib/shoryuken/helpers/atomic_hash_spec.rb +307 -0
  145. data/spec/lib/shoryuken/helpers/hash_utils_spec.rb +145 -0
  146. data/spec/lib/shoryuken/helpers/string_utils_spec.rb +124 -0
  147. data/spec/lib/shoryuken/helpers/timer_task_spec.rb +298 -0
  148. data/spec/lib/shoryuken/helpers_integration_spec.rb +96 -0
  149. data/spec/lib/shoryuken/inline_message_spec.rb +196 -0
  150. data/spec/{shoryuken → lib/shoryuken}/launcher_spec.rb +23 -2
  151. data/spec/lib/shoryuken/logging_spec.rb +242 -0
  152. data/spec/{shoryuken → lib/shoryuken}/manager_spec.rb +1 -2
  153. data/spec/lib/shoryuken/message_spec.rb +109 -0
  154. data/spec/{shoryuken → lib/shoryuken}/middleware/chain_spec.rb +1 -1
  155. data/spec/lib/shoryuken/middleware/entry_spec.rb +68 -0
  156. data/spec/lib/shoryuken/middleware/server/active_record_spec.rb +133 -0
  157. data/spec/{shoryuken → lib/shoryuken}/middleware/server/auto_delete_spec.rb +1 -1
  158. data/spec/{shoryuken → lib/shoryuken}/middleware/server/auto_extend_visibility_spec.rb +51 -1
  159. data/spec/{shoryuken → lib/shoryuken}/middleware/server/exponential_backoff_retry_spec.rb +1 -1
  160. data/spec/lib/shoryuken/middleware/server/non_retryable_exception_spec.rb +214 -0
  161. data/spec/{shoryuken → lib/shoryuken}/middleware/server/timing_spec.rb +1 -1
  162. data/spec/{shoryuken → lib/shoryuken}/options_spec.rb +49 -6
  163. data/spec/lib/shoryuken/polling/base_strategy_spec.rb +280 -0
  164. data/spec/lib/shoryuken/polling/queue_configuration_spec.rb +195 -0
  165. data/spec/{shoryuken → lib/shoryuken}/polling/strict_priority_spec.rb +1 -1
  166. data/spec/{shoryuken → lib/shoryuken}/polling/weighted_round_robin_spec.rb +1 -1
  167. data/spec/{shoryuken → lib/shoryuken}/processor_spec.rb +1 -1
  168. data/spec/{shoryuken → lib/shoryuken}/queue_spec.rb +2 -3
  169. data/spec/{shoryuken → lib/shoryuken}/runner_spec.rb +1 -3
  170. data/spec/{shoryuken → lib/shoryuken}/util_spec.rb +2 -2
  171. data/spec/lib/shoryuken/version_spec.rb +17 -0
  172. data/spec/{shoryuken → lib/shoryuken}/worker/default_executor_spec.rb +1 -1
  173. data/spec/lib/shoryuken/worker/inline_executor_spec.rb +105 -0
  174. data/spec/lib/shoryuken/worker_registry_spec.rb +63 -0
  175. data/spec/{shoryuken → lib/shoryuken}/worker_spec.rb +15 -11
  176. data/spec/{shoryuken_spec.rb → lib/shoryuken_spec.rb} +1 -1
  177. data/spec/shared_examples_for_active_job.rb +40 -15
  178. data/spec/spec_helper.rb +48 -2
  179. metadata +295 -101
  180. data/.codeclimate.yml +0 -20
  181. data/.devcontainer/Dockerfile +0 -17
  182. data/.devcontainer/base.Dockerfile +0 -43
  183. data/.devcontainer/devcontainer.json +0 -35
  184. data/.github/FUNDING.yml +0 -12
  185. data/.github/dependabot.yml +0 -6
  186. data/.github/workflows/stale.yml +0 -20
  187. data/.reek.yml +0 -5
  188. data/Appraisals +0 -42
  189. data/gemfiles/.gitignore +0 -1
  190. data/gemfiles/aws_sdk_core_2.gemfile +0 -21
  191. data/gemfiles/rails_4_2.gemfile +0 -20
  192. data/gemfiles/rails_5_2.gemfile +0 -21
  193. data/gemfiles/rails_6_0.gemfile +0 -21
  194. data/gemfiles/rails_6_1.gemfile +0 -21
  195. data/gemfiles/rails_7_0.gemfile +0 -22
  196. data/lib/shoryuken/core_ext.rb +0 -69
  197. data/lib/shoryuken/extensions/active_job_adapter.rb +0 -103
  198. data/lib/shoryuken/extensions/active_job_concurrent_send_adapter.rb +0 -50
  199. data/lib/shoryuken/polling/base.rb +0 -67
  200. data/shoryuken.jpg +0 -0
  201. data/spec/integration/launcher_spec.rb +0 -128
  202. data/spec/shoryuken/core_ext_spec.rb +0 -40
  203. data/spec/shoryuken/extensions/active_job_adapter_spec.rb +0 -7
  204. data/spec/shoryuken/extensions/active_job_base_spec.rb +0 -84
  205. data/spec/shoryuken/worker/inline_executor_spec.rb +0 -49
@@ -0,0 +1,280 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Shoryuken::Polling::BaseStrategy do
4
+ # Create a concrete implementation for testing
5
+ let(:test_strategy_class) do
6
+ Class.new(described_class) do
7
+ def initialize(queues, delay = nil)
8
+ @queues = queues
9
+ @delay = delay
10
+ end
11
+
12
+ def next_queue
13
+ @queues.first
14
+ end
15
+
16
+ def messages_found(queue, messages_found)
17
+ # Test implementation - store the call
18
+ @last_messages_found = { queue: queue, count: messages_found }
19
+ end
20
+
21
+ def active_queues
22
+ @queues
23
+ end
24
+
25
+ attr_reader :last_messages_found
26
+ end
27
+ end
28
+
29
+ let(:strategy) { test_strategy_class.new(['queue1', 'queue2']) }
30
+
31
+ describe 'abstract interface' do
32
+ it 'includes Util module' do
33
+ expect(described_class.included_modules).to include(Shoryuken::Util)
34
+ end
35
+
36
+ describe '#next_queue' do
37
+ it 'raises NotImplementedError in base class' do
38
+ base_strategy = described_class.new
39
+
40
+ expect { base_strategy.next_queue }.to raise_error(NotImplementedError)
41
+ end
42
+
43
+ it 'can be implemented by subclasses' do
44
+ expect(strategy.next_queue).to eq('queue1')
45
+ end
46
+ end
47
+
48
+ describe '#messages_found' do
49
+ it 'raises NotImplementedError in base class' do
50
+ base_strategy = described_class.new
51
+
52
+ expect { base_strategy.messages_found('queue', 5) }.to raise_error(NotImplementedError)
53
+ end
54
+
55
+ it 'can be implemented by subclasses' do
56
+ strategy.messages_found('test_queue', 3)
57
+
58
+ expect(strategy.last_messages_found).to eq({ queue: 'test_queue', count: 3 })
59
+ end
60
+
61
+ it 'accepts zero messages found' do
62
+ strategy.messages_found('empty_queue', 0)
63
+
64
+ expect(strategy.last_messages_found).to eq({ queue: 'empty_queue', count: 0 })
65
+ end
66
+ end
67
+
68
+ describe '#message_processed' do
69
+ it 'has default empty implementation' do
70
+ base_strategy = described_class.new
71
+
72
+ expect { base_strategy.message_processed('queue') }.not_to raise_error
73
+ end
74
+
75
+ it 'can be overridden by subclasses' do
76
+ # Default implementation should do nothing
77
+ expect { strategy.message_processed('queue') }.not_to raise_error
78
+ end
79
+ end
80
+
81
+ describe '#active_queues' do
82
+ it 'raises NotImplementedError in base class' do
83
+ base_strategy = described_class.new
84
+
85
+ expect { base_strategy.active_queues }.to raise_error(NotImplementedError)
86
+ end
87
+
88
+ it 'can be implemented by subclasses' do
89
+ expect(strategy.active_queues).to eq(['queue1', 'queue2'])
90
+ end
91
+ end
92
+ end
93
+
94
+ describe '#==' do
95
+ context 'when comparing with Array' do
96
+ it 'compares against @queues instance variable' do
97
+ expect(strategy).to eq(['queue1', 'queue2'])
98
+ end
99
+
100
+ it 'returns false for different arrays' do
101
+ expect(strategy).not_to eq(['queue3', 'queue4'])
102
+ end
103
+
104
+ it 'returns false for arrays with different order' do
105
+ expect(strategy).not_to eq(['queue2', 'queue1'])
106
+ end
107
+ end
108
+
109
+ context 'when comparing with another strategy' do
110
+ it 'compares active_queues when other responds to active_queues' do
111
+ other_strategy = test_strategy_class.new(['queue1', 'queue2'])
112
+
113
+ expect(strategy).to eq(other_strategy)
114
+ end
115
+
116
+ it 'returns false when active_queues differ' do
117
+ other_strategy = test_strategy_class.new(['queue3', 'queue4'])
118
+
119
+ expect(strategy).not_to eq(other_strategy)
120
+ end
121
+
122
+ it 'handles strategies with different active_queues order' do
123
+ other_strategy = test_strategy_class.new(['queue2', 'queue1'])
124
+
125
+ expect(strategy).not_to eq(other_strategy)
126
+ end
127
+ end
128
+
129
+ context 'when comparing with objects that do not respond to active_queues' do
130
+ it 'returns false for strings' do
131
+ expect(strategy).not_to eq('some_string')
132
+ end
133
+
134
+ it 'returns false for numbers' do
135
+ expect(strategy).not_to eq(123)
136
+ end
137
+
138
+ it 'returns false for hashes' do
139
+ expect(strategy).not_to eq({ queues: ['queue1', 'queue2'] })
140
+ end
141
+
142
+ it 'returns false for objects without active_queues method' do
143
+ plain_object = Object.new
144
+
145
+ expect(strategy).not_to eq(plain_object)
146
+ end
147
+ end
148
+
149
+ context 'when @queues is not set' do
150
+ let(:strategy_without_queues) { test_strategy_class.new(nil) }
151
+
152
+ it 'handles nil @queues when comparing with nil' do
153
+ # nil is not an Array, so this goes to the else branch
154
+ # nil doesn't respond to active_queues, so it returns false
155
+ expect(strategy_without_queues == nil).to be false
156
+ end
157
+
158
+ it 'handles nil @queues when comparing with empty array' do
159
+ expect(strategy_without_queues == []).to be false
160
+ end
161
+ end
162
+ end
163
+
164
+ describe '#delay' do
165
+ context 'when delay is set on strategy' do
166
+ let(:strategy_with_delay) { test_strategy_class.new(['queue1'], 5.0) }
167
+
168
+ it 'returns the strategy-specific delay' do
169
+ expect(strategy_with_delay.delay).to eq(5.0)
170
+ end
171
+ end
172
+
173
+ context 'when delay is not set on strategy' do
174
+ before do
175
+ allow(Shoryuken.options).to receive(:[]).with(:delay).and_return(3.5)
176
+ end
177
+
178
+ it 'returns the global Shoryuken delay converted to float' do
179
+ expect(strategy.delay).to eq(3.5)
180
+ end
181
+ end
182
+
183
+ context 'when global delay is a string' do
184
+ before do
185
+ allow(Shoryuken.options).to receive(:[]).with(:delay).and_return('2.5')
186
+ end
187
+
188
+ it 'converts string delay to float' do
189
+ expect(strategy.delay).to eq(2.5)
190
+ end
191
+ end
192
+
193
+ context 'when global delay is an integer' do
194
+ before do
195
+ allow(Shoryuken.options).to receive(:[]).with(:delay).and_return(4)
196
+ end
197
+
198
+ it 'converts integer delay to float' do
199
+ expect(strategy.delay).to eq(4.0)
200
+ end
201
+ end
202
+
203
+ context 'when delay is explicitly set to nil' do
204
+ let(:strategy_with_nil_delay) { test_strategy_class.new(['queue1'], nil) }
205
+
206
+ before do
207
+ allow(Shoryuken.options).to receive(:[]).with(:delay).and_return(1.5)
208
+ end
209
+
210
+ it 'falls back to global delay' do
211
+ expect(strategy_with_nil_delay.delay).to eq(1.5)
212
+ end
213
+ end
214
+ end
215
+
216
+ describe 'inheritance patterns' do
217
+ it 'allows subclasses to call super for implemented methods' do
218
+ subclass = Class.new(described_class) do
219
+ def next_queue
220
+ begin
221
+ super
222
+ rescue NotImplementedError
223
+ 'fallback'
224
+ end
225
+ end
226
+
227
+ def active_queues
228
+ []
229
+ end
230
+
231
+ def messages_found(queue, count)
232
+ # Implementation required
233
+ end
234
+ end
235
+
236
+ instance = subclass.new
237
+ expect(instance.next_queue).to eq('fallback')
238
+ end
239
+
240
+ it 'supports method chaining in subclasses' do
241
+ chainable_strategy = Class.new(described_class) do
242
+ def initialize(queues)
243
+ @queues = queues
244
+ @call_chain = []
245
+ end
246
+
247
+ def next_queue
248
+ @call_chain << :next_queue
249
+ @queues.first
250
+ end
251
+
252
+ def messages_found(queue, count)
253
+ @call_chain << :messages_found
254
+ self
255
+ end
256
+
257
+ def active_queues
258
+ @call_chain << :active_queues
259
+ @queues
260
+ end
261
+
262
+ attr_reader :call_chain
263
+ end
264
+
265
+ strategy = chainable_strategy.new(['test'])
266
+ result = strategy.messages_found('queue', 1)
267
+
268
+ expect(result).to be(strategy)
269
+ expect(strategy.call_chain).to eq([:messages_found])
270
+ end
271
+ end
272
+
273
+ describe 'utility method access' do
274
+ it 'provides access to utility methods through Util module' do
275
+ # Verify that utility methods are available
276
+ expect(strategy).to respond_to(:unparse_queues)
277
+ expect(strategy).to respond_to(:logger)
278
+ end
279
+ end
280
+ end
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Shoryuken::Polling::QueueConfiguration do
4
+ describe '#initialize' do
5
+ it 'creates configuration with name and options' do
6
+ config = described_class.new('test_queue', { priority: :high })
7
+
8
+ expect(config.name).to eq('test_queue')
9
+ expect(config.options).to eq({ priority: :high })
10
+ end
11
+
12
+ it 'creates configuration with empty options' do
13
+ config = described_class.new('simple_queue', {})
14
+
15
+ expect(config.name).to eq('simple_queue')
16
+ expect(config.options).to eq({})
17
+ end
18
+
19
+ it 'accepts nil options' do
20
+ config = described_class.new('queue', nil)
21
+
22
+ expect(config.name).to eq('queue')
23
+ expect(config.options).to be_nil
24
+ end
25
+ end
26
+
27
+ describe '#hash' do
28
+ it 'returns hash based on queue name' do
29
+ config1 = described_class.new('queue', {})
30
+ config2 = described_class.new('queue', { weight: 5 })
31
+
32
+ expect(config1.hash).to eq(config2.hash)
33
+ expect(config1.hash).to eq('queue'.hash)
34
+ end
35
+
36
+ it 'returns different hash for different queue names' do
37
+ config1 = described_class.new('queue1', {})
38
+ config2 = described_class.new('queue2', {})
39
+
40
+ expect(config1.hash).not_to eq(config2.hash)
41
+ end
42
+ end
43
+
44
+ describe '#==' do
45
+ context 'when comparing with another QueueConfiguration' do
46
+ it 'returns true for same name and options' do
47
+ config1 = described_class.new('queue', { weight: 5 })
48
+ config2 = described_class.new('queue', { weight: 5 })
49
+
50
+ expect(config1).to eq(config2)
51
+ end
52
+
53
+ it 'returns false for same name but different options' do
54
+ config1 = described_class.new('queue', { weight: 5 })
55
+ config2 = described_class.new('queue', { weight: 10 })
56
+
57
+ expect(config1).not_to eq(config2)
58
+ end
59
+
60
+ it 'returns false for different names' do
61
+ config1 = described_class.new('queue1', {})
62
+ config2 = described_class.new('queue2', {})
63
+
64
+ expect(config1).not_to eq(config2)
65
+ end
66
+ end
67
+
68
+ context 'when comparing with a string' do
69
+ it 'returns true when options are empty and names match' do
70
+ config = described_class.new('test_queue', {})
71
+
72
+ expect(config).to eq('test_queue')
73
+ end
74
+
75
+ it 'returns false when options are not empty' do
76
+ config = described_class.new('test_queue', { weight: 5 })
77
+
78
+ expect(config).not_to eq('test_queue')
79
+ end
80
+
81
+ it 'returns false when names do not match' do
82
+ config = described_class.new('queue1', {})
83
+
84
+ expect(config).not_to eq('queue2')
85
+ end
86
+ end
87
+
88
+ context 'when comparing with other objects' do
89
+ it 'returns false for non-string, non-QueueConfiguration objects' do
90
+ config = described_class.new('queue', {})
91
+
92
+ expect(config).not_to eq(123)
93
+ expect(config).not_to eq([])
94
+ expect(config).not_to eq({ name: 'queue' })
95
+ end
96
+ end
97
+ end
98
+
99
+ describe '#eql?' do
100
+ it 'behaves the same as ==' do
101
+ config1 = described_class.new('queue', {})
102
+ config2 = described_class.new('queue', {})
103
+
104
+ expect(config1.eql?(config2)).to eq(config1 == config2)
105
+ expect(config1.eql?('queue')).to eq(config1 == 'queue')
106
+ end
107
+ end
108
+
109
+ describe '#to_s' do
110
+ context 'when options are empty' do
111
+ it 'returns just the queue name' do
112
+ config = described_class.new('simple_queue', {})
113
+
114
+ expect(config.to_s).to eq('simple_queue')
115
+ end
116
+ end
117
+
118
+ context 'when options are present' do
119
+ it 'returns detailed representation with options' do
120
+ config = described_class.new('complex_queue', { priority: :high, weight: 5 })
121
+
122
+ expect(config.to_s).to include('#<QueueConfiguration complex_queue options={')
123
+ end
124
+
125
+ it 'handles single option' do
126
+ config = described_class.new('weighted_queue', { weight: 10 })
127
+
128
+ expect(config.to_s).to include('#<QueueConfiguration weighted_queue options={')
129
+ end
130
+ end
131
+
132
+ context 'when options are nil' do
133
+ it 'returns detailed representation' do
134
+ config = described_class.new('nil_options_queue', nil)
135
+
136
+ expect(config.to_s).to eq('#<QueueConfiguration nil_options_queue options=nil>')
137
+ end
138
+ end
139
+ end
140
+
141
+ describe 'struct behavior' do
142
+ it 'provides attribute accessors' do
143
+ config = described_class.new('queue', { weight: 5 })
144
+
145
+ expect(config.name).to eq('queue')
146
+ expect(config.options).to eq({ weight: 5 })
147
+ end
148
+
149
+ it 'allows attribute modification' do
150
+ config = described_class.new('queue', {})
151
+
152
+ config.name = 'new_queue'
153
+ config.options = { priority: :low }
154
+
155
+ expect(config.name).to eq('new_queue')
156
+ expect(config.options).to eq({ priority: :low })
157
+ end
158
+
159
+ it 'supports array-like access' do
160
+ config = described_class.new('queue', { weight: 5 })
161
+
162
+ expect(config[0]).to eq('queue')
163
+ expect(config[1]).to eq({ weight: 5 })
164
+ end
165
+ end
166
+
167
+ describe 'usage as hash key' do
168
+ it 'can be used as hash keys' do
169
+ config1 = described_class.new('queue', {})
170
+ config2 = described_class.new('queue', {}) # Same config
171
+
172
+ hash = {}
173
+ hash[config1] = 'value1'
174
+ hash[config2] = 'value2'
175
+
176
+ # Same queue name and options should use same hash key
177
+ expect(hash[config1]).to eq('value2')
178
+ expect(hash[config2]).to eq('value2')
179
+ expect(hash.size).to eq(1)
180
+ end
181
+
182
+ it 'different queue names create different keys' do
183
+ config1 = described_class.new('queue1', {})
184
+ config2 = described_class.new('queue2', {})
185
+
186
+ hash = {}
187
+ hash[config1] = 'value1'
188
+ hash[config2] = 'value2'
189
+
190
+ expect(hash[config1]).to eq('value1')
191
+ expect(hash[config2]).to eq('value2')
192
+ expect(hash.size).to eq(2)
193
+ end
194
+ end
195
+ end
@@ -1,4 +1,4 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
2
 
3
3
  RSpec.describe Shoryuken::Polling::StrictPriority do
4
4
  let(:queue1) { 'shoryuken' }
@@ -1,4 +1,4 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
2
 
3
3
  RSpec.describe Shoryuken::Polling::WeightedRoundRobin do
4
4
  let(:queue1) { 'shoryuken' }
@@ -1,4 +1,4 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
2
 
3
3
  RSpec.describe Shoryuken::Processor do
4
4
  let(:manager) { double Shoryuken::Manager }
@@ -1,6 +1,5 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
2
 
3
- # rubocop:disable Metrics/BlockLength
4
3
  RSpec.describe Shoryuken::Queue do
5
4
  let(:credentials) { Aws::Credentials.new('access_key_id', 'secret_access_key') }
6
5
  let(:sqs) { Aws::SQS::Client.new(stub_responses: true, credentials: credentials) }
@@ -30,7 +29,7 @@ RSpec.describe Shoryuken::Queue do
30
29
  let(:queue_url) { "http://localhost:4576/queue/#{queue_name}" }
31
30
 
32
31
  it 'instantiates by URL and validate the URL' do
33
- # See https://github.com/phstc/shoryuken/pull/551
32
+ # See https://github.com/ruby-shoryuken/shoryuken/pull/551
34
33
  expect_any_instance_of(described_class).to receive(:fifo?).and_return(false)
35
34
 
36
35
  subject = described_class.new(sqs, queue_url)
@@ -1,7 +1,5 @@
1
- require 'spec_helper'
2
- require 'shoryuken/runner'
1
+ # frozen_string_literal: true
3
2
 
4
- # rubocop:disable Metrics/BlockLength
5
3
  RSpec.describe Shoryuken::Runner do
6
4
  let(:cli) { Shoryuken::Runner.instance }
7
5
 
@@ -1,4 +1,4 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
2
 
3
3
  describe 'Shoryuken::Util' do
4
4
  subject do
@@ -26,7 +26,7 @@ describe 'Shoryuken::Util' do
26
26
  end
27
27
 
28
28
  let(:message_attributes) do
29
- { 'shoryuken_class' => { string_value: ActiveJob::QueueAdapters::ShoryukenAdapter::JobWrapper.to_s } }
29
+ { 'shoryuken_class' => { string_value: 'Shoryuken::ActiveJob::JobWrapper' } }
30
30
  end
31
31
 
32
32
  let(:body) do
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Shoryuken::VERSION do
6
+ it 'has a version number' do
7
+ expect(Shoryuken::VERSION).not_to be_nil
8
+ end
9
+
10
+ it 'follows semantic versioning format' do
11
+ expect(Shoryuken::VERSION).to match(/^\d+\.\d+\.\d+/)
12
+ end
13
+
14
+ it 'is a string' do
15
+ expect(Shoryuken::VERSION).to be_a(String)
16
+ end
17
+ end
@@ -1,4 +1,4 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
2
 
3
3
  RSpec.describe Shoryuken::Worker::DefaultExecutor do
4
4
  let(:sqs_queue) { double 'SQS Queue' }
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Shoryuken::Worker::InlineExecutor do
4
+ before do
5
+ Shoryuken.worker_executor = described_class
6
+ end
7
+
8
+ describe '.perform_async' do
9
+ specify do
10
+ expect_any_instance_of(TestWorker).to receive(:perform).with(anything, 'test')
11
+
12
+ TestWorker.perform_async('test')
13
+ end
14
+
15
+ it 'properly sets message_attributes' do
16
+ custom_attributes = {
17
+ 'custom_key' => { string_value: 'custom_value', data_type: 'String' }
18
+ }
19
+
20
+ expect_any_instance_of(TestWorker).to receive(:perform) do |_, sqs_msg, _|
21
+ expect(sqs_msg.message_attributes).to include('shoryuken_class')
22
+ expect(sqs_msg.message_attributes).to include('custom_key')
23
+ expect(sqs_msg.message_attributes['custom_key'][:string_value]).to eq('custom_value')
24
+ end
25
+
26
+ TestWorker.perform_async('test', message_attributes: custom_attributes)
27
+ end
28
+ end
29
+
30
+ describe '.perform_in' do
31
+ specify do
32
+ expect_any_instance_of(TestWorker).to receive(:perform).with(anything, 'test')
33
+
34
+ TestWorker.perform_in(60, 'test')
35
+ end
36
+
37
+ it 'properly passes message_attributes to perform_async' do
38
+ custom_attributes = {
39
+ 'custom_key' => { string_value: 'custom_value', data_type: 'String' }
40
+ }
41
+
42
+ expect_any_instance_of(TestWorker).to receive(:perform) do |_, sqs_msg, _|
43
+ expect(sqs_msg.message_attributes).to include('shoryuken_class')
44
+ expect(sqs_msg.message_attributes).to include('custom_key')
45
+ expect(sqs_msg.message_attributes['custom_key'][:string_value]).to eq('custom_value')
46
+ end
47
+
48
+ TestWorker.perform_in(60, 'test', message_attributes: custom_attributes)
49
+ end
50
+ end
51
+
52
+ context 'batch' do
53
+ before do
54
+ TestWorker.get_shoryuken_options['batch'] = true
55
+ end
56
+
57
+ after do
58
+ TestWorker.get_shoryuken_options['batch'] = false
59
+ end
60
+
61
+ describe '.perform_async' do
62
+ specify do
63
+ expect_any_instance_of(TestWorker).to receive(:perform).with(anything, ['test'])
64
+
65
+ TestWorker.perform_async('test')
66
+ end
67
+
68
+ it 'properly passes message_attributes with batch' do
69
+ custom_attributes = {
70
+ 'custom_key' => { string_value: 'custom_value', data_type: 'String' }
71
+ }
72
+
73
+ expect_any_instance_of(TestWorker).to receive(:perform) do |_, sqs_msgs, _|
74
+ expect(sqs_msgs.first.message_attributes).to include('shoryuken_class')
75
+ expect(sqs_msgs.first.message_attributes).to include('custom_key')
76
+ expect(sqs_msgs.first.message_attributes['custom_key'][:string_value]).to eq('custom_value')
77
+ end
78
+
79
+ TestWorker.perform_async('test', message_attributes: custom_attributes)
80
+ end
81
+ end
82
+
83
+ describe '.perform_in' do
84
+ specify do
85
+ expect_any_instance_of(TestWorker).to receive(:perform).with(anything, ['test'])
86
+
87
+ TestWorker.perform_in(60, 'test')
88
+ end
89
+
90
+ it 'properly passes message_attributes with batch' do
91
+ custom_attributes = {
92
+ 'custom_key' => { string_value: 'custom_value', data_type: 'String' }
93
+ }
94
+
95
+ expect_any_instance_of(TestWorker).to receive(:perform) do |_, sqs_msgs, _|
96
+ expect(sqs_msgs.first.message_attributes).to include('shoryuken_class')
97
+ expect(sqs_msgs.first.message_attributes).to include('custom_key')
98
+ expect(sqs_msgs.first.message_attributes['custom_key'][:string_value]).to eq('custom_value')
99
+ end
100
+
101
+ TestWorker.perform_in(60, 'test', message_attributes: custom_attributes)
102
+ end
103
+ end
104
+ end
105
+ end