shoryuken 7.0.0.alpha1 → 7.0.0.rc1

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 (113) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/push.yml +3 -3
  3. data/.github/workflows/specs.yml +27 -17
  4. data/.github/workflows/verify-action-pins.yml +1 -1
  5. data/.rspec +2 -1
  6. data/.ruby-version +1 -1
  7. data/Appraisals +6 -18
  8. data/CHANGELOG.md +200 -142
  9. data/Gemfile +1 -0
  10. data/README.md +12 -13
  11. data/bin/cli/base.rb +1 -2
  12. data/bin/cli/sqs.rb +6 -5
  13. data/bin/shoryuken +3 -2
  14. data/gemfiles/rails_7_2.gemfile +1 -0
  15. data/gemfiles/rails_8_0.gemfile +1 -0
  16. data/gemfiles/{rails_7_1.gemfile → rails_8_1.gemfile} +2 -1
  17. data/lib/shoryuken/body_parser.rb +3 -1
  18. data/lib/shoryuken/client.rb +2 -0
  19. data/lib/shoryuken/default_exception_handler.rb +2 -0
  20. data/lib/shoryuken/default_worker_registry.rb +11 -11
  21. data/lib/shoryuken/environment_loader.rb +6 -6
  22. data/lib/shoryuken/extensions/active_job_adapter.rb +21 -6
  23. data/lib/shoryuken/extensions/active_job_concurrent_send_adapter.rb +5 -5
  24. data/lib/shoryuken/extensions/active_job_extensions.rb +2 -0
  25. data/lib/shoryuken/fetcher.rb +4 -2
  26. data/lib/shoryuken/helpers/atomic_boolean.rb +44 -0
  27. data/lib/shoryuken/helpers/atomic_counter.rb +104 -0
  28. data/lib/shoryuken/helpers/atomic_hash.rb +182 -0
  29. data/lib/shoryuken/helpers/hash_utils.rb +56 -0
  30. data/lib/shoryuken/helpers/string_utils.rb +65 -0
  31. data/lib/shoryuken/helpers/timer_task.rb +66 -0
  32. data/lib/shoryuken/inline_message.rb +22 -0
  33. data/lib/shoryuken/launcher.rb +16 -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 +6 -12
  38. data/lib/shoryuken/manager.rb +6 -4
  39. data/lib/shoryuken/message.rb +116 -1
  40. data/lib/shoryuken/middleware/chain.rb +140 -43
  41. data/lib/shoryuken/middleware/entry.rb +30 -0
  42. data/lib/shoryuken/middleware/server/active_record.rb +2 -0
  43. data/lib/shoryuken/middleware/server/auto_delete.rb +2 -0
  44. data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +11 -11
  45. data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +5 -3
  46. data/lib/shoryuken/middleware/server/timing.rb +2 -0
  47. data/lib/shoryuken/options.rb +9 -5
  48. data/lib/shoryuken/polling/base_strategy.rb +126 -0
  49. data/lib/shoryuken/polling/queue_configuration.rb +103 -0
  50. data/lib/shoryuken/polling/strict_priority.rb +2 -0
  51. data/lib/shoryuken/polling/weighted_round_robin.rb +2 -0
  52. data/lib/shoryuken/processor.rb +5 -2
  53. data/lib/shoryuken/queue.rb +6 -4
  54. data/lib/shoryuken/runner.rb +12 -12
  55. data/lib/shoryuken/util.rb +6 -6
  56. data/lib/shoryuken/version.rb +3 -1
  57. data/lib/shoryuken/worker/default_executor.rb +2 -0
  58. data/lib/shoryuken/worker/inline_executor.rb +3 -1
  59. data/lib/shoryuken/worker.rb +173 -0
  60. data/lib/shoryuken/worker_registry.rb +2 -0
  61. data/lib/shoryuken.rb +8 -28
  62. data/shoryuken.gemspec +6 -6
  63. data/spec/integration/active_job_continuation_spec.rb +145 -0
  64. data/spec/integration/launcher_spec.rb +2 -3
  65. data/spec/shared_examples_for_active_job.rb +13 -8
  66. data/spec/shoryuken/body_parser_spec.rb +1 -2
  67. data/spec/shoryuken/client_spec.rb +1 -1
  68. data/spec/shoryuken/default_exception_handler_spec.rb +9 -10
  69. data/spec/shoryuken/default_worker_registry_spec.rb +1 -2
  70. data/spec/shoryuken/environment_loader_spec.rb +9 -8
  71. data/spec/shoryuken/extensions/active_job_adapter_spec.rb +2 -1
  72. data/spec/shoryuken/extensions/active_job_base_spec.rb +2 -1
  73. data/spec/shoryuken/extensions/active_job_concurrent_send_adapter_spec.rb +2 -1
  74. data/spec/shoryuken/extensions/active_job_continuation_spec.rb +110 -0
  75. data/spec/shoryuken/extensions/active_job_wrapper_spec.rb +2 -1
  76. data/spec/shoryuken/fetcher_spec.rb +23 -26
  77. data/spec/shoryuken/helpers/atomic_boolean_spec.rb +196 -0
  78. data/spec/shoryuken/helpers/atomic_counter_spec.rb +177 -0
  79. data/spec/shoryuken/helpers/atomic_hash_spec.rb +307 -0
  80. data/spec/shoryuken/helpers/hash_utils_spec.rb +145 -0
  81. data/spec/shoryuken/helpers/string_utils_spec.rb +124 -0
  82. data/spec/shoryuken/helpers/timer_task_spec.rb +298 -0
  83. data/spec/shoryuken/helpers_integration_spec.rb +96 -0
  84. data/spec/shoryuken/inline_message_spec.rb +196 -0
  85. data/spec/shoryuken/launcher_spec.rb +23 -2
  86. data/spec/shoryuken/manager_spec.rb +1 -2
  87. data/spec/shoryuken/middleware/chain_spec.rb +1 -1
  88. data/spec/shoryuken/middleware/server/auto_delete_spec.rb +1 -1
  89. data/spec/shoryuken/middleware/server/auto_extend_visibility_spec.rb +1 -1
  90. data/spec/shoryuken/middleware/server/exponential_backoff_retry_spec.rb +1 -1
  91. data/spec/shoryuken/middleware/server/timing_spec.rb +1 -1
  92. data/spec/shoryuken/options_spec.rb +4 -4
  93. data/spec/shoryuken/polling/base_strategy_spec.rb +280 -0
  94. data/spec/shoryuken/polling/queue_configuration_spec.rb +195 -0
  95. data/spec/shoryuken/polling/strict_priority_spec.rb +1 -1
  96. data/spec/shoryuken/polling/weighted_round_robin_spec.rb +1 -1
  97. data/spec/shoryuken/processor_spec.rb +1 -1
  98. data/spec/shoryuken/queue_spec.rb +2 -3
  99. data/spec/shoryuken/runner_spec.rb +1 -3
  100. data/spec/shoryuken/util_spec.rb +1 -1
  101. data/spec/shoryuken/worker/default_executor_spec.rb +1 -1
  102. data/spec/shoryuken/worker/inline_executor_spec.rb +1 -1
  103. data/spec/shoryuken/worker_spec.rb +15 -11
  104. data/spec/shoryuken_spec.rb +1 -1
  105. data/spec/spec_helper.rb +16 -0
  106. metadata +72 -29
  107. data/.github/FUNDING.yml +0 -12
  108. data/gemfiles/rails_6_1.gemfile +0 -18
  109. data/gemfiles/rails_7_0.gemfile +0 -19
  110. data/lib/shoryuken/core_ext.rb +0 -69
  111. data/lib/shoryuken/polling/base.rb +0 -67
  112. data/shoryuken.jpg +0 -0
  113. data/spec/shoryuken/core_ext_spec.rb +0 -40
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ require 'active_job'
5
+ require 'shoryuken/extensions/active_job_adapter'
6
+ require 'shoryuken/extensions/active_job_extensions'
7
+
8
+ RSpec.describe 'ActiveJob Continuations Integration' do
9
+ # Skip all tests in this suite if ActiveJob::Continuable is not available (Rails < 8.0)
10
+ before(:all) do
11
+ skip 'ActiveJob::Continuable not available (Rails < 8.0)' unless defined?(ActiveJob::Continuable)
12
+ end
13
+
14
+ # Test job that uses ActiveJob Continuations
15
+ class ContinuableTestJob < ActiveJob::Base
16
+ include ActiveJob::Continuable if defined?(ActiveJob::Continuable)
17
+
18
+ queue_as :default
19
+
20
+ class_attribute :executions_log, default: []
21
+ class_attribute :checkpoints_reached, default: []
22
+
23
+ def perform(max_iterations: 10)
24
+ self.class.executions_log << { execution: executions, started_at: Time.current }
25
+
26
+ step :initialize_work do
27
+ self.class.checkpoints_reached << "initialize_work_#{executions}"
28
+ end
29
+
30
+ step :process_items, start: cursor || 0 do
31
+ (cursor..max_iterations).each do |i|
32
+ self.class.checkpoints_reached << "processing_item_#{i}"
33
+
34
+ # Check if we should stop (checkpoint)
35
+ checkpoint
36
+
37
+ # Simulate some work
38
+ sleep 0.01
39
+
40
+ # Advance cursor
41
+ cursor.advance!
42
+ end
43
+ end
44
+
45
+ step :finalize_work do
46
+ self.class.checkpoints_reached << 'finalize_work'
47
+ end
48
+
49
+ self.class.executions_log.last[:completed] = true
50
+ end
51
+ end
52
+
53
+ describe 'stopping? method (unit tests)' do
54
+ it 'returns false when launcher is not initialized' do
55
+ adapter = ActiveJob::QueueAdapters::ShoryukenAdapter.new
56
+ expect(adapter.stopping?).to be false
57
+ end
58
+
59
+ it 'returns true when launcher is stopping' do
60
+ launcher = Shoryuken::Launcher.new
61
+ runner = Shoryuken::Runner.instance
62
+ runner.instance_variable_set(:@launcher, launcher)
63
+
64
+ adapter = ActiveJob::QueueAdapters::ShoryukenAdapter.new
65
+ expect(adapter.stopping?).to be false
66
+
67
+ launcher.instance_variable_set(:@stopping, true)
68
+ expect(adapter.stopping?).to be true
69
+ end
70
+ end
71
+
72
+ describe 'timestamp handling for continuation retries' do
73
+ it 'handles past timestamps for continuation retries' do
74
+ adapter = ActiveJob::QueueAdapters::ShoryukenAdapter.new
75
+ job = ContinuableTestJob.new
76
+ job.sqs_send_message_parameters = {}
77
+
78
+ # Mock the queue
79
+ queue = instance_double(Shoryuken::Queue, fifo?: false)
80
+ allow(Shoryuken::Client).to receive(:queues).and_return(queue)
81
+ allow(Shoryuken).to receive(:register_worker)
82
+ allow(queue).to receive(:send_message) do |params|
83
+ # Verify past timestamp results in immediate delivery (delay_seconds <= 0)
84
+ expect(params[:delay_seconds]).to be <= 0
85
+ end
86
+
87
+ # Enqueue with past timestamp (simulating continuation retry)
88
+ past_timestamp = Time.current.to_f - 60
89
+ adapter.enqueue_at(job, past_timestamp)
90
+ end
91
+ end
92
+
93
+ describe 'enqueue_at with continuation timestamps (unit tests)' do
94
+ let(:adapter) { ActiveJob::QueueAdapters::ShoryukenAdapter.new }
95
+ let(:job) do
96
+ job = ContinuableTestJob.new
97
+ job.sqs_send_message_parameters = {}
98
+ job
99
+ end
100
+ let(:queue) { instance_double(Shoryuken::Queue, fifo?: false) }
101
+
102
+ before do
103
+ allow(Shoryuken::Client).to receive(:queues).and_return(queue)
104
+ allow(Shoryuken).to receive(:register_worker)
105
+ @sent_messages = []
106
+ allow(queue).to receive(:send_message) do |params|
107
+ @sent_messages << params
108
+ end
109
+ end
110
+
111
+ it 'accepts past timestamps without error' do
112
+ past_timestamp = Time.current.to_f - 30
113
+
114
+ expect {
115
+ adapter.enqueue_at(job, past_timestamp)
116
+ }.not_to raise_error
117
+
118
+ expect(@sent_messages.size).to eq(1)
119
+ expect(@sent_messages.first[:delay_seconds]).to be <= 0
120
+ end
121
+
122
+ it 'accepts current timestamp' do
123
+ current_timestamp = Time.current.to_f
124
+
125
+ expect {
126
+ adapter.enqueue_at(job, current_timestamp)
127
+ }.not_to raise_error
128
+
129
+ expect(@sent_messages.size).to eq(1)
130
+ expect(@sent_messages.first[:delay_seconds]).to be_between(-1, 1)
131
+ end
132
+
133
+ it 'accepts future timestamp' do
134
+ future_timestamp = Time.current.to_f + 30
135
+
136
+ expect {
137
+ adapter.enqueue_at(job, future_timestamp)
138
+ }.not_to raise_error
139
+
140
+ expect(@sent_messages.size).to eq(1)
141
+ expect(@sent_messages.first[:delay_seconds]).to be > 0
142
+ expect(@sent_messages.first[:delay_seconds]).to be <= 30
143
+ end
144
+ end
145
+ end
@@ -1,6 +1,5 @@
1
- require 'spec_helper'
2
- require 'shoryuken/manager'
3
- require 'shoryuken/launcher'
1
+ # frozen_string_literal: true
2
+
4
3
  require 'securerandom'
