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,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Shoryuken::Message do
6
+ let(:client) { instance_double('Aws::SQS::Client') }
7
+ let(:queue) { instance_double('Shoryuken::Queue', name: 'test-queue', url: 'https://sqs.us-east-1.amazonaws.com/123456789/test-queue') }
8
+ let(:data) do
9
+ instance_double('Aws::SQS::Types::Message',
10
+ message_id: 'msg-123',
11
+ receipt_handle: 'handle-456',
12
+ md5_of_body: 'abcd1234',
13
+ body: '{"test": "data"}',
14
+ attributes: { 'ApproximateReceiveCount' => '1' },
15
+ md5_of_message_attributes: 'efgh5678',
16
+ message_attributes: { 'type' => 'test' })
17
+ end
18
+
19
+ subject { described_class.new(client, queue, data) }
20
+
21
+ describe '#initialize' do
22
+ it 'sets client, queue_url, queue_name, and data' do
23
+ expect(subject.client).to eq(client)
24
+ expect(subject.queue_url).to eq('https://sqs.us-east-1.amazonaws.com/123456789/test-queue')
25
+ expect(subject.queue_name).to eq('test-queue')
26
+ expect(subject.data).to eq(data)
27
+ end
28
+ end
29
+
30
+ describe 'delegated methods' do
31
+ it 'delegates message_id to data' do
32
+ expect(subject.message_id).to eq('msg-123')
33
+ end
34
+
35
+ it 'delegates receipt_handle to data' do
36
+ expect(subject.receipt_handle).to eq('handle-456')
37
+ end
38
+
39
+ it 'delegates md5_of_body to data' do
40
+ expect(subject.md5_of_body).to eq('abcd1234')
41
+ end
42
+
43
+ it 'delegates body to data' do
44
+ expect(subject.body).to eq('{"test": "data"}')
45
+ end
46
+
47
+ it 'delegates attributes to data' do
48
+ expect(subject.attributes).to eq({ 'ApproximateReceiveCount' => '1' })
49
+ end
50
+
51
+ it 'delegates md5_of_message_attributes to data' do
52
+ expect(subject.md5_of_message_attributes).to eq('efgh5678')
53
+ end
54
+
55
+ it 'delegates message_attributes to data' do
56
+ expect(subject.message_attributes).to eq({ 'type' => 'test' })
57
+ end
58
+ end
59
+
60
+ describe '#delete' do
61
+ it 'calls delete_message on the client with correct parameters' do
62
+ expect(client).to receive(:delete_message).with(
63
+ queue_url: 'https://sqs.us-east-1.amazonaws.com/123456789/test-queue',
64
+ receipt_handle: 'handle-456'
65
+ )
66
+
67
+ subject.delete
68
+ end
69
+ end
70
+
71
+ describe '#change_visibility' do
72
+ it 'calls change_message_visibility on the client with merged parameters' do
73
+ options = { visibility_timeout: 300 }
74
+
75
+ expect(client).to receive(:change_message_visibility).with(hash_including(
76
+ visibility_timeout: 300,
77
+ queue_url: 'https://sqs.us-east-1.amazonaws.com/123456789/test-queue',
78
+ receipt_handle: 'handle-456'
79
+ ))
80
+
81
+ subject.change_visibility(options)
82
+ end
83
+
84
+ it 'merges queue_url and receipt_handle into provided options' do
85
+ options = { visibility_timeout: 120, custom_param: 'value' }
86
+
87
+ expect(client).to receive(:change_message_visibility).with(hash_including(
88
+ visibility_timeout: 120,
89
+ custom_param: 'value',
90
+ queue_url: 'https://sqs.us-east-1.amazonaws.com/123456789/test-queue',
91
+ receipt_handle: 'handle-456'
92
+ ))
93
+
94
+ subject.change_visibility(options)
95
+ end
96
+ end
97
+
98
+ describe '#visibility_timeout=' do
99
+ it 'calls change_message_visibility on the client with the timeout' do
100
+ expect(client).to receive(:change_message_visibility).with(
101
+ queue_url: 'https://sqs.us-east-1.amazonaws.com/123456789/test-queue',
102
+ receipt_handle: 'handle-456',
103
+ visibility_timeout: 600
104
+ )
105
+
106
+ subject.visibility_timeout = 600
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'shoryuken/middleware/entry'
4
+
5
+ RSpec.describe Shoryuken::Middleware::Entry do
6
+ describe '#initialize' do
7
+ it 'stores the middleware class' do
8
+ entry = described_class.new(String)
9
+ expect(entry.klass).to eq String
10
+ end
11
+
12
+ it 'stores initialization arguments' do
13
+ entry = described_class.new(String, 'arg1', 'arg2')
14
+ expect(entry.instance_variable_get(:@args)).to eq ['arg1', 'arg2']
15
+ end
16
+ end
17
+
18
+ describe '#make_new' do
19
+ let(:test_class) do
20
+ Class.new do
21
+ attr_reader :args
22
+
23
+ def initialize(*args)
24
+ @args = args
25
+ end
26
+ end
27
+ end
28
+
29
+ it 'creates a new instance of the stored class without arguments' do
30
+ entry = described_class.new(test_class)
31
+ instance = entry.make_new
32
+
33
+ expect(instance).to be_a test_class
34
+ expect(instance.args).to eq []
35
+ end
36
+
37
+ it 'creates a new instance with stored arguments' do
38
+ entry = described_class.new(test_class, 'arg1', 42, { key: 'value' })
39
+ instance = entry.make_new
40
+
41
+ expect(instance).to be_a test_class
42
+ expect(instance.args).to eq ['arg1', 42, { key: 'value' }]
43
+ end
44
+
45
+ it 'creates a new instance each time it is called' do
46
+ entry = described_class.new(test_class, 'shared_arg')
47
+ instance1 = entry.make_new
48
+ instance2 = entry.make_new
49
+
50
+ expect(instance1).to be_a test_class
51
+ expect(instance2).to be_a test_class
52
+ expect(instance1).not_to be instance2
53
+ expect(instance1.args).to eq instance2.args
54
+ end
55
+ end
56
+
57
+ describe '#klass' do
58
+ it 'returns the stored class' do
59
+ entry = described_class.new(Array)
60
+ expect(entry.klass).to eq Array
61
+ end
62
+
63
+ it 'is readable' do
64
+ entry = described_class.new(Hash)
65
+ expect(entry.klass).to eq Hash
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Shoryuken::Middleware::Server::ActiveRecord do
6
+ subject { described_class.new }
7
+
8
+ # Mock ActiveRecord to avoid requiring the actual gem in tests
9
+ before do
10
+ # Create mock ActiveRecord module
11
+ active_record_module = Module.new
12
+
13
+ # Create mock Base class with simplified methods
14
+ active_record_base = Class.new do
15
+ @connection_handler = nil
16
+
17
+ def self.clear_active_connections!
18
+ # Mock implementation for Rails < 7.1
19
+ end
20
+
21
+ def self.connection_handler
22
+ @connection_handler ||= Object.new.tap do |handler|
23
+ def handler.clear_active_connections!(_pool_key)
24
+ # Mock implementation for Rails 7.1+
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ active_record_module.const_set('Base', active_record_base)
31
+ stub_const('ActiveRecord', active_record_module)
32
+
33
+ # Mock version checking - start with a simple approach
34
+ def active_record_module.version
35
+ @version ||= Object.new.tap do |v|
36
+ def v.>=(other)
37
+ # For our tests, we'll control this with instance variables
38
+ @is_rails_71_or_higher ||= false
39
+ end
40
+
41
+ def v.rails_71_or_higher!
42
+ @is_rails_71_or_higher = true
43
+ end
44
+
45
+ def v.rails_70!
46
+ @is_rails_71_or_higher = false
47
+ end
48
+ end
49
+ end
50
+
51
+ # Mock Gem::Version
52
+ unless defined?(Gem::Version)
53
+ gem_module = Module.new
54
+ gem_version_class = Class.new do
55
+ def initialize(_version)
56
+ # Simple mock
57
+ end
58
+ end
59
+ gem_module.const_set('Version', gem_version_class)
60
+ stub_const('Gem', gem_module)
61
+ end
62
+ end
63
+
64
+ describe '#call' do
65
+ it 'yields to the block' do
66
+ block_called = false
67
+ subject.call do
68
+ block_called = true
69
+ end
70
+ expect(block_called).to be true
71
+ end
72
+
73
+ it 'returns the value from the block' do
74
+ result = subject.call { 'block_result' }
75
+ expect(result).to eq('block_result')
76
+ end
77
+
78
+ context 'when ActiveRecord version is 7.1 or higher' do
79
+ before do
80
+ # Mock Rails 7.1+ behavior
81
+ allow(ActiveRecord).to receive(:version).and_return(double('>=' => true))
82
+ end
83
+
84
+ it 'calls clear_active_connections! on connection_handler with :all parameter' do
85
+ connection_handler = ActiveRecord::Base.connection_handler
86
+ expect(connection_handler).to receive(:clear_active_connections!).with(:all)
87
+
88
+ subject.call { 'test' }
89
+ end
90
+
91
+ it 'clears connections even when an exception is raised' do
92
+ connection_handler = ActiveRecord::Base.connection_handler
93
+ expect(connection_handler).to receive(:clear_active_connections!).with(:all)
94
+
95
+ expect do
96
+ subject.call { raise StandardError, 'test error' }
97
+ end.to raise_error(StandardError, 'test error')
98
+ end
99
+ end
100
+
101
+ context 'when ActiveRecord version is lower than 7.1' do
102
+ before do
103
+ # Mock Rails < 7.1 behavior
104
+ allow(ActiveRecord).to receive(:version).and_return(double('>=' => false))
105
+ end
106
+
107
+ it 'calls clear_active_connections! directly on ActiveRecord::Base' do
108
+ expect(ActiveRecord::Base).to receive(:clear_active_connections!)
109
+
110
+ subject.call { 'test' }
111
+ end
112
+
113
+ it 'clears connections even when an exception is raised' do
114
+ expect(ActiveRecord::Base).to receive(:clear_active_connections!)
115
+
116
+ expect do
117
+ subject.call { raise StandardError, 'test error' }
118
+ end.to raise_error(StandardError, 'test error')
119
+ end
120
+ end
121
+
122
+ it 'works with middleware arguments (ignores them)' do
123
+ allow(ActiveRecord).to receive(:version).and_return(double('>=' => false))
124
+ expect(ActiveRecord::Base).to receive(:clear_active_connections!)
125
+
126
+ worker = double('worker')
127
+ message = double('message')
128
+
129
+ result = subject.call(worker, message) { 'middleware_result' }
130
+ expect(result).to eq('middleware_result')
131
+ end
132
+ end
133
+ end
@@ -64,4 +64,54 @@ RSpec.describe Shoryuken::Middleware::Server::AutoExtendVisibility do
64
64
 
