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.
- checksums.yaml +4 -4
- data/.github/workflows/push.yml +36 -0
- data/.github/workflows/specs.yml +49 -44
- data/.github/workflows/verify-action-pins.yml +16 -0
- data/.gitignore +4 -1
- data/.rspec +3 -1
- data/.rubocop.yml +6 -1
- data/.ruby-version +1 -0
- data/.yard-lint.yml +279 -0
- data/CHANGELOG.md +308 -139
- data/Gemfile +1 -8
- data/Gemfile.lint +9 -0
- data/Gemfile.lint.lock +69 -0
- data/README.md +16 -33
- data/Rakefile +6 -10
- data/bin/clean_sqs +52 -0
- data/bin/cli/base.rb +22 -2
- data/bin/cli/sqs.rb +74 -7
- data/bin/integrations +275 -0
- data/bin/scenario +154 -0
- data/bin/shoryuken +3 -2
- data/docker-compose.yml +6 -0
- data/lib/{shoryuken/extensions/active_job_extensions.rb → active_job/extensions.rb} +20 -6
- data/lib/active_job/queue_adapters/shoryuken_adapter.rb +208 -0
- data/lib/active_job/queue_adapters/shoryuken_concurrent_send_adapter.rb +78 -0
- data/lib/shoryuken/active_job/current_attributes.rb +139 -0
- data/lib/shoryuken/active_job/job_wrapper.rb +28 -0
- data/lib/shoryuken/body_parser.rb +11 -1
- data/lib/shoryuken/client.rb +16 -0
- data/lib/shoryuken/default_exception_handler.rb +11 -0
- data/lib/shoryuken/default_worker_registry.rb +39 -11
- data/lib/shoryuken/environment_loader.rb +85 -15
- data/lib/shoryuken/errors.rb +36 -0
- data/lib/shoryuken/fetcher.rb +41 -3
- data/lib/shoryuken/helpers/atomic_boolean.rb +58 -0
- data/lib/shoryuken/helpers/atomic_counter.rb +104 -0
- data/lib/shoryuken/helpers/atomic_hash.rb +182 -0
- data/lib/shoryuken/helpers/hash_utils.rb +56 -0
- data/lib/shoryuken/helpers/string_utils.rb +65 -0
- data/lib/shoryuken/helpers/timer_task.rb +80 -0
- data/lib/shoryuken/inline_message.rb +22 -0
- data/lib/shoryuken/launcher.rb +55 -0
- data/lib/shoryuken/logging/base.rb +26 -0
- data/lib/shoryuken/logging/pretty.rb +25 -0
- data/lib/shoryuken/logging/without_timestamp.rb +25 -0
- data/lib/shoryuken/logging.rb +43 -15
- data/lib/shoryuken/manager.rb +84 -5
- data/lib/shoryuken/message.rb +116 -1
- data/lib/shoryuken/middleware/chain.rb +141 -43
- data/lib/shoryuken/middleware/entry.rb +30 -0
- data/lib/shoryuken/middleware/server/active_record.rb +10 -0
- data/lib/shoryuken/middleware/server/auto_delete.rb +12 -0
- data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +37 -11
- data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +34 -3
- data/lib/shoryuken/middleware/server/non_retryable_exception.rb +95 -0
- data/lib/shoryuken/middleware/server/timing.rb +13 -0
- data/lib/shoryuken/options.rb +154 -13
- data/lib/shoryuken/polling/base_strategy.rb +127 -0
- data/lib/shoryuken/polling/queue_configuration.rb +103 -0
- data/lib/shoryuken/polling/strict_priority.rb +41 -0
- data/lib/shoryuken/polling/weighted_round_robin.rb +44 -0
- data/lib/shoryuken/processor.rb +37 -3
- data/lib/shoryuken/queue.rb +99 -8
- data/lib/shoryuken/runner.rb +54 -16
- data/lib/shoryuken/util.rb +32 -7
- data/lib/shoryuken/version.rb +4 -1
- data/lib/shoryuken/worker/default_executor.rb +23 -1
- data/lib/shoryuken/worker/inline_executor.rb +33 -2
- data/lib/shoryuken/worker.rb +224 -0
- data/lib/shoryuken/worker_registry.rb +35 -0
- data/lib/shoryuken.rb +27 -38
- data/renovate.json +62 -0
- data/shoryuken.gemspec +8 -4
- data/spec/integration/.rspec +1 -0
- data/spec/integration/active_job/adapter_configuration/configuration_spec.rb +26 -0
- data/spec/integration/active_job/bulk_enqueue/bulk_enqueue_spec.rb +53 -0
- data/spec/integration/active_job/current_attributes/bulk_enqueue_spec.rb +50 -0
- data/spec/integration/active_job/current_attributes/complex_types_spec.rb +55 -0
- data/spec/integration/active_job/current_attributes/empty_context_spec.rb +41 -0
- data/spec/integration/active_job/current_attributes/full_context_spec.rb +63 -0
- data/spec/integration/active_job/current_attributes/partial_context_spec.rb +57 -0
- data/spec/integration/active_job/custom_attributes/number_attributes_spec.rb +37 -0
- data/spec/integration/active_job/custom_attributes/string_attributes_spec.rb +39 -0
- data/spec/integration/active_job/error_handling/job_wrapper_spec.rb +53 -0
- data/spec/integration/active_job/fifo_and_attributes/deduplication_spec.rb +86 -0
- data/spec/integration/active_job/keyword_arguments/keyword_arguments_spec.rb +63 -0
- data/spec/integration/active_job/retry/discard_on_spec.rb +43 -0
- data/spec/integration/active_job/retry/retry_on_spec.rb +36 -0
- data/spec/integration/active_job/roundtrip/roundtrip_spec.rb +52 -0
- data/spec/integration/active_job/scheduled/scheduled_spec.rb +76 -0
- data/spec/integration/active_record_middleware/active_record_middleware_spec.rb +84 -0
- data/spec/integration/auto_delete/auto_delete_spec.rb +53 -0
- data/spec/integration/auto_extend_visibility/auto_extend_visibility_spec.rb +57 -0
- data/spec/integration/aws_config/aws_config_spec.rb +59 -0
- data/spec/integration/batch_processing/batch_processing_spec.rb +37 -0
- data/spec/integration/body_parser/json_parser_spec.rb +45 -0
- data/spec/integration/body_parser/proc_parser_spec.rb +54 -0
- data/spec/integration/body_parser/text_parser_spec.rb +43 -0
- data/spec/integration/concurrent_processing/concurrent_processing_spec.rb +45 -0
- data/spec/integration/custom_group_polling_strategy/custom_group_polling_strategy_spec.rb +87 -0
- data/spec/integration/dead_letter_queue/dead_letter_queue_spec.rb +91 -0
- data/spec/integration/exception_handlers/exception_handlers_spec.rb +69 -0
- data/spec/integration/exponential_backoff/exponential_backoff_spec.rb +67 -0
- data/spec/integration/fifo_ordering/fifo_ordering_spec.rb +44 -0
- data/spec/integration/large_payloads/large_payloads_spec.rb +30 -0
- data/spec/integration/launcher/launcher_spec.rb +40 -0
- data/spec/integration/message_attributes/message_attributes_spec.rb +54 -0
- data/spec/integration/message_operations/message_operations_spec.rb +59 -0
- data/spec/integration/middleware_chain/empty_chain_spec.rb +11 -0
- data/spec/integration/middleware_chain/execution_order_spec.rb +33 -0
- data/spec/integration/middleware_chain/removal_spec.rb +31 -0
- data/spec/integration/middleware_chain/short_circuit_spec.rb +40 -0
- data/spec/integration/non_retryable_exception/non_retryable_exception_spec.rb +149 -0
- data/spec/integration/polling_strategies/polling_strategies_spec.rb +46 -0
- data/spec/integration/queue_operations/queue_operations_spec.rb +84 -0
- data/spec/integration/rails/rails_72/Gemfile +6 -0
- data/spec/integration/rails/rails_72/activejob_adapter_spec.rb +98 -0
- data/spec/integration/rails/rails_80/Gemfile +6 -0
- data/spec/integration/rails/rails_80/activejob_adapter_spec.rb +98 -0
- data/spec/integration/rails/rails_80/continuation_spec.rb +79 -0
- data/spec/integration/rails/rails_81/Gemfile +6 -0
- data/spec/integration/rails/rails_81/activejob_adapter_spec.rb +98 -0
- data/spec/integration/rails/rails_81/continuation_spec.rb +79 -0
- data/spec/integration/retry_behavior/retry_behavior_spec.rb +45 -0
- data/spec/integration/spec_helper.rb +7 -0
- data/spec/integration/strict_priority_polling/strict_priority_polling_spec.rb +58 -0
- data/spec/integration/visibility_timeout/visibility_timeout_spec.rb +37 -0
- data/spec/integration/worker_enqueueing/worker_enqueueing_spec.rb +60 -0
- data/spec/integration/worker_groups/worker_groups_spec.rb +79 -0
- data/spec/integration/worker_lifecycle/worker_lifecycle_spec.rb +33 -0
- data/spec/integrations_helper.rb +243 -0
- data/spec/lib/active_job/extensions_spec.rb +225 -0
- data/spec/lib/active_job/queue_adapters/shoryuken_adapter_spec.rb +29 -0
- data/spec/{shoryuken/extensions/active_job_concurrent_send_adapter_spec.rb → lib/active_job/queue_adapters/shoryuken_concurrent_send_adapter_spec.rb} +5 -4
- data/spec/{shoryuken/extensions/active_job_wrapper_spec.rb → lib/shoryuken/active_job/job_wrapper_spec.rb} +6 -5
- data/spec/{shoryuken → lib/shoryuken}/body_parser_spec.rb +2 -4
- data/spec/{shoryuken → lib/shoryuken}/client_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/default_exception_handler_spec.rb +9 -10
- data/spec/{shoryuken → lib/shoryuken}/default_worker_registry_spec.rb +1 -2
- data/spec/{shoryuken → lib/shoryuken}/environment_loader_spec.rb +10 -9
- data/spec/{shoryuken → lib/shoryuken}/fetcher_spec.rb +23 -26
- data/spec/lib/shoryuken/helpers/atomic_boolean_spec.rb +196 -0
- data/spec/lib/shoryuken/helpers/atomic_counter_spec.rb +177 -0
- data/spec/lib/shoryuken/helpers/atomic_hash_spec.rb +307 -0
- data/spec/lib/shoryuken/helpers/hash_utils_spec.rb +145 -0
- data/spec/lib/shoryuken/helpers/string_utils_spec.rb +124 -0
- data/spec/lib/shoryuken/helpers/timer_task_spec.rb +298 -0
- data/spec/lib/shoryuken/helpers_integration_spec.rb +96 -0
- data/spec/lib/shoryuken/inline_message_spec.rb +196 -0
- data/spec/{shoryuken → lib/shoryuken}/launcher_spec.rb +23 -2
- data/spec/lib/shoryuken/logging_spec.rb +242 -0
- data/spec/{shoryuken → lib/shoryuken}/manager_spec.rb +1 -2
- data/spec/lib/shoryuken/message_spec.rb +109 -0
- data/spec/{shoryuken → lib/shoryuken}/middleware/chain_spec.rb +1 -1
- data/spec/lib/shoryuken/middleware/entry_spec.rb +68 -0
- data/spec/lib/shoryuken/middleware/server/active_record_spec.rb +133 -0
- data/spec/{shoryuken → lib/shoryuken}/middleware/server/auto_delete_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/middleware/server/auto_extend_visibility_spec.rb +51 -1
- data/spec/{shoryuken → lib/shoryuken}/middleware/server/exponential_backoff_retry_spec.rb +1 -1
- data/spec/lib/shoryuken/middleware/server/non_retryable_exception_spec.rb +214 -0
- data/spec/{shoryuken → lib/shoryuken}/middleware/server/timing_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/options_spec.rb +49 -6
- data/spec/lib/shoryuken/polling/base_strategy_spec.rb +280 -0
- data/spec/lib/shoryuken/polling/queue_configuration_spec.rb +195 -0
- data/spec/{shoryuken → lib/shoryuken}/polling/strict_priority_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/polling/weighted_round_robin_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/processor_spec.rb +1 -1
- data/spec/{shoryuken → lib/shoryuken}/queue_spec.rb +2 -3
- data/spec/{shoryuken → lib/shoryuken}/runner_spec.rb +1 -3
- data/spec/{shoryuken → lib/shoryuken}/util_spec.rb +2 -2
- data/spec/lib/shoryuken/version_spec.rb +17 -0
- data/spec/{shoryuken → lib/shoryuken}/worker/default_executor_spec.rb +1 -1
- data/spec/lib/shoryuken/worker/inline_executor_spec.rb +105 -0
- data/spec/lib/shoryuken/worker_registry_spec.rb +63 -0
- data/spec/{shoryuken → lib/shoryuken}/worker_spec.rb +15 -11
- data/spec/{shoryuken_spec.rb → lib/shoryuken_spec.rb} +1 -1
- data/spec/shared_examples_for_active_job.rb +40 -15
- data/spec/spec_helper.rb +48 -2
- metadata +295 -101
- data/.codeclimate.yml +0 -20
- data/.devcontainer/Dockerfile +0 -17
- data/.devcontainer/base.Dockerfile +0 -43
- data/.devcontainer/devcontainer.json +0 -35
- data/.github/FUNDING.yml +0 -12
- data/.github/dependabot.yml +0 -6
- data/.github/workflows/stale.yml +0 -20
- data/.reek.yml +0 -5
- data/Appraisals +0 -42
- data/gemfiles/.gitignore +0 -1
- data/gemfiles/aws_sdk_core_2.gemfile +0 -21
- data/gemfiles/rails_4_2.gemfile +0 -20
- data/gemfiles/rails_5_2.gemfile +0 -21
- data/gemfiles/rails_6_0.gemfile +0 -21
- data/gemfiles/rails_6_1.gemfile +0 -21
- data/gemfiles/rails_7_0.gemfile +0 -22
- data/lib/shoryuken/core_ext.rb +0 -69
- data/lib/shoryuken/extensions/active_job_adapter.rb +0 -103
- data/lib/shoryuken/extensions/active_job_concurrent_send_adapter.rb +0 -50
- data/lib/shoryuken/polling/base.rb +0 -67
- data/shoryuken.jpg +0 -0
- data/spec/integration/launcher_spec.rb +0 -128
- data/spec/shoryuken/core_ext_spec.rb +0 -40
- data/spec/shoryuken/extensions/active_job_adapter_spec.rb +0 -7
- data/spec/shoryuken/extensions/active_job_base_spec.rb +0 -84
- data/spec/shoryuken/worker/inline_executor_spec.rb +0 -49
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# CurrentAttributes with complex data types (hashes, arrays, symbols) are serialized and restored
|
|
4
|
+
|
|
5
|
+
setup_sqs
|
|
6
|
+
setup_active_job
|
|
7
|
+
|
|
8
|
+
require 'active_support/current_attributes'
|
|
9
|
+
require 'shoryuken/active_job/current_attributes'
|
|
10
|
+
|
|
11
|
+
queue_name = DT.queue
|
|
12
|
+
create_test_queue(queue_name)
|
|
13
|
+
|
|
14
|
+
class TestCurrent < ActiveSupport::CurrentAttributes
|
|
15
|
+
attribute :user_id, :tenant_id
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
Shoryuken::ActiveJob::CurrentAttributes.persist(TestCurrent)
|
|
19
|
+
|
|
20
|
+
class ComplexTypesTestJob < ActiveJob::Base
|
|
21
|
+
def perform
|
|
22
|
+
DT[:executions] << {
|
|
23
|
+
user_id: TestCurrent.user_id,
|
|
24
|
+
tenant_id: TestCurrent.tenant_id
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
ComplexTypesTestJob.queue_as(queue_name)
|
|
30
|
+
|
|
31
|
+
Shoryuken.add_group('default', 1)
|
|
32
|
+
Shoryuken.add_queue(queue_name, 1, 'default')
|
|
33
|
+
Shoryuken.register_worker(queue_name, Shoryuken::ActiveJob::JobWrapper)
|
|
34
|
+
|
|
35
|
+
TestCurrent.user_id = { role: :admin, permissions: [:read, :write, :delete] }
|
|
36
|
+
TestCurrent.tenant_id = [:tenant_a, :tenant_b]
|
|
37
|
+
|
|
38
|
+
ComplexTypesTestJob.perform_later
|
|
39
|
+
|
|
40
|
+
TestCurrent.reset
|
|
41
|
+
|
|
42
|
+
poll_queues_until(timeout: 30) { DT[:executions].size >= 1 }
|
|
43
|
+
|
|
44
|
+
result = DT[:executions].first
|
|
45
|
+
|
|
46
|
+
user_data = result[:user_id]
|
|
47
|
+
assert(user_data.is_a?(Hash))
|
|
48
|
+
role = user_data['role'] || user_data[:role]
|
|
49
|
+
assert_equal('admin', role.to_s)
|
|
50
|
+
permissions = user_data['permissions'] || user_data[:permissions]
|
|
51
|
+
assert_equal(3, permissions.size)
|
|
52
|
+
|
|
53
|
+
tenant_data = result[:tenant_id]
|
|
54
|
+
assert(tenant_data.is_a?(Array))
|
|
55
|
+
assert_equal(2, tenant_data.size)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# CurrentAttributes without any values set result in nil attributes during job execution
|
|
4
|
+
|
|
5
|
+
setup_sqs
|
|
6
|
+
setup_active_job
|
|
7
|
+
|
|
8
|
+
require 'active_support/current_attributes'
|
|
9
|
+
require 'shoryuken/active_job/current_attributes'
|
|
10
|
+
|
|
11
|
+
queue_name = DT.queue
|
|
12
|
+
create_test_queue(queue_name)
|
|
13
|
+
|
|
14
|
+
class TestCurrent < ActiveSupport::CurrentAttributes
|
|
15
|
+
attribute :user_id, :tenant_id
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
Shoryuken::ActiveJob::CurrentAttributes.persist(TestCurrent)
|
|
19
|
+
|
|
20
|
+
class EmptyContextTestJob < ActiveJob::Base
|
|
21
|
+
def perform
|
|
22
|
+
DT[:executions] << {
|
|
23
|
+
user_id: TestCurrent.user_id,
|
|
24
|
+
tenant_id: TestCurrent.tenant_id
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
EmptyContextTestJob.queue_as(queue_name)
|
|
30
|
+
|
|
31
|
+
Shoryuken.add_group('default', 1)
|
|
32
|
+
Shoryuken.add_queue(queue_name, 1, 'default')
|
|
33
|
+
Shoryuken.register_worker(queue_name, Shoryuken::ActiveJob::JobWrapper)
|
|
34
|
+
|
|
35
|
+
EmptyContextTestJob.perform_later
|
|
36
|
+
|
|
37
|
+
poll_queues_until(timeout: 30) { DT[:executions].size >= 1 }
|
|
38
|
+
|
|
39
|
+
result = DT[:executions].first
|
|
40
|
+
assert(result[:user_id].nil?)
|
|
41
|
+
assert(result[:tenant_id].nil?)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# CurrentAttributes with full context are persisted and restored during job execution
|
|
4
|
+
|
|
5
|
+
setup_sqs
|
|
6
|
+
setup_active_job
|
|
7
|
+
|
|
8
|
+
require 'active_support/current_attributes'
|
|
9
|
+
require 'shoryuken/active_job/current_attributes'
|
|
10
|
+
|
|
11
|
+
queue_name = DT.queue
|
|
12
|
+
create_test_queue(queue_name)
|
|
13
|
+
|
|
14
|
+
class TestCurrent < ActiveSupport::CurrentAttributes
|
|
15
|
+
attribute :user_id, :tenant_id, :request_id
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class RequestContext < ActiveSupport::CurrentAttributes
|
|
19
|
+
attribute :locale, :timezone, :trace_id
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
Shoryuken::ActiveJob::CurrentAttributes.persist(TestCurrent, RequestContext)
|
|
23
|
+
|
|
24
|
+
class FullContextTestJob < ActiveJob::Base
|
|
25
|
+
def perform
|
|
26
|
+
DT[:executions] << {
|
|
27
|
+
user_id: TestCurrent.user_id,
|
|
28
|
+
tenant_id: TestCurrent.tenant_id,
|
|
29
|
+
request_id: TestCurrent.request_id,
|
|
30
|
+
locale: RequestContext.locale,
|
|
31
|
+
timezone: RequestContext.timezone,
|
|
32
|
+
trace_id: RequestContext.trace_id
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
FullContextTestJob.queue_as(queue_name)
|
|
38
|
+
|
|
39
|
+
Shoryuken.add_group('default', 1)
|
|
40
|
+
Shoryuken.add_queue(queue_name, 1, 'default')
|
|
41
|
+
Shoryuken.register_worker(queue_name, Shoryuken::ActiveJob::JobWrapper)
|
|
42
|
+
|
|
43
|
+
TestCurrent.user_id = 42
|
|
44
|
+
TestCurrent.tenant_id = 'acme-corp'
|
|
45
|
+
TestCurrent.request_id = 'req-123-abc'
|
|
46
|
+
RequestContext.locale = 'en-US'
|
|
47
|
+
RequestContext.timezone = 'America/New_York'
|
|
48
|
+
RequestContext.trace_id = 'trace-xyz-789'
|
|
49
|
+
|
|
50
|
+
FullContextTestJob.perform_later
|
|
51
|
+
|
|
52
|
+
TestCurrent.reset
|
|
53
|
+
RequestContext.reset
|
|
54
|
+
|
|
55
|
+
poll_queues_until(timeout: 30) { DT[:executions].size >= 1 }
|
|
56
|
+
|
|
57
|
+
result = DT[:executions].first
|
|
58
|
+
assert_equal(42, result[:user_id])
|
|
59
|
+
assert_equal('acme-corp', result[:tenant_id])
|
|
60
|
+
assert_equal('req-123-abc', result[:request_id])
|
|
61
|
+
assert_equal('en-US', result[:locale])
|
|
62
|
+
assert_equal('America/New_York', result[:timezone])
|
|
63
|
+
assert_equal('trace-xyz-789', result[:trace_id])
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# CurrentAttributes with partial values set preserve only set attributes during job execution
|
|
4
|
+
|
|
5
|
+
setup_sqs
|
|
6
|
+
setup_active_job
|
|
7
|
+
|
|
8
|
+
require 'active_support/current_attributes'
|
|
9
|
+
require 'shoryuken/active_job/current_attributes'
|
|
10
|
+
|
|
11
|
+
queue_name = DT.queue
|
|
12
|
+
create_test_queue(queue_name)
|
|
13
|
+
|
|
14
|
+
class TestCurrent < ActiveSupport::CurrentAttributes
|
|
15
|
+
attribute :user_id, :tenant_id, :request_id
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class RequestContext < ActiveSupport::CurrentAttributes
|
|
19
|
+
attribute :locale, :timezone
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
Shoryuken::ActiveJob::CurrentAttributes.persist(TestCurrent, RequestContext)
|
|
23
|
+
|
|
24
|
+
class PartialContextTestJob < ActiveJob::Base
|
|
25
|
+
def perform
|
|
26
|
+
DT[:executions] << {
|
|
27
|
+
user_id: TestCurrent.user_id,
|
|
28
|
+
tenant_id: TestCurrent.tenant_id,
|
|
29
|
+
request_id: TestCurrent.request_id,
|
|
30
|
+
locale: RequestContext.locale,
|
|
31
|
+
timezone: RequestContext.timezone
|
|
32
|
+
}
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
PartialContextTestJob.queue_as(queue_name)
|
|
37
|
+
|
|
38
|
+
Shoryuken.add_group('default', 1)
|
|
39
|
+
Shoryuken.add_queue(queue_name, 1, 'default')
|
|
40
|
+
Shoryuken.register_worker(queue_name, Shoryuken::ActiveJob::JobWrapper)
|
|
41
|
+
|
|
42
|
+
TestCurrent.user_id = 99
|
|
43
|
+
RequestContext.locale = 'fr-FR'
|
|
44
|
+
|
|
45
|
+
PartialContextTestJob.perform_later
|
|
46
|
+
|
|
47
|
+
TestCurrent.reset
|
|
48
|
+
RequestContext.reset
|
|
49
|
+
|
|
50
|
+
poll_queues_until(timeout: 30) { DT[:executions].size >= 1 }
|
|
51
|
+
|
|
52
|
+
result = DT[:executions].first
|
|
53
|
+
assert_equal(99, result[:user_id])
|
|
54
|
+
assert(result[:tenant_id].nil?)
|
|
55
|
+
assert(result[:request_id].nil?)
|
|
56
|
+
assert_equal('fr-FR', result[:locale])
|
|
57
|
+
assert(result[:timezone].nil?)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# ActiveJob custom numeric message attributes are sent to SQS with correct data type
|
|
4
|
+
|
|
5
|
+
setup_sqs
|
|
6
|
+
setup_active_job
|
|
7
|
+
|
|
8
|
+
queue_name = DT.queue
|
|
9
|
+
create_test_queue(queue_name)
|
|
10
|
+
|
|
11
|
+
class NumberAttributesTestJob < ActiveJob::Base
|
|
12
|
+
def perform
|
|
13
|
+
DT[:executions] << { job_id: job_id }
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
NumberAttributesTestJob.queue_as(queue_name)
|
|
18
|
+
|
|
19
|
+
Shoryuken.add_group('default', 1)
|
|
20
|
+
Shoryuken.add_queue(queue_name, 1, 'default')
|
|
21
|
+
Shoryuken.register_worker(queue_name, Shoryuken::ActiveJob::JobWrapper)
|
|
22
|
+
|
|
23
|
+
job = NumberAttributesTestJob.new
|
|
24
|
+
job.sqs_send_message_parameters = {
|
|
25
|
+
message_attributes: {
|
|
26
|
+
'priority' => { string_value: '10', data_type: 'Number' },
|
|
27
|
+
'retry_count' => { string_value: '0', data_type: 'Number' }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
ActiveJob::QueueAdapters::ShoryukenAdapter.enqueue(job)
|
|
31
|
+
|
|
32
|
+
poll_queues_until(timeout: 30) { DT[:executions].size >= 1 }
|
|
33
|
+
|
|
34
|
+
params = job.sqs_send_message_parameters
|
|
35
|
+
assert(params[:message_attributes].key?('priority'))
|
|
36
|
+
assert_equal('10', params[:message_attributes]['priority'][:string_value])
|
|
37
|
+
assert_equal('Number', params[:message_attributes]['priority'][:data_type])
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# ActiveJob custom string message attributes are sent to SQS and preserved
|
|
4
|
+
|
|
5
|
+
setup_sqs
|
|
6
|
+
setup_active_job
|
|
7
|
+
|
|
8
|
+
queue_name = DT.queue
|
|
9
|
+
create_test_queue(queue_name)
|
|
10
|
+
|
|
11
|
+
class StringAttributesTestJob < ActiveJob::Base
|
|
12
|
+
def perform
|
|
13
|
+
DT[:executions] << { job_id: job_id }
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
StringAttributesTestJob.queue_as(queue_name)
|
|
18
|
+
|
|
19
|
+
Shoryuken.add_group('default', 1)
|
|
20
|
+
Shoryuken.add_queue(queue_name, 1, 'default')
|
|
21
|
+
Shoryuken.register_worker(queue_name, Shoryuken::ActiveJob::JobWrapper)
|
|
22
|
+
|
|
23
|
+
job = StringAttributesTestJob.new
|
|
24
|
+
job.sqs_send_message_parameters = {
|
|
25
|
+
message_attributes: {
|
|
26
|
+
'trace_id' => { string_value: 'trace-abc-123', data_type: 'String' },
|
|
27
|
+
'correlation_id' => { string_value: 'corr-xyz-789', data_type: 'String' }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
ActiveJob::QueueAdapters::ShoryukenAdapter.enqueue(job)
|
|
31
|
+
|
|
32
|
+
poll_queues_until(timeout: 30) { DT[:executions].size >= 1 }
|
|
33
|
+
|
|
34
|
+
params = job.sqs_send_message_parameters
|
|
35
|
+
assert(params[:message_attributes].key?('trace_id'))
|
|
36
|
+
assert(params[:message_attributes].key?('correlation_id'))
|
|
37
|
+
assert(params[:message_attributes].key?('shoryuken_class'))
|
|
38
|
+
assert_equal('trace-abc-123', params[:message_attributes]['trace_id'][:string_value])
|
|
39
|
+
assert_equal('corr-xyz-789', params[:message_attributes]['correlation_id'][:string_value])
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# This spec tests error handling including retry configuration,
|
|
4
|
+
# discard configuration, and job processing through JobWrapper.
|
|
5
|
+
|
|
6
|
+
setup_active_job
|
|
7
|
+
|
|
8
|
+
class RetryableJob < ActiveJob::Base
|
|
9
|
+
queue_as :default
|
|
10
|
+
retry_on StandardError, wait: 1.second, attempts: 3
|
|
11
|
+
|
|
12
|
+
def perform(should_fail = true)
|
|
13
|
+
raise StandardError, 'Job failed!' if should_fail
|
|
14
|
+
'Job succeeded!'
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class DiscardableJob < ActiveJob::Base
|
|
19
|
+
queue_as :default
|
|
20
|
+
discard_on ArgumentError
|
|
21
|
+
|
|
22
|
+
def perform(should_fail = false)
|
|
23
|
+
raise ArgumentError, 'Invalid argument' if should_fail
|
|
24
|
+
'Job succeeded!'
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
job_capture = JobCapture.new
|
|
29
|
+
job_capture.start_capturing
|
|
30
|
+
|
|
31
|
+
RetryableJob.perform_later(false)
|
|
32
|
+
|
|
33
|
+
assert_equal(1, job_capture.job_count)
|
|
34
|
+
job = job_capture.last_job
|
|
35
|
+
message_body = job[:message_body]
|
|
36
|
+
assert_equal('RetryableJob', message_body['job_class'])
|
|
37
|
+
assert_equal([false], message_body['arguments'])
|
|
38
|
+
|
|
39
|
+
job_capture2 = JobCapture.new
|
|
40
|
+
job_capture2.start_capturing
|
|
41
|
+
|
|
42
|
+
DiscardableJob.perform_later(false)
|
|
43
|
+
|
|
44
|
+
assert_equal(1, job_capture2.job_count)
|
|
45
|
+
job2 = job_capture2.last_job
|
|
46
|
+
message_body2 = job2[:message_body]
|
|
47
|
+
assert_equal('DiscardableJob', message_body2['job_class'])
|
|
48
|
+
|
|
49
|
+
wrapper_class = Shoryuken::ActiveJob::JobWrapper
|
|
50
|
+
options = wrapper_class.get_shoryuken_options
|
|
51
|
+
|
|
52
|
+
assert_equal(:json, options['body_parser'])
|
|
53
|
+
assert_equal(true, options['auto_delete'])
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'digest'
|
|
4
|
+
|
|
5
|
+
# This spec tests FIFO queue support including message deduplication ID generation
|
|
6
|
+
# and message attributes handling.
|
|
7
|
+
|
|
8
|
+
setup_active_job
|
|
9
|
+
|
|
10
|
+
class FifoTestJob < ActiveJob::Base
|
|
11
|
+
queue_as :test_fifo
|
|
12
|
+
|
|
13
|
+
def perform(order_id, action)
|
|
14
|
+
"Processed order #{order_id}: #{action}"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class AttributesTestJob < ActiveJob::Base
|
|
19
|
+
queue_as :attributes_test
|
|
20
|
+
|
|
21
|
+
def perform(data)
|
|
22
|
+
"Processed: #{data}"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
fifo_queue_mock = Object.new
|
|
27
|
+
fifo_queue_mock.define_singleton_method(:fifo?) { true }
|
|
28
|
+
fifo_queue_mock.define_singleton_method(:name) { 'test_fifo.fifo' }
|
|
29
|
+
|
|
30
|
+
captured_params = nil
|
|
31
|
+
fifo_queue_mock.define_singleton_method(:send_message) do |params|
|
|
32
|
+
captured_params = params
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
Shoryuken::Client.define_singleton_method(:queues) do |queue_name = nil|
|
|
36
|
+
if queue_name
|
|
37
|
+
fifo_queue_mock
|
|
38
|
+
else
|
|
39
|
+
{ test_fifo: fifo_queue_mock }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
Shoryuken.define_singleton_method(:register_worker) { |*args| nil }
|
|
44
|
+
|
|
45
|
+
FifoTestJob.perform_later('order-123', 'process')
|
|
46
|
+
|
|
47
|
+
assert(captured_params.key?(:message_deduplication_id))
|
|
48
|
+
assert_equal(64, captured_params[:message_deduplication_id].length)
|
|
49
|
+
|
|
50
|
+
body = captured_params[:message_body]
|
|
51
|
+
body_without_variable_fields = body.except('job_id', 'enqueued_at')
|
|
52
|
+
expected_dedupe_id = Digest::SHA256.hexdigest(JSON.dump(body_without_variable_fields))
|
|
53
|
+
assert_equal(expected_dedupe_id, captured_params[:message_deduplication_id])
|
|
54
|
+
|
|
55
|
+
regular_queue_mock = Object.new
|
|
56
|
+
regular_queue_mock.define_singleton_method(:fifo?) { false }
|
|
57
|
+
regular_queue_mock.define_singleton_method(:name) { 'attributes_test' }
|
|
58
|
+
|
|
59
|
+
captured_attrs = nil
|
|
60
|
+
regular_queue_mock.define_singleton_method(:send_message) do |params|
|
|
61
|
+
captured_attrs = params
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
Shoryuken::Client.define_singleton_method(:queues) do |queue_name = nil|
|
|
65
|
+
regular_queue_mock
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
custom_attributes = {
|
|
69
|
+
'trace_id' => { string_value: 'trace-123', data_type: 'String' },
|
|
70
|
+
'priority' => { string_value: 'high', data_type: 'String' }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
job = AttributesTestJob.new('test data')
|
|
74
|
+
job.sqs_send_message_parameters = { message_attributes: custom_attributes }
|
|
75
|
+
ActiveJob::QueueAdapters::ShoryukenAdapter.enqueue(job)
|
|
76
|
+
|
|
77
|
+
attributes = captured_attrs[:message_attributes]
|
|
78
|
+
assert_equal(custom_attributes['trace_id'], attributes['trace_id'])
|
|
79
|
+
assert_equal(custom_attributes['priority'], attributes['priority'])
|
|
80
|
+
|
|
81
|
+
# Should still include required Shoryuken attribute
|
|
82
|
+
expected_shoryuken_class = {
|
|
83
|
+
string_value: "Shoryuken::ActiveJob::JobWrapper",
|
|
84
|
+
data_type: 'String'
|
|
85
|
+
}
|
|
86
|
+
assert_equal(expected_shoryuken_class, attributes['shoryuken_class'])
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Integration test for ActiveJob keyword arguments support
|
|
4
|
+
# Regression test for: https://github.com/ruby-shoryuken/shoryuken/issues/961
|
|
5
|
+
#
|
|
6
|
+
# In Shoryuken 7.0, the SQSSendMessageParametersSupport module's initialize method
|
|
7
|
+
# breaks keyword argument passing to ActiveJob jobs because it lacks ruby2_keywords.
|
|
8
|
+
|
|
9
|
+
setup_sqs
|
|
10
|
+
setup_active_job
|
|
11
|
+
|
|
12
|
+
queue_name = DT.queue
|
|
13
|
+
create_test_queue(queue_name)
|
|
14
|
+
|
|
15
|
+
# Job that accepts keyword arguments - this was broken in Shoryuken 7.0
|
|
16
|
+
class KeywordArgumentsTestJob < ActiveJob::Base
|
|
17
|
+
def perform(name:, count:, enabled: false)
|
|
18
|
+
DT[:executions] << {
|
|
19
|
+
name: name,
|
|
20
|
+
count: count,
|
|
21
|
+
enabled: enabled,
|
|
22
|
+
job_id: job_id
|
|
23
|
+
}
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
KeywordArgumentsTestJob.queue_as(queue_name)
|
|
28
|
+
|
|
29
|
+
Shoryuken.add_group('default', 1)
|
|
30
|
+
Shoryuken.add_queue(queue_name, 1, 'default')
|
|
31
|
+
Shoryuken.register_worker(queue_name, Shoryuken::ActiveJob::JobWrapper)
|
|
32
|
+
|
|
33
|
+
# Enqueue jobs with keyword arguments
|
|
34
|
+
# This is where the bug manifests - the job instantiation fails
|
|
35
|
+
KeywordArgumentsTestJob.perform_later(name: 'first', count: 1)
|
|
36
|
+
KeywordArgumentsTestJob.perform_later(name: 'second', count: 2, enabled: true)
|
|
37
|
+
|
|
38
|
+
poll_queues_until(timeout: 30) do
|
|
39
|
+
DT[:executions].size >= 2
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
assert_equal(2, DT[:executions].size, "Expected 2 job executions, got #{DT[:executions].size}")
|
|
43
|
+
|
|
44
|
+
# Find the executions by name
|
|
45
|
+
first_exec = DT[:executions].find { |e| e[:name] == 'first' }
|
|
46
|
+
second_exec = DT[:executions].find { |e| e[:name] == 'second' }
|
|
47
|
+
|
|
48
|
+
assert(first_exec, "Expected to find 'first' job execution")
|
|
49
|
+
assert(second_exec, "Expected to find 'second' job execution")
|
|
50
|
+
|
|
51
|
+
# Verify keyword arguments were passed correctly
|
|
52
|
+
assert_equal('first', first_exec[:name])
|
|
53
|
+
assert_equal(1, first_exec[:count])
|
|
54
|
+
assert_equal(false, first_exec[:enabled])
|
|
55
|
+
|
|
56
|
+
assert_equal('second', second_exec[:name])
|
|
57
|
+
assert_equal(2, second_exec[:count])
|
|
58
|
+
assert_equal(true, second_exec[:enabled])
|
|
59
|
+
|
|
60
|
+
# Verify job IDs
|
|
61
|
+
job_ids = DT[:executions].map { |e| e[:job_id] }
|
|
62
|
+
assert(job_ids.all? { |id| id && !id.empty? }, "All jobs should have job IDs")
|
|
63
|
+
assert_equal(2, job_ids.uniq.size, "All job IDs should be unique")
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# ActiveJob discard_on discards jobs that raise specific errors without retry
|
|
4
|
+
|
|
5
|
+
setup_sqs
|
|
6
|
+
setup_active_job
|
|
7
|
+
|
|
8
|
+
queue_name = DT.queue
|
|
9
|
+
create_test_queue(queue_name)
|
|
10
|
+
|
|
11
|
+
class DiscardOnTestJob < ActiveJob::Base
|
|
12
|
+
discard_on ArgumentError
|
|
13
|
+
|
|
14
|
+
def perform(should_fail)
|
|
15
|
+
DT[:attempts] << { job_id: job_id, should_fail: should_fail }
|
|
16
|
+
|
|
17
|
+
if should_fail
|
|
18
|
+
raise ArgumentError, "This should be discarded"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
DT[:successes] << { job_id: job_id }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
DiscardOnTestJob.queue_as(queue_name)
|
|
26
|
+
|
|
27
|
+
Shoryuken.add_group('default', 1)
|
|
28
|
+
Shoryuken.add_queue(queue_name, 1, 'default')
|
|
29
|
+
Shoryuken.register_worker(queue_name, Shoryuken::ActiveJob::JobWrapper)
|
|
30
|
+
|
|
31
|
+
failing_job = DiscardOnTestJob.perform_later(true)
|
|
32
|
+
success_job = DiscardOnTestJob.perform_later(false)
|
|
33
|
+
|
|
34
|
+
poll_queues_until(timeout: 30) { DT[:attempts].size >= 2 }
|
|
35
|
+
|
|
36
|
+
failing_attempts = DT[:attempts].select { |a| a[:job_id] == failing_job.job_id }
|
|
37
|
+
assert_equal(1, failing_attempts.size, "Discarded job should only attempt once")
|
|
38
|
+
|
|
39
|
+
failing_successes = DT[:successes].select { |s| s[:job_id] == failing_job.job_id }
|
|
40
|
+
assert_equal(0, failing_successes.size, "Discarded job should not succeed")
|
|
41
|
+
|
|
42
|
+
success_successes = DT[:successes].select { |s| s[:job_id] == success_job.job_id }
|
|
43
|
+
assert_equal(1, success_successes.size, "Non-failing job should succeed")
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# ActiveJob retry_on re-enqueues failed jobs until they succeed or exhaust attempts
|
|
4
|
+
|
|
5
|
+
setup_sqs
|
|
6
|
+
setup_active_job
|
|
7
|
+
|
|
8
|
+
queue_name = DT.queue
|
|
9
|
+
create_test_queue(queue_name, attributes: { 'VisibilityTimeout' => '2' })
|
|
10
|
+
|
|
11
|
+
class RetryOnTestJob < ActiveJob::Base
|
|
12
|
+
retry_on StandardError, wait: 0, attempts: 3
|
|
13
|
+
|
|
14
|
+
def perform
|
|
15
|
+
DT[:attempts] << { job_id: job_id, attempt: executions + 1, time: Time.now }
|
|
16
|
+
|
|
17
|
+
if DT[:attempts].count { |a| a[:job_id] == job_id } < 3
|
|
18
|
+
raise StandardError, "Simulated failure"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
DT[:successes] << { job_id: job_id, final_attempt: executions + 1 }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
RetryOnTestJob.queue_as(queue_name)
|
|
26
|
+
|
|
27
|
+
Shoryuken.add_group('default', 1)
|
|
28
|
+
Shoryuken.add_queue(queue_name, 1, 'default')
|
|
29
|
+
Shoryuken.register_worker(queue_name, Shoryuken::ActiveJob::JobWrapper)
|
|
30
|
+
|
|
31
|
+
RetryOnTestJob.perform_later
|
|
32
|
+
|
|
33
|
+
poll_queues_until(timeout: 30) { DT[:successes].size >= 1 }
|
|
34
|
+
|
|
35
|
+
assert(DT[:attempts].size >= 2, "Expected at least 2 retry attempts, got #{DT[:attempts].size}")
|
|
36
|
+
assert_equal(1, DT[:successes].size)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Full round-trip ActiveJob integration test
|
|
4
|
+
# Enqueues a job via ActiveJob → sends to ElasticMQ SQS → processes via Shoryuken → verifies execution
|
|
5
|
+
|
|
6
|
+
setup_sqs
|
|
7
|
+
setup_active_job
|
|
8
|
+
|
|
9
|
+
queue_name = DT.queue
|
|
10
|
+
create_test_queue(queue_name)
|
|
11
|
+
|
|
12
|
+
class RoundtripTestJob < ActiveJob::Base
|
|
13
|
+
def perform(payload)
|
|
14
|
+
DT[:executions] << {
|
|
15
|
+
payload: payload,
|
|
16
|
+
executed_at: Time.now,
|
|
17
|
+
job_id: job_id
|
|
18
|
+
}
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
RoundtripTestJob.queue_as(queue_name)
|
|
23
|
+
|
|
24
|
+
Shoryuken.add_group('default', 1)
|
|
25
|
+
Shoryuken.add_queue(queue_name, 1, 'default')
|
|
26
|
+
Shoryuken.register_worker(queue_name, Shoryuken::ActiveJob::JobWrapper)
|
|
27
|
+
|
|
28
|
+
RoundtripTestJob.perform_later('first_payload')
|
|
29
|
+
RoundtripTestJob.perform_later('second_payload')
|
|
30
|
+
RoundtripTestJob.perform_later({ key: 'complex', data: [1, 2, 3] })
|
|
31
|
+
|
|
32
|
+
poll_queues_until(timeout: 30) do
|
|
33
|
+
DT[:executions].size >= 3
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
assert_equal(3, DT[:executions].size, "Expected 3 job executions, got #{DT[:executions].size}")
|
|
37
|
+
|
|
38
|
+
payloads = DT[:executions].map { |e| e[:payload] }
|
|
39
|
+
assert_includes(payloads, 'first_payload')
|
|
40
|
+
assert_includes(payloads, 'second_payload')
|
|
41
|
+
|
|
42
|
+
complex_payload = payloads.find { |p| p.is_a?(Hash) }
|
|
43
|
+
assert(complex_payload, "Expected to find complex payload")
|
|
44
|
+
# Keys may be strings or symbols depending on serialization
|
|
45
|
+
key_value = complex_payload['key'] || complex_payload[:key]
|
|
46
|
+
data_value = complex_payload['data'] || complex_payload[:data]
|
|
47
|
+
assert_equal('complex', key_value)
|
|
48
|
+
assert_equal([1, 2, 3], data_value)
|
|
49
|
+
|
|
50
|
+
job_ids = DT[:executions].map { |e| e[:job_id] }
|
|
51
|
+
assert(job_ids.all? { |id| id && !id.empty? }, "All jobs should have job IDs")
|
|
52
|
+
assert_equal(3, job_ids.uniq.size, "All job IDs should be unique")
|