5
4
 
6
5
  RSpec.describe Shoryuken::Launcher do
@@ -24,7 +24,7 @@ RSpec.shared_examples 'active_job_adapters' do
24
24
  expect(queue).to receive(:send_message) do |hash|
25
25
  expect(hash[:message_deduplication_id]).to_not be
26
26
  expect(hash[:message_attributes]['shoryuken_class'][:string_value]).to eq(described_class::JobWrapper.to_s)
27
- expect(hash[:message_attributes]['shoryuken_class'][:data_type]).to eq("String")
27
+ expect(hash[:message_attributes]['shoryuken_class'][:data_type]).to eq('String')
28
28
  expect(hash[:message_attributes].keys).to eq(['shoryuken_class'])
29
29
  end
30
30
  expect(Shoryuken).to receive(:register_worker).with(job.queue_name, described_class::JobWrapper)
@@ -133,9 +133,9 @@ RSpec.shared_examples 'active_job_adapters' do
133
133
 
134
134
  expect(queue).to receive(:send_message) do |hash|
135
135
  expect(hash[:message_attributes]['shoryuken_class'][:string_value]).to eq(described_class::JobWrapper.to_s)
136
- expect(hash[:message_attributes]['shoryuken_class'][:data_type]).to eq("String")
136
+ expect(hash[:message_attributes]['shoryuken_class'][:data_type]).to eq('String')
137
137
  expect(hash[:message_attributes]['tracer_id'][:string_value]).to eq(custom_message_attributes['tracer_id'][:string_value])