65
65
  Runner.new.run_and_sleep(TestWorker.new, queue, sqs_msg, visibility_timeout)
66
66
  end
67
+
68
+ context 'when batch worker with auto_visibility_timeout' do
69
+ it 'warns and does not extend visibility for batch workers' do
70
+ TestWorker.get_shoryuken_options['auto_visibility_timeout'] = true
71
+
72
+ expect(Shoryuken.logger).to receive(:warn) do |&block|
73
+ expect(block.call).to include("Auto extend visibility isn't supported for batch workers")
74
+ end
75
+
76
+ expect { |b| subject.call(TestWorker.new, queue, [sqs_msg], nil, &b) }.to yield_control
77
+ end
78
+ end
79
+
80
+ context 'when visibility extension fails' do
81
+ it 'logs error when change_visibility raises an exception' do
82
+ TestWorker.get_shoryuken_options['auto_visibility_timeout'] = true
83
+
84
+ allow(sqs_msg).to receive(:queue) { sqs_queue }
85
+ allow(sqs_msg).to receive(:message_id).and_return('test-message-id')
86
+ allow(sqs_msg).to receive(:change_visibility).and_raise(StandardError, 'AWS error')
87
+
88
+ expect(Shoryuken.logger).to receive(:error) do |&block|
89
+ msg = block.call
90
+ expect(msg).to include('Could not auto extend the message')
91
+ expect(msg).to include('test-message-id')
92
+ expect(msg).to include('AWS error')
93
+ end
94
+
95
+ Runner.new.run_and_sleep(TestWorker.new, queue, sqs_msg, visibility_timeout)
96
+ end
97
+ end
98
+
99
+ context 'debug logging' do
100
+ it 'logs debug message when extending visibility' do
101
+ TestWorker.get_shoryuken_options['auto_visibility_timeout'] = true
102
+
103
+ allow(sqs_msg).to receive(:queue) { sqs_queue }
104
+ allow(sqs_msg).to receive(:message_id).and_return('test-message-id')
105
+ allow(sqs_msg).to receive(:change_visibility)
106
+
107
+ expect(Shoryuken.logger).to receive(:debug) do |&block|
108
+ msg = block.call
109
+ expect(msg).to include('Extending message')
110
+ expect(msg).to include('test-message-id')
111
+ expect(msg).to include("by #{visibility_timeout}s")
112
+ end
113
+
114
+ Runner.new.run_and_sleep(TestWorker.new, queue, sqs_msg, visibility_timeout)
115
+ end
116
+ end
67
117
  end
