shoryuken 6.2.1 → 7.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/push.yml +36 -0
  3. data/.github/workflows/specs.yml +49 -44
  4. data/.github/workflows/verify-action-pins.yml +16 -0
  5. data/.gitignore +4 -1
  6. data/.rspec +3 -1
  7. data/.rubocop.yml +6 -1
  8. data/.ruby-version +1 -0
  9. data/.yard-lint.yml +279 -0
  10. data/CHANGELOG.md +308 -139
  11. data/Gemfile +1 -8
  12. data/Gemfile.lint +9 -0
  13. data/Gemfile.lint.lock +69 -0
  14. data/README.md +16 -33
  15. data/Rakefile +6 -10
  16. data/bin/clean_sqs +52 -0
  17. data/bin/cli/base.rb +22 -2
  18. data/bin/cli/sqs.rb +74 -7
  19. data/bin/integrations +275 -0
  20. data/bin/scenario +154 -0
  21. data/bin/shoryuken +3 -2
  22. data/docker-compose.yml +6 -0
  23. data/lib/{shoryuken/extensions/active_job_extensions.rb → active_job/extensions.rb} +20 -6
  24. data/lib/active_job/queue_adapters/shoryuken_adapter.rb +208 -0
  25. data/lib/active_job/queue_adapters/shoryuken_concurrent_send_adapter.rb +78 -0
  26. data/lib/shoryuken/active_job/current_attributes.rb +139 -0
  27. data/lib/shoryuken/active_job/job_wrapper.rb +28 -0
  28. data/lib/shoryuken/body_parser.rb +11 -1
  29. data/lib/shoryuken/client.rb +16 -0
  30. data/lib/shoryuken/default_exception_handler.rb +11 -0
  31. data/lib/shoryuken/default_worker_registry.rb +39 -11
  32. data/lib/shoryuken/environment_loader.rb +85 -15
  33. data/lib/shoryuken/errors.rb +36 -0
  34. data/lib/shoryuken/fetcher.rb +41 -3
  35. data/lib/shoryuken/helpers/atomic_boolean.rb +58 -0
  36. data/lib/shoryuken/helpers/atomic_counter.rb +104 -0
  37. data/lib/shoryuken/helpers/atomic_hash.rb +182 -0
  38. data/lib/shoryuken/helpers/hash_utils.rb +56 -0
  39. data/lib/shoryuken/helpers/string_utils.rb +65 -0
  40. data/lib/shoryuken/helpers/timer_task.rb +80 -0
  41. data/lib/shoryuken/inline_message.rb +22 -0
  42. data/lib/shoryuken/launcher.rb +55 -0
  43. data/lib/shoryuken/logging/base.rb +26 -0
  44. data/lib/shoryuken/logging/pretty.rb +25 -0
  45. data/lib/shoryuken/logging/without_timestamp.rb +25 -0
  46. data/lib/shoryuken/logging.rb +43 -15
  47. data/lib/shoryuken/manager.rb +84 -5
  48. data/lib/shoryuken/message.rb +116 -1
  49. data/lib/shoryuken/middleware/chain.rb +141 -43
  50. data/lib/shoryuken/middleware/entry.rb +30 -0
  51. data/lib/shoryuken/middleware/server/active_record.rb +10 -0
  52. data/lib/shoryuken/middleware/server/auto_delete.rb +12 -0
  53. data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +37 -11
  54. data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +34 -3
  55. data/lib/shoryuken/middleware/server/non_retryable_exception.rb +95 -0
  56. data/lib/shoryuken/middleware/server/timing.rb +13 -0
  57. data/lib/shoryuken/options.rb +154 -13
  58. data/lib/shoryuken/polling/base_strategy.rb +127 -0
  59. data/lib/shoryuken/polling/queue_configuration.rb +103 -0
  60. data/lib/shoryuken/polling/strict_priority.rb +41 -0
  61. data/lib/shoryuken/polling/weighted_round_robin.rb +44 -0
  62. data/lib/shoryuken/processor.rb +37 -3
  63. data/lib/shoryuken/queue.rb +99 -8
  64. data/lib/shoryuken/runner.rb +54 -16
  65. data/lib/shoryuken/util.rb +32 -7
  66. data/lib/shoryuken/version.rb +4 -1
  67. data/lib/shoryuken/worker/default_executor.rb +23 -1
  68. data/lib/shoryuken/worker/inline_executor.rb +33 -2
  69. data/lib/shoryuken/worker.rb +224 -0
  70. data/lib/shoryuken/worker_registry.rb +35 -0
  71. data/lib/shoryuken.rb +27 -38
  72. data/renovate.json +62 -0
  73. data/shoryuken.gemspec +8 -4
  74. data/spec/integration/.rspec +1 -0
  75. data/spec/integration/active_job/adapter_configuration/configuration_spec.rb +26 -0
  76. data/spec/integration/active_job/bulk_enqueue/bulk_enqueue_spec.rb +53 -0
  77. data/spec/integration/active_job/current_attributes/bulk_enqueue_spec.rb +50 -0
  78. data/spec/integration/active_job/current_attributes/complex_types_spec.rb +55 -0
  79. data/spec/integration/active_job/current_attributes/empty_context_spec.rb +41 -0
  80. data/spec/integration/active_job/current_attributes/full_context_spec.rb +63 -0
  81. data/spec/integration/active_job/current_attributes/partial_context_spec.rb +57 -0
  82. data/spec/integration/active_job/custom_attributes/number_attributes_spec.rb +37 -0
  83. data/spec/integration/active_job/custom_attributes/string_attributes_spec.rb +39 -0
  84. data/spec/integration/active_job/error_handling/job_wrapper_spec.rb +53 -0
  85. data/spec/integration/active_job/fifo_and_attributes/deduplication_spec.rb +86 -0
  86. data/spec/integration/active_job/keyword_arguments/keyword_arguments_spec.rb +63 -0
  87. data/spec/integration/active_job/retry/discard_on_spec.rb +43 -0
  88. data/spec/integration/active_job/retry/retry_on_spec.rb +36 -0
  89. data/spec/integration/active_job/roundtrip/roundtrip_spec.rb +52 -0
  90. data/spec/integration/active_job/scheduled/scheduled_spec.rb +76 -0
  91. data/spec/integration/active_record_middleware/active_record_middleware_spec.rb +84 -0
  92. data/spec/integration/auto_delete/auto_delete_spec.rb +53 -0
  93. data/spec/integration/auto_extend_visibility/auto_extend_visibility_spec.rb +57 -0
  94. data/spec/integration/aws_config/aws_config_spec.rb +59 -0
  95. data/spec/integration/batch_processing/batch_processing_spec.rb +37 -0
  96. data/spec/integration/body_parser/json_parser_spec.rb +45 -0
  97. data/spec/integration/body_parser/proc_parser_spec.rb +54 -0
  98. data/spec/integration/body_parser/text_parser_spec.rb +43 -0
  99. data/spec/integration/concurrent_processing/concurrent_processing_spec.rb +45 -0
  100. data/spec/integration/custom_group_polling_strategy/custom_group_polling_strategy_spec.rb +87 -0
  101. data/spec/integration/dead_letter_queue/dead_letter_queue_spec.rb +91 -0
  102. data/spec/integration/exception_handlers/exception_handlers_spec.rb +69 -0
  103. data/spec/integration/exponential_backoff/exponential_backoff_spec.rb +67 -0
  104. data/spec/integration/fifo_ordering/fifo_ordering_spec.rb +44 -0
  105. data/spec/integration/large_payloads/large_payloads_spec.rb +30 -0
  106. data/spec/integration/launcher/launcher_spec.rb +40 -0
  107. data/spec/integration/message_attributes/message_attributes_spec.rb +54 -0
  108. data/spec/integration/message_operations/message_operations_spec.rb +59 -0
  109. data/spec/integration/middleware_chain/empty_chain_spec.rb +11 -0
  110. data/spec/integration/middleware_chain/execution_order_spec.rb +33 -0
  111. data/spec/integration/middleware_chain/removal_spec.rb +31 -0
  112. data/spec/integration/middleware_chain/short_circuit_spec.rb +40 -0
  113. data/spec/integration/non_retryable_exception/non_retryable_exception_spec.rb +149 -0
  114. data/spec/integration/polling_strategies/polling_strategies_spec.rb +46 -0
  115. data/spec/integration/queue_operations/queue_operations_spec.rb +84 -0
  116. data/spec/integration/rails/rails_72/Gemfile +6 -0
  117. data/spec/integration/rails/rails_72/activejob_adapter_spec.rb +98 -0
  118. data/spec/integration/rails/rails_80/Gemfile +6 -0
  119. data/spec/integration/rails/rails_80/activejob_adapter_spec.rb +98 -0
  120. data/spec/integration/rails/rails_80/continuation_spec.rb +79 -0
  121. data/spec/integration/rails/rails_81/Gemfile +6 -0
  122. data/spec/integration/rails/rails_81/activejob_adapter_spec.rb +98 -0
  123. data/spec/integration/rails/rails_81/continuation_spec.rb +79 -0
  124. data/spec/integration/retry_behavior/retry_behavior_spec.rb +45 -0
  125. data/spec/integration/spec_helper.rb +7 -0
  126. data/spec/integration/strict_priority_polling/strict_priority_polling_spec.rb +58 -0
  127. data/spec/integration/visibility_timeout/visibility_timeout_spec.rb +37 -0
  128. data/spec/integration/worker_enqueueing/worker_enqueueing_spec.rb +60 -0
  129. data/spec/integration/worker_groups/worker_groups_spec.rb +79 -0
  130. data/spec/integration/worker_lifecycle/worker_lifecycle_spec.rb +33 -0
  131. data/spec/integrations_helper.rb +243 -0
  132. data/spec/lib/active_job/extensions_spec.rb +225 -0
  133. data/spec/lib/active_job/queue_adapters/shoryuken_adapter_spec.rb +29 -0
  134. data/spec/{shoryuken/extensions/active_job_concurrent_send_adapter_spec.rb → lib/active_job/queue_adapters/shoryuken_concurrent_send_adapter_spec.rb} +5 -4
  135. data/spec/{shoryuken/extensions/active_job_wrapper_spec.rb → lib/shoryuken/active_job/job_wrapper_spec.rb} +6 -5
  136. data/spec/{shoryuken → lib/shoryuken}/body_parser_spec.rb +2 -4
  137. data/spec/{shoryuken → lib/shoryuken}/client_spec.rb +1 -1
  138. data/spec/{shoryuken → lib/shoryuken}/default_exception_handler_spec.rb +9 -10
  139. data/spec/{shoryuken → lib/shoryuken}/default_worker_registry_spec.rb +1 -2
  140. data/spec/{shoryuken → lib/shoryuken}/environment_loader_spec.rb +10 -9
  141. data/spec/{shoryuken → lib/shoryuken}/fetcher_spec.rb +23 -26
  142. data/spec/lib/shoryuken/helpers/atomic_boolean_spec.rb +196 -0
  143. data/spec/lib/shoryuken/helpers/atomic_counter_spec.rb +177 -0
  144. data/spec/lib/shoryuken/helpers/atomic_hash_spec.rb +307 -0
  145. data/spec/lib/shoryuken/helpers/hash_utils_spec.rb +145 -0
  146. data/spec/lib/shoryuken/helpers/string_utils_spec.rb +124 -0
  147. data/spec/lib/shoryuken/helpers/timer_task_spec.rb +298 -0
  148. data/spec/lib/shoryuken/helpers_integration_spec.rb +96 -0
  149. data/spec/lib/shoryuken/inline_message_spec.rb +196 -0
  150. data/spec/{shoryuken → lib/shoryuken}/launcher_spec.rb +23 -2
  151. data/spec/lib/shoryuken/logging_spec.rb +242 -0
  152. data/spec/{shoryuken → lib/shoryuken}/manager_spec.rb +1 -2
  153. data/spec/lib/shoryuken/message_spec.rb +109 -0
  154. data/spec/{shoryuken → lib/shoryuken}/middleware/chain_spec.rb +1 -1
  155. data/spec/lib/shoryuken/middleware/entry_spec.rb +68 -0
  156. data/spec/lib/shoryuken/middleware/server/active_record_spec.rb +133 -0
  157. data/spec/{shoryuken → lib/shoryuken}/middleware/server/auto_delete_spec.rb +1 -1
  158. data/spec/{shoryuken → lib/shoryuken}/middleware/server/auto_extend_visibility_spec.rb +51 -1
  159. data/spec/{shoryuken → lib/shoryuken}/middleware/server/exponential_backoff_retry_spec.rb +1 -1
  160. data/spec/lib/shoryuken/middleware/server/non_retryable_exception_spec.rb +214 -0
  161. data/spec/{shoryuken → lib/shoryuken}/middleware/server/timing_spec.rb +1 -1
  162. data/spec/{shoryuken → lib/shoryuken}/options_spec.rb +49 -6
  163. data/spec/lib/shoryuken/polling/base_strategy_spec.rb +280 -0
  164. data/spec/lib/shoryuken/polling/queue_configuration_spec.rb +195 -0
  165. data/spec/{shoryuken → lib/shoryuken}/polling/strict_priority_spec.rb +1 -1
  166. data/spec/{shoryuken → lib/shoryuken}/polling/weighted_round_robin_spec.rb +1 -1
  167. data/spec/{shoryuken → lib/shoryuken}/processor_spec.rb +1 -1
  168. data/spec/{shoryuken → lib/shoryuken}/queue_spec.rb +2 -3
  169. data/spec/{shoryuken → lib/shoryuken}/runner_spec.rb +1 -3
  170. data/spec/{shoryuken → lib/shoryuken}/util_spec.rb +2 -2
  171. data/spec/lib/shoryuken/version_spec.rb +17 -0
  172. data/spec/{shoryuken → lib/shoryuken}/worker/default_executor_spec.rb +1 -1
  173. data/spec/lib/shoryuken/worker/inline_executor_spec.rb +105 -0
  174. data/spec/lib/shoryuken/worker_registry_spec.rb +63 -0
  175. data/spec/{shoryuken → lib/shoryuken}/worker_spec.rb +15 -11
  176. data/spec/{shoryuken_spec.rb → lib/shoryuken_spec.rb} +1 -1
  177. data/spec/shared_examples_for_active_job.rb +40 -15
  178. data/spec/spec_helper.rb +48 -2
  179. metadata +295 -101
  180. data/.codeclimate.yml +0 -20
  181. data/.devcontainer/Dockerfile +0 -17
  182. data/.devcontainer/base.Dockerfile +0 -43
  183. data/.devcontainer/devcontainer.json +0 -35
  184. data/.github/FUNDING.yml +0 -12
  185. data/.github/dependabot.yml +0 -6
  186. data/.github/workflows/stale.yml +0 -20
  187. data/.reek.yml +0 -5
  188. data/Appraisals +0 -42
  189. data/gemfiles/.gitignore +0 -1
  190. data/gemfiles/aws_sdk_core_2.gemfile +0 -21
  191. data/gemfiles/rails_4_2.gemfile +0 -20
  192. data/gemfiles/rails_5_2.gemfile +0 -21
  193. data/gemfiles/rails_6_0.gemfile +0 -21
  194. data/gemfiles/rails_6_1.gemfile +0 -21
  195. data/gemfiles/rails_7_0.gemfile +0 -22
  196. data/lib/shoryuken/core_ext.rb +0 -69
  197. data/lib/shoryuken/extensions/active_job_adapter.rb +0 -103
  198. data/lib/shoryuken/extensions/active_job_concurrent_send_adapter.rb +0 -50
  199. data/lib/shoryuken/polling/base.rb +0 -67
  200. data/shoryuken.jpg +0 -0
  201. data/spec/integration/launcher_spec.rb +0 -128
  202. data/spec/shoryuken/core_ext_spec.rb +0 -40
  203. data/spec/shoryuken/extensions/active_job_adapter_spec.rb +0 -7
  204. data/spec/shoryuken/extensions/active_job_base_spec.rb +0 -84
  205. data/spec/shoryuken/worker/inline_executor_spec.rb +0 -49
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This spec tests the non_retryable_exception middleware functionality.
4
+ # When non_retryable_exceptions is configured, messages that raise those exceptions
5
+ # should be deleted immediately instead of being retried.
6
+
7
+ require 'timeout'
8
+
9
+ setup_sqs
10
+
11
+ # Reset DT state to prevent data leakage from previous tests
12
+ DT.clear
13
+
14
+ queue_name = DT.queue
15
+ create_test_queue(queue_name, attributes: { 'VisibilityTimeout' => '2' })
16
+ Shoryuken.add_group('default', 1)
17
+ Shoryuken.add_queue(queue_name, 1, 'default')
18
+
19
+ # Define custom exception classes for testing
20
+ InvalidInputError = Class.new(StandardError)
21
+ RecordNotFoundError = Class.new(StandardError)
22
+ RetryableError = Class.new(StandardError)
23
+
24
+ # Worker that handles non-retryable exceptions
25
+ non_retryable_worker = Class.new do
26
+ include Shoryuken::Worker
27
+
28
+ shoryuken_options auto_delete: false,
29
+ batch: false,
30
+ non_retryable_exceptions: [InvalidInputError, RecordNotFoundError]
31
+
32
+ def perform(sqs_msg, body)
33
+ receive_count = sqs_msg.attributes['ApproximateReceiveCount'].to_i
34
+ DT[:attempts] << { receive_count: receive_count, body: body }
35
+
36
+ case body
37
+ when 'non_retryable_invalid'
38
+ raise InvalidInputError, 'Invalid input data'
39
+ when 'non_retryable_not_found'
40
+ raise RecordNotFoundError, 'Record not found'
41
+ when 'retryable_error'
42
+ # Fail on first attempt, succeed on retry
43
+ if receive_count < 2
44
+ raise RetryableError, 'Temporary failure'
45
+ end
46
+ DT[:successful_processing] << { body: body, final_receive_count: receive_count }
47
+ sqs_msg.delete
48
+ when 'success'
49
+ DT[:successful_processing] << { body: body, final_receive_count: receive_count }
50
+ sqs_msg.delete
51
+ end
52
+ end
53
+ end
54
+
55
+ non_retryable_worker.get_shoryuken_options['queue'] = queue_name
56
+ Shoryuken.register_worker(queue_name, non_retryable_worker)
57
+
58
+ queue_url = Shoryuken::Client.sqs.get_queue_url(queue_name: queue_name).queue_url
59
+
60
+ # Start launcher once for all tests
61
+ launcher = Shoryuken::Launcher.new
62
+ launcher.start
63
+
64
+ begin
65
+ # Test 1: Non-retryable exception (InvalidInputError) should be deleted immediately
66
+ Shoryuken::Client.sqs.send_message(
67
+ queue_url: queue_url,
68
+ message_body: 'non_retryable_invalid'
69
+ )
70
+
71
+ # Wait for processing attempt
72
+ Timeout.timeout(10) { sleep 0.5 until DT[:attempts].size >= 1 }
73
+
74
+ # Verify it was only attempted once (not retried)
75
+ invalid_attempts = DT[:attempts].select { |a| a[:body] == 'non_retryable_invalid' }
76
+ assert_equal(1, invalid_attempts.size, 'Non-retryable exception should only be attempted once')
77
+ assert_equal(1, invalid_attempts.first[:receive_count], 'Should be first attempt')
78
+
79
+ # Wait a moment for deletion to complete
80
+ sleep 2
81
+
82
+ # Verify message was deleted - queue should be empty
83
+ attributes = Shoryuken::Client.sqs.get_queue_attributes(
84
+ queue_url: queue_url,
85
+ attribute_names: ['ApproximateNumberOfMessages', 'ApproximateNumberOfMessagesNotVisible']
86
+ ).attributes
87
+
88
+ total_messages = attributes['ApproximateNumberOfMessages'].to_i +
89
+ attributes['ApproximateNumberOfMessagesNotVisible'].to_i
90
+ assert_equal(0, total_messages, 'Message with non-retryable exception should be deleted immediately')
91
+
92
+ # Test 2: Another non-retryable exception (RecordNotFoundError) should also be deleted immediately
93
+ Shoryuken::Client.sqs.send_message(
94
+ queue_url: queue_url,
95
+ message_body: 'non_retryable_not_found'
96
+ )
97
+
98
+ # Wait for processing attempt
99
+ Timeout.timeout(10) { sleep 0.5 until DT[:attempts].size >= 2 }
100
+
101
+ # Verify it was only attempted once
102
+ not_found_attempts = DT[:attempts].select { |a| a[:body] == 'non_retryable_not_found' }
103
+ assert_equal(1, not_found_attempts.size, 'Non-retryable exception should only be attempted once')
104
+
105
+ # Wait for deletion
106
+ sleep 2
107
+
108
+ # Verify queue is empty again
109
+ attributes = Shoryuken::Client.sqs.get_queue_attributes(
110
+ queue_url: queue_url,
111
+ attribute_names: ['ApproximateNumberOfMessages', 'ApproximateNumberOfMessagesNotVisible']
112
+ ).attributes
113
+
114
+ total_messages = attributes['ApproximateNumberOfMessages'].to_i +
115
+ attributes['ApproximateNumberOfMessagesNotVisible'].to_i
116
+ assert_equal(0, total_messages, 'Message with non-retryable exception should be deleted immediately')
117
+
118
+ # Test 3: Retryable exception (not in non_retryable_exceptions list) should still retry
119
+ Shoryuken::Client.sqs.send_message(
120
+ queue_url: queue_url,
121
+ message_body: 'retryable_error'
122
+ )
123
+
124
+ # Wait for successful processing (after retry)
125
+ Timeout.timeout(15) { sleep 0.5 until DT[:successful_processing].size >= 1 }
126
+
127
+ # Verify it was retried
128
+ retryable_attempts = DT[:attempts].select { |a| a[:body] == 'retryable_error' }
129
+ assert(retryable_attempts.size >= 2, 'Retryable exception should be retried')
130
+ assert_equal(1, DT[:successful_processing].size, 'Message should eventually succeed after retry')
131
+ assert_equal('retryable_error', DT[:successful_processing].first[:body])
132
+
133
+ # Test 4: Successful message should process normally
134
+ Shoryuken::Client.sqs.send_message(
135
+ queue_url: queue_url,
136
+ message_body: 'success'
137
+ )
138
+
139
+ # Wait for successful processing
140
+ Timeout.timeout(10) { sleep 0.5 until DT[:successful_processing].size >= 2 }
141
+
142
+ # Verify successful processing
143
+ success_attempts = DT[:attempts].select { |a| a[:body] == 'success' }
144
+ assert_equal(1, success_attempts.size, 'Successful message should only be attempted once')
145
+ assert_equal(2, DT[:successful_processing].size, 'Both successful messages should be processed')
146
+ ensure
147
+ launcher.stop
148
+ end
149
+
@@ -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_sqs
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_sqs
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'
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ # ActiveJob adapter integration tests for Rails 8.1
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'])