138
- expect(hash[:message_attributes]['tracer_id'][:data_type]).to eq("String")
138
+ expect(hash[:message_attributes]['tracer_id'][:data_type]).to eq('String')
139
139
  end
140
140
  expect(Shoryuken).to receive(:register_worker).with(job.queue_name, described_class::JobWrapper)
141
141
 
@@ -157,7 +157,8 @@ RSpec.shared_examples 'active_job_adapters' do
157
157
  it 'should enqueue a message with the message_attributes specified on the job' do
158
158
  expect(queue).to receive(:send_message) do |hash|
159
159
  expect(hash[:message_attributes]['tracer_id']).to eq({ data_type: 'String', string_value: 'job-value' })
160
- expect(hash[:message_attributes]['shoryuken_class']).to eq({ data_type: 'String', string_value: described_class::JobWrapper.to_s })
160
+ expect(hash[:message_attributes]['shoryuken_class']).to eq({ data_type: 'String',
161
+ string_value: described_class::JobWrapper.to_s })
161
162
  end
162
163
  subject.enqueue job
163
164
  end
@@ -185,8 +186,10 @@ RSpec.shared_examples 'active_job_adapters' do
185
186
 
186
187
  expect(queue).to receive(:send_message) do |hash|
187
188
  expect(hash[:message_attributes]['tracer_id']).to be_nil