@@ -188,8 +188,8 @@ RSpec.describe Shoryuken::Options do
188
188
  end
189
189
 
190
190
  specify do
191
- expect { Shoryuken.polling_strategy('default') }.to raise_error(ArgumentError)
192
- expect { Shoryuken.polling_strategy('group1') }.to raise_error(ArgumentError)
191
+ expect { Shoryuken.polling_strategy('default') }.to raise_error(Shoryuken::Errors::InvalidPollingStrategyError)
192
+ expect { Shoryuken.polling_strategy('group1') }.to raise_error(Shoryuken::Errors::InvalidPollingStrategyError)
193
193
  end
194
194
  end
195
195
 
@@ -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
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Shoryuken::WorkerRegistry do
6
+ subject { described_class.new }
7
+
8
+ describe '#batch_receive_messages?' do
9
+ it 'raises NotImplementedError' do
10
+ expect { subject.batch_receive_messages?('test-queue') }.to raise_error(NotImplementedError)
11
+ end
12
+ end
13
+
14
+ describe '#clear' do
15
+ it 'raises NotImplementedError' do
16
+ expect { subject.clear }.to raise_error(NotImplementedError)
17
+ end
18
+ end
19
+
20
+ describe '#fetch_worker' do
21
+ it 'raises NotImplementedError' do
22
+ queue = 'test-queue'
23
+ message = double('message')
24
+ expect { subject.fetch_worker(queue, message) }.to raise_error(NotImplementedError)
25
+ end
26
+ end
27
+
28
+ describe '#queues' do
29
+ it 'raises NotImplementedError' do
30
+ expect { subject.queues }.to raise_error(NotImplementedError)
31
+ end
32
+ end
33
+
34
+ describe '#register_worker' do
35
+ it 'raises NotImplementedError' do
36
+ queue = 'test-queue'
37
+ worker_class = Class.new
38
+ expect { subject.register_worker(queue, worker_class) }.to raise_error(NotImplementedError)
39
+ end
40
+ end
41
+
42
+ describe '#workers' do
43
+ it 'raises NotImplementedError' do
44
+ expect { subject.workers('test-queue') }.to raise_error(NotImplementedError)
45
+ end
46
+ end
47
+
48
+ context 'interface documentation' do
49
+ it 'defines the required interface methods' do
50
+ expect(subject).to respond_to(:batch_receive_messages?)
51
+ expect(subject).to respond_to(:clear)
52
+ expect(subject).to respond_to(:fetch_worker)
53
+ expect(subject).to respond_to(:queues)
54
+ expect(subject).to respond_to(:register_worker)
55
+ expect(subject).to respond_to(:workers)
56
+ end
57
+
58
+ it 'is designed to be subclassed' do
59
+ expect(described_class).to be < Object
60
+ expect(described_class.ancestors).to include(described_class)
61
+ end
62
+ end
63
+ end
@@ -1,5 +1,5 @@
1
1
  require 'active_job'
