shoryuken 7.0.0.alpha1 → 7.0.0.alpha2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/push.yml +3 -3
- data/.github/workflows/specs.yml +6 -9
- data/.github/workflows/verify-action-pins.yml +1 -1
- data/.rspec +2 -1
- data/.ruby-version +1 -1
- data/Appraisals +0 -6
- data/CHANGELOG.md +186 -142
- data/Gemfile +1 -0
- data/README.md +12 -13
- data/bin/cli/base.rb +1 -2
- data/bin/cli/sqs.rb +5 -4
- data/bin/shoryuken +2 -1
- data/gemfiles/rails_7_0.gemfile +10 -10
- data/gemfiles/rails_7_1.gemfile +10 -9
- data/gemfiles/rails_7_2.gemfile +10 -9
- data/gemfiles/rails_8_0.gemfile +10 -9
- data/lib/shoryuken/body_parser.rb +3 -1
- data/lib/shoryuken/client.rb +2 -0
- data/lib/shoryuken/default_exception_handler.rb +2 -0
- data/lib/shoryuken/default_worker_registry.rb +11 -11
- data/lib/shoryuken/environment_loader.rb +6 -6
- data/lib/shoryuken/extensions/active_job_adapter.rb +8 -6
- data/lib/shoryuken/extensions/active_job_concurrent_send_adapter.rb +5 -5
- data/lib/shoryuken/extensions/active_job_extensions.rb +2 -0
- data/lib/shoryuken/fetcher.rb +4 -2
- data/lib/shoryuken/helpers/atomic_boolean.rb +44 -0
- data/lib/shoryuken/helpers/atomic_counter.rb +104 -0
- data/lib/shoryuken/helpers/atomic_hash.rb +182 -0
- data/lib/shoryuken/helpers/hash_utils.rb +56 -0
- data/lib/shoryuken/helpers/string_utils.rb +65 -0
- data/lib/shoryuken/inline_message.rb +22 -0
- data/lib/shoryuken/launcher.rb +2 -0
- data/lib/shoryuken/logging.rb +19 -5
- data/lib/shoryuken/manager.rb +6 -4
- data/lib/shoryuken/message.rb +2 -0
- data/lib/shoryuken/middleware/chain.rb +2 -0
- data/lib/shoryuken/middleware/server/active_record.rb +2 -0
- data/lib/shoryuken/middleware/server/auto_delete.rb +2 -0
- data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +10 -10
- data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +5 -3
- data/lib/shoryuken/middleware/server/timing.rb +2 -0
- data/lib/shoryuken/options.rb +9 -5
- data/lib/shoryuken/polling/base_strategy.rb +126 -0
- data/lib/shoryuken/polling/queue_configuration.rb +103 -0
- data/lib/shoryuken/polling/strict_priority.rb +2 -0
- data/lib/shoryuken/polling/weighted_round_robin.rb +2 -0
- data/lib/shoryuken/processor.rb +5 -2
- data/lib/shoryuken/queue.rb +6 -4
- data/lib/shoryuken/runner.rb +9 -12
- data/lib/shoryuken/util.rb +6 -6
- data/lib/shoryuken/version.rb +3 -1
- data/lib/shoryuken/worker/default_executor.rb +2 -0
- data/lib/shoryuken/worker/inline_executor.rb +3 -1
- data/lib/shoryuken/worker.rb +2 -0
- data/lib/shoryuken/worker_registry.rb +2 -0
- data/lib/shoryuken.rb +8 -28
- data/shoryuken.gemspec +6 -6
- data/spec/integration/launcher_spec.rb +2 -3
- data/spec/shared_examples_for_active_job.rb +13 -8
- data/spec/shoryuken/body_parser_spec.rb +1 -2
- data/spec/shoryuken/client_spec.rb +1 -1
- data/spec/shoryuken/default_exception_handler_spec.rb +9 -10
- data/spec/shoryuken/default_worker_registry_spec.rb +1 -2
- data/spec/shoryuken/environment_loader_spec.rb +9 -8
- data/spec/shoryuken/extensions/active_job_adapter_spec.rb +2 -1
- data/spec/shoryuken/extensions/active_job_base_spec.rb +2 -1
- data/spec/shoryuken/extensions/active_job_concurrent_send_adapter_spec.rb +2 -1
- data/spec/shoryuken/extensions/active_job_wrapper_spec.rb +2 -1
- data/spec/shoryuken/fetcher_spec.rb +23 -26
- data/spec/shoryuken/helpers/atomic_boolean_spec.rb +196 -0
- data/spec/shoryuken/helpers/atomic_counter_spec.rb +177 -0
- data/spec/shoryuken/helpers/atomic_hash_spec.rb +307 -0
- data/spec/shoryuken/helpers/hash_utils_spec.rb +145 -0
- data/spec/shoryuken/helpers/string_utils_spec.rb +124 -0
- data/spec/shoryuken/helpers_integration_spec.rb +96 -0
- data/spec/shoryuken/inline_message_spec.rb +196 -0
- data/spec/shoryuken/launcher_spec.rb +1 -2
- data/spec/shoryuken/manager_spec.rb +1 -2
- data/spec/shoryuken/middleware/chain_spec.rb +1 -1
- data/spec/shoryuken/middleware/server/auto_delete_spec.rb +1 -1
- data/spec/shoryuken/middleware/server/auto_extend_visibility_spec.rb +1 -1
- data/spec/shoryuken/middleware/server/exponential_backoff_retry_spec.rb +1 -1
- data/spec/shoryuken/middleware/server/timing_spec.rb +1 -1
- data/spec/shoryuken/options_spec.rb +4 -4
- data/spec/shoryuken/polling/base_strategy_spec.rb +280 -0
- data/spec/shoryuken/polling/queue_configuration_spec.rb +195 -0
- data/spec/shoryuken/polling/strict_priority_spec.rb +1 -1
- data/spec/shoryuken/polling/weighted_round_robin_spec.rb +1 -1
- data/spec/shoryuken/processor_spec.rb +1 -1
- data/spec/shoryuken/queue_spec.rb +2 -3
- data/spec/shoryuken/runner_spec.rb +1 -3
- data/spec/shoryuken/util_spec.rb +1 -1
- data/spec/shoryuken/worker/default_executor_spec.rb +1 -1
- data/spec/shoryuken/worker/inline_executor_spec.rb +1 -1
- data/spec/shoryuken/worker_spec.rb +15 -11
- data/spec/shoryuken_spec.rb +1 -1
- data/spec/spec_helper.rb +16 -0
- metadata +60 -27
- data/.github/FUNDING.yml +0 -12
- data/gemfiles/rails_6_1.gemfile +0 -18
- data/lib/shoryuken/core_ext.rb +0 -69
- data/lib/shoryuken/polling/base.rb +0 -67
- data/shoryuken.jpg +0 -0
- data/spec/shoryuken/core_ext_spec.rb +0 -40
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Shoryuken::InlineMessage do
|
|
4
|
+
let(:body) { '{"message": "test"}' }
|
|
5
|
+
let(:attributes) { { 'SentTimestamp' => '1234567890' } }
|
|
6
|
+
let(:md5_of_body) { 'abc123def456' }
|
|
7
|
+
let(:md5_of_message_attributes) { 'def456abc123' }
|
|
8
|
+
let(:message_attributes) { { 'CustomAttribute' => { string_value: 'value', data_type: 'String' } } }
|
|
9
|
+
let(:message_id) { 'msg-12345' }
|
|
10
|
+
let(:receipt_handle) { 'receipt-handle-12345' }
|
|
11
|
+
let(:delete) { nil }
|
|
12
|
+
let(:queue_name) { 'test-queue' }
|
|
13
|
+
|
|
14
|
+
describe '#new' do
|
|
15
|
+
context 'with positional arguments' do
|
|
16
|
+
subject do
|
|
17
|
+
described_class.new(
|
|
18
|
+
body: body,
|
|
19
|
+
attributes: attributes,
|
|
20
|
+
md5_of_body: md5_of_body,
|
|
21
|
+
md5_of_message_attributes: md5_of_message_attributes,
|
|
22
|
+
message_attributes: message_attributes,
|
|
23
|
+
message_id: message_id,
|
|
24
|
+
receipt_handle: receipt_handle,
|
|
25
|
+
delete: delete,
|
|
26
|
+
queue_name: queue_name
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'initializes with all attributes' do
|
|
31
|
+
expect(subject.body).to eq(body)
|
|
32
|
+
expect(subject.attributes).to eq(attributes)
|
|
33
|
+
expect(subject.md5_of_body).to eq(md5_of_body)
|
|
34
|
+
expect(subject.md5_of_message_attributes).to eq(md5_of_message_attributes)
|
|
35
|
+
expect(subject.message_attributes).to eq(message_attributes)
|
|
36
|
+
expect(subject.message_id).to eq(message_id)
|
|
37
|
+
expect(subject.receipt_handle).to eq(receipt_handle)
|
|
38
|
+
expect(subject.delete).to eq(delete)
|
|
39
|
+
expect(subject.queue_name).to eq(queue_name)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
context 'with keyword arguments' do
|
|
44
|
+
subject do
|
|
45
|
+
described_class.new(
|
|
46
|
+
body: body,
|
|
47
|
+
attributes: attributes,
|
|
48
|
+
md5_of_body: md5_of_body,
|
|
49
|
+
md5_of_message_attributes: md5_of_message_attributes,
|
|
50
|
+
message_attributes: message_attributes,
|
|
51
|
+
message_id: message_id,
|
|
52
|
+
receipt_handle: receipt_handle,
|
|
53
|
+
delete: delete,
|
|
54
|
+
queue_name: queue_name
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'initializes with all attributes' do
|
|
59
|
+
expect(subject.body).to eq(body)
|
|
60
|
+
expect(subject.attributes).to eq(attributes)
|
|
61
|
+
expect(subject.md5_of_body).to eq(md5_of_body)
|
|
62
|
+
expect(subject.md5_of_message_attributes).to eq(md5_of_message_attributes)
|
|
63
|
+
expect(subject.message_attributes).to eq(message_attributes)
|
|
64
|
+
expect(subject.message_id).to eq(message_id)
|
|
65
|
+
expect(subject.receipt_handle).to eq(receipt_handle)
|
|
66
|
+
expect(subject.delete).to eq(delete)
|
|
67
|
+
expect(subject.queue_name).to eq(queue_name)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
context 'with nil values' do
|
|
72
|
+
subject do
|
|
73
|
+
described_class.new(
|
|
74
|
+
body: body,
|
|
75
|
+
attributes: nil,
|
|
76
|
+
md5_of_body: nil,
|
|
77
|
+
md5_of_message_attributes: nil,
|
|
78
|
+
message_attributes: message_attributes,
|
|
79
|
+
message_id: nil,
|
|
80
|
+
receipt_handle: nil,
|
|
81
|
+
delete: nil,
|
|
82
|
+
queue_name: queue_name
|
|
83
|
+
)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it 'handles nil values correctly' do
|
|
87
|
+
expect(subject.body).to eq(body)
|
|
88
|
+
expect(subject.attributes).to be_nil
|
|
89
|
+
expect(subject.md5_of_body).to be_nil
|
|
90
|
+
expect(subject.md5_of_message_attributes).to be_nil
|
|
91
|
+
expect(subject.message_attributes).to eq(message_attributes)
|
|
92
|
+
expect(subject.message_id).to be_nil
|
|
93
|
+
expect(subject.receipt_handle).to be_nil
|
|
94
|
+
expect(subject.delete).to be_nil
|
|
95
|
+
expect(subject.queue_name).to eq(queue_name)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
context 'with minimal required attributes' do
|
|
100
|
+
subject { described_class.new(body: body, queue_name: queue_name) }
|
|
101
|
+
|
|
102
|
+
it 'initializes with only required attributes' do
|
|
103
|
+
expect(subject.body).to eq(body)
|
|
104
|
+
expect(subject.queue_name).to eq(queue_name)
|
|
105
|
+
expect(subject.attributes).to be_nil
|
|
106
|
+
expect(subject.md5_of_body).to be_nil
|
|
107
|
+
expect(subject.md5_of_message_attributes).to be_nil
|
|
108
|
+
expect(subject.message_attributes).to be_nil
|
|
109
|
+
expect(subject.message_id).to be_nil
|
|
110
|
+
expect(subject.receipt_handle).to be_nil
|
|
111
|
+
expect(subject.delete).to be_nil
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
describe 'attribute accessors' do
|
|
117
|
+
subject do
|
|
118
|
+
described_class.new(
|
|
119
|
+
body: body,
|
|
120
|
+
attributes: attributes,
|
|
121
|
+
md5_of_body: md5_of_body,
|
|
122
|
+
md5_of_message_attributes: md5_of_message_attributes,
|
|
123
|
+
message_attributes: message_attributes,
|
|
124
|
+
message_id: message_id,
|
|
125
|
+
receipt_handle: receipt_handle,
|
|
126
|
+
delete: delete,
|
|
127
|
+
queue_name: queue_name
|
|
128
|
+
)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it 'provides read access to all attributes' do
|
|
132
|
+
expect(subject.body).to eq(body)
|
|
133
|
+
expect(subject.attributes).to eq(attributes)
|
|
134
|
+
expect(subject.md5_of_body).to eq(md5_of_body)
|
|
135
|
+
expect(subject.md5_of_message_attributes).to eq(md5_of_message_attributes)
|
|
136
|
+
expect(subject.message_attributes).to eq(message_attributes)
|
|
137
|
+
expect(subject.message_id).to eq(message_id)
|
|
138
|
+
expect(subject.receipt_handle).to eq(receipt_handle)
|
|
139
|
+
expect(subject.delete).to eq(delete)
|
|
140
|
+
expect(subject.queue_name).to eq(queue_name)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it 'provides write access to all attributes' do
|
|
144
|
+
new_body = '{"updated": "message"}'
|
|
145
|
+
new_queue_name = 'updated-queue'
|
|
146
|
+
|
|
147
|
+
subject.body = new_body
|
|
148
|
+
subject.queue_name = new_queue_name
|
|
149
|
+
|
|
150
|
+
expect(subject.body).to eq(new_body)
|
|
151
|
+
expect(subject.queue_name).to eq(new_queue_name)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
describe 'struct behavior' do
|
|
156
|
+
subject { described_class.new(body: body, queue_name: queue_name) }
|
|
157
|
+
|
|
158
|
+
it 'behaves like a struct' do
|
|
159
|
+
expect(subject).to be_a(Struct)
|
|
160
|
+
expect(subject.class.superclass).to eq(Struct)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
it 'supports array-like access' do
|
|
164
|
+
expect(subject[0]).to eq(body) # body is first attribute
|
|
165
|
+
expect(subject[-1]).to eq(queue_name) # queue_name is last attribute
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
it 'supports enumeration' do
|
|
169
|
+
values = subject.to_a
|
|
170
|
+
expect(values.first).to eq(body)
|
|
171
|
+
expect(values.last).to eq(queue_name)
|
|
172
|
+
expect(values.length).to eq(9) # 9 attributes total
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
it 'supports hash conversion' do
|
|
176
|
+
hash = subject.to_h
|
|
177
|
+
expect(hash[:body]).to eq(body)
|
|
178
|
+
expect(hash[:queue_name]).to eq(queue_name)
|
|
179
|
+
expect(hash.keys).to contain_exactly(
|
|
180
|
+
:body, :attributes, :md5_of_body, :md5_of_message_attributes,
|
|
181
|
+
:message_attributes, :message_id, :receipt_handle, :delete, :queue_name
|
|
182
|
+
)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
describe 'equality' do
|
|
187
|
+
let(:message1) { described_class.new(body: body, queue_name: queue_name) }
|
|
188
|
+
let(:message2) { described_class.new(body: body, queue_name: queue_name) }
|
|
189
|
+
let(:message3) { described_class.new(body: 'different', queue_name: queue_name) }
|
|
190
|
+
|
|
191
|
+
it 'compares messages by attribute values' do
|
|
192
|
+
expect(message1).to eq(message2)
|
|
193
|
+
expect(message1).not_to eq(message3)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
RSpec.describe Shoryuken::Options do
|
|
4
4
|
subject { Shoryuken.shoryuken_options }
|
|
5
5
|
|
|
6
6
|
describe '.on_stop' do
|
|
7
7
|
specify do
|
|
8
|
-
on_stop =
|
|
8
|
+
on_stop = proc {}
|
|
9
9
|
Shoryuken.on_stop(&on_stop)
|
|
10
10
|
|
|
11
11
|
expect(Shoryuken.stop_callback).to eq(on_stop)
|
|
@@ -14,7 +14,7 @@ RSpec.describe Shoryuken::Options do
|
|
|
14
14
|
|
|
15
15
|
describe '.on_start' do
|
|
16
16
|
specify do
|
|
17
|
-
on_start =
|
|
17
|
+
on_start = proc {}
|
|
18
18
|
Shoryuken.on_start(&on_start)
|
|
19
19
|
|
|
20
20
|
expect(Shoryuken.start_callback).to eq(on_start)
|
|
@@ -81,7 +81,7 @@ RSpec.describe Shoryuken::Options do
|
|
|
81
81
|
|
|
82
82
|
expect(Shoryuken.sqs_client_receive_message_opts).to eq(
|
|
83
83
|
'default' => { test: 1 },
|
|
84
|
-
'group1'
|
|
84
|
+
'group1' => { test: 2 }
|
|
85
85
|
)
|
|
86
86
|
end
|
|
87
87
|
end
|
|
@@ -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
|