188
- expect(hash[:message_attributes]['options_tracer_id']).to eq({ data_type: 'String', string_value: 'options-value' })
189
- expect(hash[:message_attributes]['shoryuken_class']).to eq({ data_type: 'String', string_value: described_class::JobWrapper.to_s })
189
+ expect(hash[:message_attributes]['options_tracer_id']).to eq({ data_type: 'String',
190
+ string_value: 'options-value' })
191
+ expect(hash[:message_attributes]['shoryuken_class']).to eq({ data_type: 'String',
192
+ string_value: described_class::JobWrapper.to_s })
190
193
  end
191
194
  subject.enqueue job, message_attributes: custom_message_attributes
192
195
  end
@@ -225,7 +228,8 @@ RSpec.shared_examples 'active_job_adapters' do
225
228
 
226
229
  it 'should enqueue a message with the message_system_attributes specified on the job' do
227
230
  expect(queue).to receive(:send_message) do |hash|
228
- expect(hash[:message_system_attributes]['AWSTraceHeader']).to eq({ data_type: 'String', string_value: 'job-value' })
231
+ expect(hash[:message_system_attributes]['AWSTraceHeader']).to eq({ data_type: 'String',
232
+ string_value: 'job-value' })
229
233
  end
230
234
  subject.enqueue job