2
- require 'shoryuken/extensions/active_job_extensions'
2
+ require 'active_job/extensions'
3
3
 
4
4
  # Stand-in for a job class specified by the user
5
5
  class TestJob < ActiveJob::Base; end
@@ -23,11 +23,11 @@ RSpec.shared_examples 'active_job_adapters' do
23
23
  specify do
24
24
  expect(queue).to receive(:send_message) do |hash|
25
25
  expect(hash[:message_deduplication_id]).to_not be
26
- expect(hash[:message_attributes]['shoryuken_class'][:string_value]).to eq(described_class::JobWrapper.to_s)
26
+ expect(hash[:message_attributes]['shoryuken_class'][:string_value]).to eq(Shoryuken::ActiveJob::JobWrapper.to_s)
27
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
- expect(Shoryuken).to receive(:register_worker).with(job.queue_name, described_class::JobWrapper)
30
+ expect(Shoryuken).to receive(:register_worker).with(job.queue_name, Shoryuken::ActiveJob::JobWrapper)
31
31
 
32
32
  subject.enqueue(job)
33
33
  end
@@ -50,7 +50,7 @@ RSpec.shared_examples 'active_job_adapters' do
50
50
 
51
51
  expect(hash[:message_deduplication_id]).to eq(message_deduplication_id)
