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,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This spec tests SQS message attributes including String, Number, and Binary
4
+ # attribute types, system attributes (ApproximateReceiveCount, SentTimestamp),
5
+ # and custom type suffixes.
6
+
7
+ setup_localstack
8
+
9
+ queue_name = DT.queue
10
+ create_test_queue(queue_name)
11
+ Shoryuken.add_group('default', 1)
12
+ Shoryuken.add_queue(queue_name, 1, 'default')
13
+
14
+ worker_class = Class.new do
15
+ include Shoryuken::Worker
16
+
17
+ shoryuken_options auto_delete: true, batch: false
18
+
19
+ def perform(sqs_msg, body)
20
+ DT[:attributes] << sqs_msg.message_attributes
21
+ end
22
+ end
23
+
24
+ worker_class.get_shoryuken_options['queue'] = queue_name
25
+ Shoryuken.register_worker(queue_name, worker_class)
26
+
27
+ queue_url = Shoryuken::Client.sqs.get_queue_url(queue_name: queue_name).queue_url
28
+
29
+ Shoryuken::Client.sqs.send_message(
30
+ queue_url: queue_url,
31
+ message_body: 'mixed-attr-test',
32
+ message_attributes: {
33
+ 'StringAttr' => {
34
+ string_value: 'hello-world',
35
+ data_type: 'String'
36
+ },
37
+ 'NumberAttr' => {
38
+ string_value: '42',
39
+ data_type: 'Number'
40
+ },
41
+ 'BinaryAttr' => {
42
+ binary_value: 'binary-data'.b,
43
+ data_type: 'Binary'
44
+ }
45
+ }
46
+ )
47
+
48
+ poll_queues_until { DT[:attributes].size >= 1 }
49
+
50
+ attrs = DT[:attributes].first
51
+ assert_equal(3, attrs.keys.size)
52
+ assert_equal('hello-world', attrs['StringAttr']&.string_value)
53
+ assert_equal('42', attrs['NumberAttr']&.string_value)
54
+ assert_equal('binary-data'.b, attrs['BinaryAttr']&.binary_value)
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This spec tests message operations from within a worker:
4
+ # - sqs_msg.delete - manually delete a message
5
+ # - sqs_msg.change_visibility - change visibility with options
6
+
7
+ setup_localstack
8
+
9
+ queue_name = DT.queue
10
+ create_test_queue(queue_name, attributes: { 'VisibilityTimeout' => '5' })
11
+ Shoryuken.add_group('default', 1)
12
+ Shoryuken.add_queue(queue_name, 1, 'default')
13
+
14
+ # Worker that tests message operations
15
+ message_ops_worker = Class.new do
16
+ include Shoryuken::Worker
17
+
18
+ shoryuken_options auto_delete: false
19
+
20
+ def perform(sqs_msg, body)
21
+ DT[:processed] << { message_id: sqs_msg.message_id, body: body }
22
+
23
+ # Test sqs_msg.change_visibility method
24
+ sqs_msg.change_visibility(visibility_timeout: 60)
25
+ DT[:extended] << sqs_msg.message_id
26
+
27
+ # Manually delete the message
28
+ sqs_msg.delete
29
+ DT[:deleted] << sqs_msg.message_id
30
+ end
31
+ end
32
+
33
+ message_ops_worker.get_shoryuken_options['queue'] = queue_name
34
+ Shoryuken.register_worker(queue_name, message_ops_worker)
35
+
36
+ queue_url = Shoryuken::Client.sqs.get_queue_url(queue_name: queue_name).queue_url
37
+
38
+ # Test: Message operations - change_visibility and delete
39
+ Shoryuken::Client.sqs.send_message(
40
+ queue_url: queue_url,
41
+ message_body: 'message ops test'
42
+ )
43
+
44
+ sleep 1
45
+ poll_queues_until { DT[:deleted].size >= 1 }
46
+
47
+ # Verify message was processed
48
+ assert_equal(1, DT[:processed].size)
49
+ assert_equal('message ops test', DT[:processed].first[:body])
50
+
51
+ # Verify visibility was extended
52
+ assert_equal(1, DT[:extended].size, "Visibility should have been extended")
53
+
54
+ # Verify message was deleted
55
+ assert_equal(1, DT[:deleted].size, "Message should have been deleted")
56
+
57
+ # Verify message was deleted - should not be reprocessed
58
+ sleep 2
59
+ assert_equal(1, DT[:processed].size, "Deleted message should only be processed once")
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Empty middleware chain executes worker block directly
4
+
5
+ chain = Shoryuken::Middleware::Chain.new
6
+
7
+ chain.invoke(nil, 'test', nil, nil) do
8
+ DT[:calls] << :worker
9
+ end
10
+
11
+ assert_equal([:worker], DT[:calls])
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Middleware executes in onion model order (first-in wraps outermost)
4
+
5
+ def create_middleware(name)
6
+ Class.new do
7
+ define_method(:call) do |worker, queue, sqs_msg, body, &block|
8
+ DT[:order] << :"#{name}_before"
9
+ block.call
10
+ DT[:order] << :"#{name}_after"
11
+ end
12
+ end
13
+ end
14
+
15
+ first = create_middleware(:first)
16
+ second = create_middleware(:second)
17
+ third = create_middleware(:third)
18
+
19
+ chain = Shoryuken::Middleware::Chain.new
20
+ chain.add first
21
+ chain.add second
22
+ chain.add third
23
+
24
+ chain.invoke(nil, 'test-queue', nil, nil) do
25
+ DT[:order] << :worker_perform
26
+ end
27
+
28
+ expected_order = [
29
+ :first_before, :second_before, :third_before,
30
+ :worker_perform,
31
+ :third_after, :second_after, :first_after
32
+ ]
33
+ assert_equal(expected_order, DT[:order])
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Middleware can be removed from the chain
4
+
5
+ def create_middleware(name)
6
+ Class.new do
7
+ define_method(:call) do |worker, queue, sqs_msg, body, &block|
8
+ DT[:calls] << :"#{name}_before"
9
+ block.call
10
+ DT[:calls] << :"#{name}_after"
11
+ end
12
+ end
13
+ end
14
+
15
+ first = create_middleware(:first)
16
+ second = create_middleware(:second)
17
+ third = create_middleware(:third)
18
+
19
+ chain = Shoryuken::Middleware::Chain.new
20
+ chain.add first
21
+ chain.add second
22
+ chain.add third
23
+ chain.remove second
24
+
25
+ chain.invoke(nil, 'test', nil, nil) do
26
+ DT[:calls] << :worker
27
+ end
28
+
29
+ assert_includes(DT[:calls], :first_before)
30
+ refute(DT[:calls].include?(:second_before), "Second should be removed")
31
+ assert_includes(DT[:calls], :third_before)
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Middleware can short-circuit the chain by not calling the block
4
+
5
+ def create_middleware(name)
6
+ Class.new do
7
+ define_method(:call) do |worker, queue, sqs_msg, body, &block|
8
+ DT[:calls] << :"#{name}_before"
9
+ block.call
10
+ DT[:calls] << :"#{name}_after"
11
+ end
12
+ end
13
+ end
14
+
15
+ def create_short_circuit_middleware
16
+ Class.new do
17
+ define_method(:call) do |worker, queue, sqs_msg, body, &block|
18
+ DT[:calls] << :short_circuit
19
+ end
20
+ end
21
+ end
22
+
23
+ first = create_middleware(:first)
24
+ short_circuit = create_short_circuit_middleware
25
+ third = create_middleware(:third)
26
+
27
+ chain = Shoryuken::Middleware::Chain.new
28
+ chain.add first
29
+ chain.add short_circuit
30
+ chain.add third
31
+
32
+ chain.invoke(nil, 'test', nil, nil) do
33
+ DT[:calls] << :worker
34
+ end
35
+
36
+ assert_includes(DT[:calls], :first_before)
37
+ assert_includes(DT[:calls], :short_circuit)
38
+ refute(DT[:calls].include?(:third_before), "Third should not execute")
39
+ refute(DT[:calls].include?(:worker), "Worker should not execute")
40
+ assert_includes(DT[:calls], :first_after)
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This spec tests polling strategies including WeightedRoundRobin (default)
4
+ # with multi-queue worker message distribution.
5
+
6
+ setup_localstack
7
+
8
+ queue_high = DT.queues[0]
9
+ queue_medium = DT.queues[1]
10
+ queue_low = DT.queues[2]
11
+
12
+ [queue_high, queue_medium, queue_low].each { |q| create_test_queue(q) }
13
+
14
+ Shoryuken.add_group('default', 1)
15
+ # Higher weight = higher priority
16
+ Shoryuken.add_queue(queue_high, 3, 'default')
17
+ Shoryuken.add_queue(queue_medium, 2, 'default')
18
+ Shoryuken.add_queue(queue_low, 1, 'default')
19
+
20
+ worker_class = Class.new do
21
+ include Shoryuken::Worker
22
+
23
+ shoryuken_options auto_delete: true, batch: false
24
+
25
+ def perform(sqs_msg, body)
26
+ queue = sqs_msg.queue_url.split('/').last
27
+ DT[:by_queue] << { queue: queue, body: body }
28
+ end
29
+ end
30
+
31
+ [queue_high, queue_medium, queue_low].each do |queue|
32
+ worker_class.get_shoryuken_options['queue'] = queue
33
+ Shoryuken.register_worker(queue, worker_class)
34
+ end
35
+
36
+ Shoryuken::Client.queues(queue_high).send_message(message_body: 'high-msg')
37
+ Shoryuken::Client.queues(queue_medium).send_message(message_body: 'medium-msg')
38
+ Shoryuken::Client.queues(queue_low).send_message(message_body: 'low-msg')
39
+
40
+ sleep 1
41
+
42
+ poll_queues_until { DT[:by_queue].size >= 3 }
43
+
44
+ queues_with_messages = DT[:by_queue].map { |m| m[:queue] }.uniq
45
+ assert_equal(3, queues_with_messages.size)
46
+ assert_equal(3, DT[:by_queue].size)
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This spec tests Shoryuken::Queue operations including:
4
+ # - Queue initialization (by name, URL, and ARN)
5
+ # - Visibility timeout retrieval
6
+ # - FIFO queue detection
7
+ # - Batch message sending
8
+ # - Batch message deletion
9
+
10
+ setup_localstack
11
+
12
+ # Test 1: Queue initialization by name
13
+ queue_name = DT.uuid
14
+ create_test_queue(queue_name)
15
+
16
+ queue = Shoryuken::Queue.new(Shoryuken::Client.sqs, queue_name)
17
+ assert_equal(queue_name, queue.name)
18
+ assert(queue.url.include?(queue_name), "URL should contain queue name")
19
+ refute(queue.fifo?, "Standard queue should not be FIFO")
20
+
21
+ # Test 2: Queue initialization by URL
22
+ queue_url = queue.url
23
+ queue_by_url = Shoryuken::Queue.new(Shoryuken::Client.sqs, queue_url)
24
+ assert_equal(queue_name, queue_by_url.name)
25
+ assert_equal(queue_url, queue_by_url.url)
26
+
27
+ # Test 3: Visibility timeout retrieval
28
+ visibility_timeout = queue.visibility_timeout
29
+ assert(visibility_timeout.is_a?(Integer), "Visibility timeout should be an integer")
30
+ assert(visibility_timeout >= 0, "Visibility timeout should be non-negative")
31
+
32
+ # Test 4: FIFO queue detection
33
+ fifo_queue_name = "#{DT.uuid}.fifo"
34
+ create_fifo_queue(fifo_queue_name)
35
+
36
+ fifo_queue = Shoryuken::Queue.new(Shoryuken::Client.sqs, fifo_queue_name)
37
+ assert_equal(fifo_queue_name, fifo_queue.name)
38
+ assert(fifo_queue.fifo?, "FIFO queue should be detected as FIFO")
39
+
40
+ # Test 5: Send single message
41
+ send_result = queue.send_message(message_body: 'test message 1')
42
+ assert(send_result.message_id, "Send result should have message_id")
43
+
44
+ # Test 6: Send message with hash body (auto JSON serialization)
45
+ hash_body = { key: 'value', number: 42 }
46
+ send_result2 = queue.send_message(message_body: hash_body)
47
+ assert(send_result2.message_id, "Send result should have message_id for hash body")
48
+
49
+ # Test 7: Batch message sending
50
+ batch_result = queue.send_messages([
51
+ { message_body: 'batch msg 1' },
52
+ { message_body: 'batch msg 2' },
53
+ { message_body: 'batch msg 3' }
54
+ ])
55
+ assert_equal(3, batch_result.successful.size, "All 3 batch messages should succeed")
56
+
57
+ # Test 8: Receive messages
58
+ sleep 1 # Allow messages to become visible
59
+ received = queue.receive_messages(max_number_of_messages: 10)
60
+ assert(received.size > 0, "Should receive at least one message")
61
+ assert(received.first.is_a?(Shoryuken::Message), "Received items should be Message objects")
62
+
63
+ # Test 9: Batch message deletion
64
+ entries = received.map.with_index do |msg, idx|
65
+ { id: idx.to_s, receipt_handle: msg.receipt_handle }
66
+ end
67
+ delete_result = queue.delete_messages(entries: entries)
68
+ refute(delete_result, "Delete should succeed without failures")
69
+
70
+ # Test 10: FIFO queue message sending with auto-generated attributes
71
+ fifo_send_result = fifo_queue.send_message(message_body: 'fifo test message')
72
+ assert(fifo_send_result.message_id, "FIFO send should have message_id")
73
+ assert(fifo_send_result.sequence_number, "FIFO send should have sequence_number")
74
+
75
+ # Test 11: Send message with delay
76
+ delayed_result = queue.send_message(
77
+ message_body: 'delayed message',
78
+ delay_seconds: 5
79
+ )
80
+ assert(delayed_result.message_id, "Delayed message should have message_id")
81
+
82
+ # Cleanup
83
+ delete_test_queue(queue_name)
84
+ delete_test_queue(fifo_queue_name)
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec path: '../../../../'
4
+
5
+ gem 'activejob', '~> 8.0'
6
+ gem 'rails', '~> 8.0'
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ # ActiveJob adapter integration tests for Rails 7.2
4
+
5
+ setup_active_job
6
+
7
+ class EmailJob < ActiveJob::Base
8
+ queue_as :default
9
+
10
+ def perform(user_id, message)
11
+ { user_id: user_id, message: message, sent_at: Time.current }
12
+ end
13
+ end
14
+
15
+ class DataProcessingJob < ActiveJob::Base
16
+ queue_as :high_priority
17
+
18
+ def perform(data_file)
19
+ "Processed: #{data_file}"
20
+ end
21
+ end
22
+
23
+ class SerializationJob < ActiveJob::Base
24
+ queue_as :default
25
+
26
+ def perform(complex_data)
27
+ complex_data.transform_values(&:upcase)
28
+ end
29
+ end
30
+
31
+ # Test adapter setup
32
+ adapter = ActiveJob::Base.queue_adapter
33
+ assert_equal("ActiveJob::QueueAdapters::ShoryukenAdapter", adapter.class.name)
34
+
35
+ # Test singleton pattern
36
+ instance1 = ActiveJob::QueueAdapters::ShoryukenAdapter.instance
37
+ instance2 = ActiveJob::QueueAdapters::ShoryukenAdapter.instance
38
+ assert_equal(instance1.object_id, instance2.object_id)
39
+
40
+ # Test transaction commit hook (Rails 7.2+)
41
+ adapter_instance = ActiveJob::QueueAdapters::ShoryukenAdapter.new
42
+ assert(adapter_instance.respond_to?(:enqueue_after_transaction_commit?))
43
+ assert_equal(true, adapter_instance.enqueue_after_transaction_commit?)
44
+
45
+ # Test simple job enqueue
46
+ job_capture = JobCapture.new
47
+ job_capture.start_capturing
48
+
49
+ EmailJob.perform_later(1, 'Hello World')
50
+
51
+ assert_equal(1, job_capture.job_count)
52
+ job = job_capture.last_job
53
+ message_body = job[:message_body]
54
+ assert_equal('EmailJob', message_body['job_class'])
55
+ assert_equal([1, 'Hello World'], message_body['arguments'])
56
+ assert_equal('default', message_body['queue_name'])
57
+
58
+ # Test different queue
59
+ job_capture2 = JobCapture.new
60
+ job_capture2.start_capturing
61
+
62
+ DataProcessingJob.perform_later('large_dataset.csv')
63
+
64
+ job2 = job_capture2.last_job
65
+ message_body2 = job2[:message_body]
66
+ assert_equal('DataProcessingJob', message_body2['job_class'])
67
+ assert_equal('high_priority', message_body2['queue_name'])
68
+
69
+ # Test complex data serialization
70
+ complex_data = {
71
+ 'user' => { 'name' => 'John', 'age' => 30 },
72
+ 'preferences' => ['email', 'sms']
73
+ }
74
+
75
+ job_capture3 = JobCapture.new
76
+ job_capture3.start_capturing
77
+
78
+ SerializationJob.perform_later(complex_data)
79
+
80
+ job3 = job_capture3.last_job
81
+ message_body3 = job3[:message_body]
82
+ args_data = message_body3['arguments'].first
83
+ assert_equal('John', args_data['user']['name'])
84
+ assert_equal(30, args_data['user']['age'])
85
+
86
+ # Test shoryuken_class message attribute
87
+ job_capture4 = JobCapture.new
88
+ job_capture4.start_capturing
89
+
90
+ EmailJob.perform_later(1, 'Attributes test')
91
+
92
+ job4 = job_capture4.last_job
93
+ attributes = job4[:message_attributes]
94
+ expected_shoryuken_class = {
95
+ string_value: "Shoryuken::ActiveJob::JobWrapper",
96
+ data_type: 'String'
97
+ }
98
+ assert_equal(expected_shoryuken_class, attributes['shoryuken_class'])
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec path: '../../../../'
4
+
5
+ gem 'activejob', '~> 8.0'
6
+ gem 'rails', '~> 8.0'
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ # ActiveJob adapter integration tests for Rails 8.0
4
+
5
+ setup_active_job
6
+
7
+ class EmailJob < ActiveJob::Base
8
+ queue_as :default
9
+
10
+ def perform(user_id, message)
11
+ { user_id: user_id, message: message, sent_at: Time.current }
12
+ end
13
+ end
14
+
15
+ class DataProcessingJob < ActiveJob::Base
16
+ queue_as :high_priority
17
+
18
+ def perform(data_file)
19
+ "Processed: #{data_file}"
20
+ end
21
+ end
22
+
23
+ class SerializationJob < ActiveJob::Base
24
+ queue_as :default
25
+
26
+ def perform(complex_data)
27
+ complex_data.transform_values(&:upcase)
28
+ end
29
+ end
30
+
31
+ # Test adapter setup
32
+ adapter = ActiveJob::Base.queue_adapter
33
+ assert_equal("ActiveJob::QueueAdapters::ShoryukenAdapter", adapter.class.name)
34
+
35
+ # Test singleton pattern
36
+ instance1 = ActiveJob::QueueAdapters::ShoryukenAdapter.instance
37
+ instance2 = ActiveJob::QueueAdapters::ShoryukenAdapter.instance
38
+ assert_equal(instance1.object_id, instance2.object_id)
39
+
40
+ # Test transaction commit hook
41
+ adapter_instance = ActiveJob::QueueAdapters::ShoryukenAdapter.new
42
+ assert(adapter_instance.respond_to?(:enqueue_after_transaction_commit?))
43
+ assert_equal(true, adapter_instance.enqueue_after_transaction_commit?)
44
+
45
+ # Test simple job enqueue
46
+ job_capture = JobCapture.new
47
+ job_capture.start_capturing
48
+
49
+ EmailJob.perform_later(1, 'Hello World')
50
+
51
+ assert_equal(1, job_capture.job_count)
52
+ job = job_capture.last_job
53
+ message_body = job[:message_body]
54
+ assert_equal('EmailJob', message_body['job_class'])
55
+ assert_equal([1, 'Hello World'], message_body['arguments'])
56
+ assert_equal('default', message_body['queue_name'])
57
+
58
+ # Test different queue
59
+ job_capture2 = JobCapture.new
60
+ job_capture2.start_capturing
61
+
62
+ DataProcessingJob.perform_later('large_dataset.csv')
63
+
64
+ job2 = job_capture2.last_job
65
+ message_body2 = job2[:message_body]
66
+ assert_equal('DataProcessingJob', message_body2['job_class'])
67
+ assert_equal('high_priority', message_body2['queue_name'])
68
+
69
+ # Test complex data serialization
70
+ complex_data = {
71
+ 'user' => { 'name' => 'John', 'age' => 30 },
72
+ 'preferences' => ['email', 'sms']
73
+ }
74
+
75
+ job_capture3 = JobCapture.new
76
+ job_capture3.start_capturing
77
+
78
+ SerializationJob.perform_later(complex_data)
79
+
80
+ job3 = job_capture3.last_job
81
+ message_body3 = job3[:message_body]
82
+ args_data = message_body3['arguments'].first
83
+ assert_equal('John', args_data['user']['name'])
84
+ assert_equal(30, args_data['user']['age'])
85
+
86
+ # Test shoryuken_class message attribute
87
+ job_capture4 = JobCapture.new
88
+ job_capture4.start_capturing
89
+
90
+ EmailJob.perform_later(1, 'Attributes test')
91
+
92
+ job4 = job_capture4.last_job
93
+ attributes = job4[:message_attributes]
94
+ expected_shoryuken_class = {
95
+ string_value: "Shoryuken::ActiveJob::JobWrapper",
96
+ data_type: 'String'
97
+ }
98
+ assert_equal(expected_shoryuken_class, attributes['shoryuken_class'])
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ # ActiveJob Continuations integration tests for Rails 8.0+
4
+ # Tests the stopping? method and continuation timestamp handling
5
+
6
+ setup_active_job
7
+
8
+ # Skip if ActiveJob::Continuable is not available (Rails < 8.0)
9
+ unless defined?(ActiveJob::Continuable)
10
+ puts "Skipping continuation tests - ActiveJob::Continuable not available (requires Rails 8.0+)"
11
+ exit 0
12
+ end
13
+
14
+ # Test stopping? returns false when launcher is not initialized
15
+ adapter = ActiveJob::QueueAdapters::ShoryukenAdapter.new
16
+ assert_equal(false, adapter.stopping?)
17
+
18
+ # Test stopping? returns true when launcher is stopping
19
+ launcher = Shoryuken::Launcher.new
20
+ runner = Shoryuken::Runner.instance
21
+ runner.instance_variable_set(:@launcher, launcher)
22
+
23
+ adapter2 = ActiveJob::QueueAdapters::ShoryukenAdapter.new
24
+ assert_equal(false, adapter2.stopping?)
25
+
26
+ launcher.instance_variable_set(:@stopping, true)
27
+ assert_equal(true, adapter2.stopping?)
28
+
29
+ # Reset launcher state
30
+ launcher.instance_variable_set(:@stopping, false)
31
+
32
+ # Test past timestamps for continuation retries
33
+ job_capture = JobCapture.new
34
+ job_capture.start_capturing
35
+
36
+ class ContinuableTestJob < ActiveJob::Base
37
+ include ActiveJob::Continuable if defined?(ActiveJob::Continuable)
38
+ queue_as :default
39
+ def perform; end
40
+ end
41
+
42
+ adapter3 = ActiveJob::QueueAdapters::ShoryukenAdapter.new
43
+ job = ContinuableTestJob.new
44
+ job.sqs_send_message_parameters = {}
45
+
46
+ past_timestamp = Time.current.to_f - 60
47
+ adapter3.enqueue_at(job, past_timestamp)
48
+
49
+ captured_job = job_capture.last_job
50
+ assert(captured_job[:delay_seconds] <= 0, "Past timestamp should result in immediate delivery")
51
+
52
+ # Test current timestamp
53
+ job_capture2 = JobCapture.new
54
+ job_capture2.start_capturing
55
+
56
+ job2 = ContinuableTestJob.new
57
+ job2.sqs_send_message_parameters = {}
58
+
59
+ current_timestamp = Time.current.to_f
60
+ adapter3.enqueue_at(job2, current_timestamp)
61
+
62
+ captured_job2 = job_capture2.last_job
63
+ delay = captured_job2[:delay_seconds]
64
+ assert(delay >= -1 && delay <= 1, "Current timestamp should have minimal delay")
65
+
66
+ # Test future timestamp
67
+ job_capture3 = JobCapture.new
68
+ job_capture3.start_capturing
69
+
70
+ job3 = ContinuableTestJob.new
71
+ job3.sqs_send_message_parameters = {}
72
+
73
+ future_timestamp = Time.current.to_f + 30
74
+ adapter3.enqueue_at(job3, future_timestamp)
75
+
76
+ captured_job3 = job_capture3.last_job
77
+ delay3 = captured_job3[:delay_seconds]
78
+ assert(delay3 > 0, "Future timestamp should have positive delay")
79
+ assert(delay3 <= 30, "Delay should not exceed scheduled time")
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec path: '../../../../'
4
+
5
+ gem 'activejob', '~> 8.1'
6
+ gem 'rails', '~> 8.1'