231
235
  end
@@ -253,7 +257,8 @@ RSpec.shared_examples 'active_job_adapters' do
253
257
 
254
258
  expect(queue).to receive(:send_message) do |hash|
255
259
  expect(hash[:message_system_attributes]['job_trace_header']).to be_nil
256
- expect(hash[:message_system_attributes]['options_trace_header']).to eq({ data_type: 'String', string_value: 'options-value' })
260
+ expect(hash[:message_system_attributes]['options_trace_header']).to eq({ data_type: 'String',
261
+ string_value: 'options-value' })
257
262
  end
258
263
  subject.enqueue job, message_system_attributes: custom_message_attributes
259
264
  end
@@ -1,5 +1,4 @@
1
- require 'spec_helper'
2
- require 'shoryuken/body_parser'
1
+ # frozen_string_literal: true
3
2
 
4
3
  RSpec.describe Shoryuken::BodyParser do
5
4
  let(:sqs_msg) { double }
@@ -1,4 +1,4 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
2
 
3
3
  RSpec.describe Shoryuken::Client do
4
4
  let(:credentials) { Aws::Credentials.new('access_key_id', 'secret_access_key') }
@@ -1,12 +1,11 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
2
 
3
- # rubocop:disable Metrics/BlockLength
4
3
  RSpec.describe Shoryuken::DefaultExceptionHandler do
5
4
  class CustomErrorHandler
6
5
  extend Shoryuken::Util
7
6
 
8
7
  def self.call(_ex, queue, _msg)
9
- logger.error("#{queue.to_s} failed to process the message")
8
+ logger.error("#{queue} failed to process the message")
10
9
  end
11
10
  end
12
11
 
@@ -38,31 +37,31 @@ RSpec.describe Shoryuken::DefaultExceptionHandler do
38
37
 
39
38
  subject { Shoryuken::Processor.new(queue, sqs_msg) }
40
39
 
41
- context "with default handler" do
40
+ context 'with default handler' do
42
41
  before do
43
42
  Shoryuken.exception_handlers = described_class
44
43
  end
45
44
 
46
- it "logs an error message" do
45
+ it 'logs an error message' do
47
46
  expect(Shoryuken::Logging.logger).to receive(:error).twice
48
47
 
49
- allow_any_instance_of(TestWorker).to receive(:perform).and_raise(StandardError, "error")
48
+ allow_any_instance_of(TestWorker).to receive(:perform).and_raise(StandardError, 'error')
50
49
  allow(sqs_msg).to receive(:body)
51
50
 
52
51
  expect { subject.process }.to raise_error(StandardError)
53
52
  end
54
53
  end
55
54
 
56
- context "with custom handler" do
55
+ context 'with custom handler' do
57
56
  before do
58
57
  Shoryuken.exception_handlers = [described_class, CustomErrorHandler]
59
58
  end
60
59
 
61
- it "logs default and custom error messages" do
60
+ it 'logs default and custom error messages' do
62
61
  expect(Shoryuken::Logging.logger).to receive(:error).twice
63
- expect(Shoryuken::Logging.logger).to receive(:error).with("default failed to process the message").once
62
+ expect(Shoryuken::Logging.logger).to receive(:error).with('default failed to process the message').once
64
63
 
65
- allow_any_instance_of(TestWorker).to receive(:perform).and_raise(StandardError, "error")
64
+ allow_any_instance_of(TestWorker).to receive(:perform).and_raise(StandardError, 'error')
66
65
  allow(sqs_msg).to receive(:body)
67
66
 
68
67
  expect { subject.process }.to raise_error(StandardError)
@@ -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::DefaultWorkerRegistry do
5
4
  class RegistryTestWorker
6
5
  include Shoryuken::Worker
@@ -1,4 +1,5 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
+
2
3
  require 'active_job'
3
4
 
4
5
  RSpec.describe Shoryuken::EnvironmentLoader do