52
52
  end
53
- expect(Shoryuken).to receive(:register_worker).with(job.queue_name, described_class::JobWrapper)
53
+ expect(Shoryuken).to receive(:register_worker).with(job.queue_name, Shoryuken::ActiveJob::JobWrapper)
54
54
 
55
55
  subject.enqueue(job)
56
56
  end
@@ -132,12 +132,12 @@ RSpec.shared_examples 'active_job_adapters' do
132
132
  }
133
133
 
134
134
  expect(queue).to receive(:send_message) do |hash|
135
- expect(hash[:message_attributes]['shoryuken_class'][:string_value]).to eq(described_class::JobWrapper.to_s)
135
+ expect(hash[:message_attributes]['shoryuken_class'][:string_value]).to eq(Shoryuken::ActiveJob::JobWrapper.to_s)
136
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
138
  expect(hash[:message_attributes]['tracer_id'][:data_type]).to eq('String')
139
139
  end
140
- expect(Shoryuken).to receive(:register_worker).with(job.queue_name, described_class::JobWrapper)
140
+ expect(Shoryuken).to receive(:register_worker).with(job.queue_name, Shoryuken::ActiveJob::JobWrapper)
141
141
 
142
142
  subject.enqueue(job, message_attributes: custom_message_attributes)
143
143
  end
@@ -158,7 +158,7 @@ RSpec.shared_examples 'active_job_adapters' 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
160
  expect(hash[:message_attributes]['shoryuken_class']).to eq({ data_type: 'String',
161
- string_value: described_class::JobWrapper.to_s })
161
+ string_value: Shoryuken::ActiveJob::JobWrapper.to_s })
162
162
  end
163
163
  subject.enqueue job
164
164
  end
@@ -189,7 +189,7 @@ RSpec.shared_examples 'active_job_adapters' do
189
189
  expect(hash[:message_attributes]['options_tracer_id']).to eq({ data_type: 'String',
190
190
  string_value: 'options-value' })
191
191
  expect(hash[:message_attributes]['shoryuken_class']).to eq({ data_type: 'String',
192
- string_value: described_class::JobWrapper.to_s })
192
+ string_value: Shoryuken::ActiveJob::JobWrapper.to_s })
193
193
  end