@@ -63,7 +64,7 @@ RSpec.describe Shoryuken::EnvironmentLoader do
63
64
  end
64
65
 
65
66
  specify do
66
- Shoryuken.options[:queues] = ['queue1', 'queue2'] # default queues
67
+ Shoryuken.options[:queues] = %w[queue1 queue2] # default queues
67
68
  Shoryuken.options[:groups] = [['custom', { queues: ['queue3'], delay: 25 }]]
68
69
  subject.load
69
70
 
@@ -124,11 +125,11 @@ RSpec.describe Shoryuken::EnvironmentLoader do
124
125
  end
125
126
  end
126
127
 
127
- describe "#setup_options" do
128
- let(:cli_queues) { { "queue1" => 10, "queue2" => 20 } }
129
- let(:config_queues) { [["queue1", 8], ["queue2", 4]] }
128
+ describe '#setup_options' do
129
+ let(:cli_queues) { { 'queue1' => 10, 'queue2' => 20 } }
130
+ let(:config_queues) { [['queue1', 8], ['queue2', 4]] }
130
131
 
131
- context "when given queues through config and CLI" do
132
+ context 'when given queues through config and CLI' do
132
133
  specify do
133
134
  allow_any_instance_of(Shoryuken::EnvironmentLoader).to receive(:config_file_options).and_return({ queues: config_queues })
134
135
  Shoryuken::EnvironmentLoader.setup_options(queues: cli_queues)
@@ -136,7 +137,7 @@ RSpec.describe Shoryuken::EnvironmentLoader do
136
137
  end
137
138
  end
138
139
 
139
- context "when given queues through config only" do
140
+ context 'when given queues through config only' do
140
141
  specify do
141
142
  allow_any_instance_of(Shoryuken::EnvironmentLoader).to receive(:config_file_options).and_return({ queues: config_queues })
142
143
  Shoryuken::EnvironmentLoader.setup_options({})
@@ -144,7 +145,7 @@ RSpec.describe Shoryuken::EnvironmentLoader do
144
145
  end
145
146
  end
146
147
 
147
- context "when given queues through CLI only" do
148
+ context 'when given queues through CLI only' do
148
149
  specify do
149
150
  Shoryuken::EnvironmentLoader.setup_options(queues: cli_queues)
150
151
  expect(Shoryuken.options[:queues]).to eq(cli_queues)
@@ -1,4 +1,5 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
+
2
3
  require 'shared_examples_for_active_job'
3
4
  require 'shoryuken/extensions/active_job_adapter'
4
5
 
@@ -1,4 +1,5 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
+
2
3
  require 'active_job'
3
4
  require 'shoryuken/extensions/active_job_extensions'
4
5
  require 'shoryuken/extensions/active_job_adapter'
@@ -1,4 +1,5 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
+
2
3
  require 'shared_examples_for_active_job'
3
4
  require 'shoryuken/extensions/active_job_adapter'