194
194
  subject.enqueue job, message_attributes: custom_message_attributes
195
195
  end
@@ -274,13 +274,33 @@ RSpec.shared_examples 'active_job_adapters' do
274
274
  expect(hash[:delay_seconds]).to eq(delay)
275
275
  end
276
276
 
277
- expect(Shoryuken).to receive(:register_worker).with(job.queue_name, described_class::JobWrapper)
277
+ expect(Shoryuken).to receive(:register_worker).with(job.queue_name, Shoryuken::ActiveJob::JobWrapper)
278
278
 
279
279
  # need to figure out what to require Time.current and N.minutes to remove the stub
280
280
  allow(subject).to receive(:calculate_delay).and_return(delay)
281
281
 
282
282
  subject.enqueue_at(job, nil)
283
283
  end
284
+
285
+ context 'when fifo' do
286
+ let(:fifo) { true }
287
+
288
+ it 'raises ArgumentError when delay is positive' do
289
+ allow(subject).to receive(:calculate_delay).and_return(3)
290
+ allow(queue).to receive(:name).and_return('test.fifo')
291
+ expect(queue).not_to receive(:send_message)
292
+
293
+ expect { subject.enqueue_at(job, nil) }.to raise_error(
294
+ ArgumentError, /FIFO queue.*does not support per-message delays/
295
+ )
296
+ end
297
+
298
+ it 'does not raise when delay is zero' do
299
+ allow(subject).to receive(:calculate_delay).and_return(0)
300
+ expect(queue).to receive(:send_message).with(hash_including(delay_seconds: 0))
301
+ expect { subject.enqueue_at(job, nil) }.not_to raise_error
302
+ end
303
+ end
284
304
  end
285
305
  end
286
306
  # rubocop:enable Metrics/BlockLength
data/spec/spec_helper.rb CHANGED
@@ -7,9 +7,20 @@ $VERBOSE = true
7
7
  require 'warning'
8
8
 
9
9
  Warning.process do |warning|
10
+ # Only check warnings from our code (not dependencies)
10
11
  next unless warning.include?(Dir.pwd)
11
- next if warning.include?('useless use of a variable in void context') && warning.include?('core_ext')
12
+
13
+ # Filter out warnings we don't care about in specs
14
+ next if warning.include?('_spec')
15
+
16
+ # We redefine methods to simulate various scenarios in tests
17
+ next if warning.include?('previous definition of')
18
+ next if warning.include?('method redefined')
19
+
20
+ # Ignore vendor and bundle directories
12
21
  next if warning.include?('vendor/')
22
+ next if warning.include?('bundle/')
23
+ next if warning.include?('.bundle/')
13
24
 
14
25
  raise "Warning in your code: #{warning}"
15
26
  end
@@ -29,8 +40,28 @@ require 'securerandom'
29
40
  require 'ostruct'
30
41
  Dotenv.load
31
42
 
32
- require 'simplecov'
33
- SimpleCov.start
43
+ unless ENV['SIMPLECOV_DISABLED']
44
+ require 'simplecov'
45
+ SimpleCov.start do
46
+ add_filter '/spec/'
47
+ add_filter '/test_workers/'
48
+ add_filter '/examples/'
49
+ add_filter '/vendor/'
50
+ add_filter '/.bundle/'
51
+
52
+ add_group 'Library', 'lib/'
53
+ add_group 'ActiveJob', 'lib/active_job'
54
+ add_group 'Middleware', 'lib/shoryuken/middleware'
55
+ add_group 'Polling', 'lib/shoryuken/polling'
56
+ add_group 'Workers', 'lib/shoryuken/worker'
57
+ add_group 'Helpers', 'lib/shoryuken/helpers'
58
+
59
+ enable_coverage :branch
60
+
61
+ minimum_coverage 89
62
+ minimum_coverage_by_file 60
63
+ end
64
+ end
34
65
 
35
66
  config_file = File.join(File.expand_path('..', __dir__), 'spec', 'shoryuken.yml')
36
67