4
5
  require 'shoryuken/extensions/active_job_concurrent_send_adapter'
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_job'
4
+ require 'shared_examples_for_active_job'
5
+ require 'shoryuken/extensions/active_job_adapter'
6
+ require 'shoryuken/extensions/active_job_extensions'
7
+
8
+ RSpec.describe 'ActiveJob Continuation support' do
9
+ let(:adapter) { ActiveJob::QueueAdapters::ShoryukenAdapter.new }
10
+ let(:job) do
11
+ job = TestJob.new
12
+ job.sqs_send_message_parameters = {}
13
+ job
14
+ end
15
+ let(:queue) { double('Queue', fifo?: false) }
16
+
17
+ before do
18
+ allow(Shoryuken::Client).to receive(:queues).with(job.queue_name).and_return(queue)
19
+ allow(Shoryuken).to receive(:register_worker)
20
+ end
21
+
22
+ describe '#stopping?' do
23
+ context 'when Launcher is not initialized' do
24
+ it 'returns false' do
25
+ runner = instance_double(Shoryuken::Runner, launcher: nil)
26
+ allow(Shoryuken::Runner).to receive(:instance).and_return(runner)
27
+
28
+ expect(adapter.stopping?).to be false
29
+ end
30
+ end
31
+
32
+ context 'when Launcher is initialized' do
33
+ let(:runner) { instance_double(Shoryuken::Runner) }
34
+ let(:launcher) { instance_double(Shoryuken::Launcher) }
35
+
36
+ before do
37
+ allow(Shoryuken::Runner).to receive(:instance).and_return(runner)
38
+ allow(runner).to receive(:launcher).and_return(launcher)
39
+ end
40
+
41
+ it 'returns false when not stopping' do
42
+ allow(launcher).to receive(:stopping?).and_return(false)
43
+ expect(adapter.stopping?).to be false
44
+ end
45
+
46
+ it 'returns true when stopping' do
47
+ allow(launcher).to receive(:stopping?).and_return(true)
48
+ expect(adapter.stopping?).to be true
49
+ end
50
+ end
51
+ end
52
+
53
+ describe '#enqueue_at with past timestamps' do
54
+ let(:past_timestamp) { Time.current.to_f - 60 } # 60 seconds ago
55
+
56
+ it 'enqueues with negative delay_seconds when timestamp is in the past' do
57
+ expect(queue).to receive(:send_message) do |hash|
58
+ expect(hash[:delay_seconds]).to be <= 0
59
+ expect(hash[:delay_seconds]).to be >= -61 # Allow for rounding and timing
60
+ end
61
+
62
+ adapter.enqueue_at(job, past_timestamp)
63
+ end
64
+
65
+ it 'does not raise an error for past timestamps' do
66
+ allow(queue).to receive(:send_message)
67
+
68
+ expect { adapter.enqueue_at(job, past_timestamp) }.not_to raise_error
69
+ end
70
+ end
71
+
72
+ describe '#enqueue_at with future timestamps' do
73
+ let(:future_timestamp) { Time.current.to_f + 60 } # 60 seconds from now
74
+
75
+ it 'enqueues with delay_seconds when timestamp is in the future' do
76
+ expect(queue).to receive(:send_message) do |hash|
77
+ expect(hash[:delay_seconds]).to be > 0
78
+ expect(hash[:delay_seconds]).to be <= 60
79
+ end
80
+
81
+ adapter.enqueue_at(job, future_timestamp)
82
+ end
83
+ end
84
+
85
+ describe '#enqueue_at with current timestamp' do
86
+ let(:current_timestamp) { Time.current.to_f }
87
+
88
+ it 'enqueues with delay_seconds close to 0' do
89
+ expect(queue).to receive(:send_message) do |hash|
90
+ expect(hash[:delay_seconds]).to be_between(-1, 1) # Allow for timing/rounding
91
+ end
92
+
93
+ adapter.enqueue_at(job, current_timestamp)
94
+ end
95
+ end
96
+
97
+ describe 'retry_on with zero wait' do
98
+ it 'allows immediate retries through continuation mechanism' do
99
+ # Simulate a job with retry_on configuration that uses zero wait
100
+ past_timestamp = Time.current.to_f - 1
101
+
102
+ expect(queue).to receive(:send_message) do |hash|
103
+ # Negative delay for past timestamp - SQS will handle immediate delivery
104
+ expect(hash[:delay_seconds]).to be <= 0
105
+ end
106
+
107
+ adapter.enqueue_at(job, past_timestamp)
108
+ end
109
+ end
110
+ end
@@ -1,4 +1,5 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
+
2
3
  require 'active_job'
3
4
  require 'shoryuken/extensions/active_job_extensions'
4
5
  require 'shoryuken/extensions/active_job_adapter'
@@ -1,8 +1,5 @@
1
- require 'spec_helper'
2
- require 'shoryuken/manager'
3
- require 'shoryuken/fetcher'
1
+ # frozen_string_literal: true
4
2
 
5
- # rubocop:disable Metrics/BlockLength
6
3
  RSpec.describe Shoryuken::Fetcher do
7
4
  let(:queue) { instance_double('Shoryuken::Queue', fifo?: false) }
8
5
  let(:queue_name) { 'default' }
@@ -29,17 +26,17 @@ RSpec.describe Shoryuken::Fetcher do
29
26
  Shoryuken.sqs_client_receive_message_opts[group] = { wait_time_seconds: 10 }
30
27
 
31
28
  expect(queue).to receive(:receive_messages).with({
32
- wait_time_seconds: 10,
33
- max_number_of_messages: limit,
34
- message_attribute_names: ['All'],
35
- attribute_names: ['All']
36
- }).and_return([])
29
+ wait_time_seconds: 10,
30
+ max_number_of_messages: limit,
31
+ message_attribute_names: ['All'],
32
+ attribute_names: ['All']
33
+ }).and_return([])
37
34
 
38
35
  subject.fetch(queue_config, limit)
39
36
  end
40
37
 
41
38
  it 'logs debug only' do
42
- # See https://github.com/phstc/shoryuken/issues/435
39
+ # See https://github.com/ruby-shoryuken/shoryuken/issues/435
43
40
  logger = double 'logger'
44
41
 
45
42
  allow(subject).to receive(:logger).and_return(logger)
@@ -63,10 +60,10 @@ RSpec.describe Shoryuken::Fetcher do
63
60
  Shoryuken.sqs_client_receive_message_opts[queue_name] = { max_number_of_messages: 1 }
64
61
 
65
62
  expect(queue).to receive(:receive_messages).with({
66
- max_number_of_messages: 1,
67
- message_attribute_names: ['All'],
68
- attribute_names: ['All']
69
- }).and_return([])
63
+ max_number_of_messages: 1,
64
+ message_attribute_names: ['All'],
65
+ attribute_names: ['All']
66
+ }).and_return([])
70
67
 
71
68
  subject.fetch(queue_config, limit)
72
69
  end
@@ -79,10 +76,10 @@ RSpec.describe Shoryuken::Fetcher do
79
76
  Shoryuken.sqs_client_receive_message_opts[queue_name] = { max_number_of_messages: 20 }
80
77
 
81
78
  expect(queue).to receive(:receive_messages).with({
82
- max_number_of_messages: limit,
83
- message_attribute_names: ['All'],
84
- attribute_names: ['All']
85
- }).and_return([])
79
+ max_number_of_messages: limit,
80
+ message_attribute_names: ['All'],
81
+ attribute_names: ['All']
82
+ }).and_return([])
86
83
 
87
84
  subject.fetch(queue_config, limit)
88
85
  end
@@ -94,8 +91,8 @@ RSpec.describe Shoryuken::Fetcher do
94
91
  specify do
95
92
  allow(Shoryuken::Client).to receive(:queues).with(queue_name).and_return(queue)
96
93
  expect(queue).to receive(:receive_messages).with({
97
- max_number_of_messages: described_class::FETCH_LIMIT, attribute_names: ['All'], message_attribute_names: ['All']
98
- }).and_return([])
94
+ max_number_of_messages: described_class::FETCH_LIMIT, attribute_names: ['All'], message_attribute_names: ['All']
95
+ }).and_return([])
99
96
 
100
97
  subject.fetch(queue_config, limit)
101
98
  end
@@ -106,26 +103,26 @@ RSpec.describe Shoryuken::Fetcher do
106
103
  let(:queue) { instance_double('Shoryuken::Queue', fifo?: true, name: queue_name) }
107
104
 
108
105
  it 'polls one message at a time' do
109
- # see https://github.com/phstc/shoryuken/pull/530
106
+ # see https://github.com/ruby-shoryuken/shoryuken/pull/530
110
107
 
111
108
  allow(Shoryuken::Client).to receive(:queues).with(queue_name).and_return(queue)
112
109
  expect(queue).to receive(:receive_messages).with({
113
- max_number_of_messages: 1, attribute_names: ['All'], message_attribute_names: ['All']
114
- }).and_return([])
110
+ max_number_of_messages: 1, attribute_names: ['All'], message_attribute_names: ['All']
111
+ }).and_return([])
115
112
 
116
113
  subject.fetch(queue_config, limit)
117
114
  end
118
115
 
119
116
  context 'with batch=true' do
120
117
  it 'polls the provided limit' do
121
- # see https://github.com/phstc/shoryuken/pull/530
118
+ # see https://github.com/ruby-shoryuken/shoryuken/pull/530
122
119
 
123
120
  allow(Shoryuken::Client).to receive(:queues).with(queue_name).and_return(queue)
124
121
  allow(Shoryuken.worker_registry).to receive(:batch_receive_messages?).with(queue.name).and_return(true)
125
122
 
126
123
  expect(queue).to receive(:receive_messages).with({
127
- max_number_of_messages: limit, attribute_names: ['All'], message_attribute_names: ['All']
128
- }).and_return([])
124
+ max_number_of_messages: limit, attribute_names: ['All'], message_attribute_names: ['All']
125
+ }).and_return([])
129
126
 
130
127
  subject.fetch(queue_config, limit)
131
128
  end