shoryuken 6.2.1 → 7.0.0.alpha2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/.devcontainer/base.Dockerfile +1 -1
 - data/.github/workflows/push.yml +36 -0
 - data/.github/workflows/specs.yml +40 -30
 - data/.github/workflows/verify-action-pins.yml +16 -0
 - data/.gitignore +2 -1
 - data/.rspec +2 -1
 - data/.rubocop.yml +6 -1
 - data/.ruby-version +1 -0
 - data/Appraisals +8 -27
 - data/CHANGELOG.md +213 -139
 - data/Gemfile +2 -7
 - data/README.md +14 -26
 - data/Rakefile +2 -0
 - data/bin/cli/base.rb +1 -2
 - data/bin/cli/sqs.rb +13 -5
 - data/bin/shoryuken +2 -1
 - data/docker-compose.yml +22 -0
 - data/gemfiles/rails_7_0.gemfile +10 -13
 - data/gemfiles/rails_7_1.gemfile +19 -0
 - data/gemfiles/rails_7_2.gemfile +19 -0
 - data/gemfiles/rails_8_0.gemfile +19 -0
 - data/lib/shoryuken/body_parser.rb +3 -1
 - data/lib/shoryuken/client.rb +2 -0
 - data/lib/shoryuken/default_exception_handler.rb +2 -0
 - data/lib/shoryuken/default_worker_registry.rb +11 -11
 - data/lib/shoryuken/environment_loader.rb +6 -6
 - data/lib/shoryuken/extensions/active_job_adapter.rb +13 -6
 - data/lib/shoryuken/extensions/active_job_concurrent_send_adapter.rb +5 -5
 - data/lib/shoryuken/extensions/active_job_extensions.rb +2 -0
 - data/lib/shoryuken/fetcher.rb +4 -2
 - data/lib/shoryuken/helpers/atomic_boolean.rb +44 -0
 - data/lib/shoryuken/helpers/atomic_counter.rb +104 -0
 - data/lib/shoryuken/helpers/atomic_hash.rb +182 -0
 - data/lib/shoryuken/helpers/hash_utils.rb +56 -0
 - data/lib/shoryuken/helpers/string_utils.rb +65 -0
 - data/lib/shoryuken/inline_message.rb +22 -0
 - data/lib/shoryuken/launcher.rb +2 -0
 - data/lib/shoryuken/logging.rb +19 -5
 - data/lib/shoryuken/manager.rb +15 -5
 - data/lib/shoryuken/message.rb +2 -0
 - data/lib/shoryuken/middleware/chain.rb +2 -0
 - data/lib/shoryuken/middleware/server/active_record.rb +2 -0
 - data/lib/shoryuken/middleware/server/auto_delete.rb +2 -0
 - data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +10 -10
 - data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +5 -3
 - data/lib/shoryuken/middleware/server/timing.rb +2 -0
 - data/lib/shoryuken/options.rb +14 -5
 - data/lib/shoryuken/polling/base_strategy.rb +126 -0
 - data/lib/shoryuken/polling/queue_configuration.rb +103 -0
 - data/lib/shoryuken/polling/strict_priority.rb +2 -0
 - data/lib/shoryuken/polling/weighted_round_robin.rb +2 -0
 - data/lib/shoryuken/processor.rb +5 -2
 - data/lib/shoryuken/queue.rb +6 -4
 - data/lib/shoryuken/runner.rb +9 -12
 - data/lib/shoryuken/util.rb +6 -6
 - data/lib/shoryuken/version.rb +3 -1
 - data/lib/shoryuken/worker/default_executor.rb +2 -0
 - data/lib/shoryuken/worker/inline_executor.rb +9 -2
 - data/lib/shoryuken/worker.rb +2 -0
 - data/lib/shoryuken/worker_registry.rb +2 -0
 - data/lib/shoryuken.rb +11 -34
 - data/renovate.json +16 -0
 - data/shoryuken.gemspec +7 -4
 - data/spec/integration/launcher_spec.rb +3 -4
 - data/spec/shared_examples_for_active_job.rb +13 -8
 - data/spec/shoryuken/body_parser_spec.rb +2 -4
 - data/spec/shoryuken/client_spec.rb +1 -1
 - data/spec/shoryuken/default_exception_handler_spec.rb +9 -10
 - data/spec/shoryuken/default_worker_registry_spec.rb +1 -2
 - data/spec/shoryuken/environment_loader_spec.rb +9 -8
 - data/spec/shoryuken/extensions/active_job_adapter_spec.rb +2 -1
 - data/spec/shoryuken/extensions/active_job_base_spec.rb +2 -1
 - data/spec/shoryuken/extensions/active_job_concurrent_send_adapter_spec.rb +2 -1
 - data/spec/shoryuken/extensions/active_job_wrapper_spec.rb +2 -1
 - data/spec/shoryuken/fetcher_spec.rb +23 -26
 - data/spec/shoryuken/helpers/atomic_boolean_spec.rb +196 -0
 - data/spec/shoryuken/helpers/atomic_counter_spec.rb +177 -0
 - data/spec/shoryuken/helpers/atomic_hash_spec.rb +307 -0
 - data/spec/shoryuken/helpers/hash_utils_spec.rb +145 -0
 - data/spec/shoryuken/helpers/string_utils_spec.rb +124 -0
 - data/spec/shoryuken/helpers_integration_spec.rb +96 -0
 - data/spec/shoryuken/inline_message_spec.rb +196 -0
 - data/spec/shoryuken/launcher_spec.rb +1 -2
 - data/spec/shoryuken/manager_spec.rb +1 -2
 - data/spec/shoryuken/middleware/chain_spec.rb +1 -1
 - data/spec/shoryuken/middleware/server/auto_delete_spec.rb +1 -1
 - data/spec/shoryuken/middleware/server/auto_extend_visibility_spec.rb +1 -1
 - data/spec/shoryuken/middleware/server/exponential_backoff_retry_spec.rb +1 -1
 - data/spec/shoryuken/middleware/server/timing_spec.rb +1 -1
 - data/spec/shoryuken/options_spec.rb +4 -4
 - data/spec/shoryuken/polling/base_strategy_spec.rb +280 -0
 - data/spec/shoryuken/polling/queue_configuration_spec.rb +195 -0
 - data/spec/shoryuken/polling/strict_priority_spec.rb +1 -1
 - data/spec/shoryuken/polling/weighted_round_robin_spec.rb +1 -1
 - data/spec/shoryuken/processor_spec.rb +1 -1
 - data/spec/shoryuken/queue_spec.rb +2 -3
 - data/spec/shoryuken/runner_spec.rb +1 -3
 - data/spec/shoryuken/util_spec.rb +1 -1
 - data/spec/shoryuken/worker/default_executor_spec.rb +1 -1
 - data/spec/shoryuken/worker/inline_executor_spec.rb +57 -1
 - data/spec/shoryuken/worker_spec.rb +15 -11
 - data/spec/shoryuken_spec.rb +1 -1
 - data/spec/spec_helper.rb +19 -4
 - metadata +79 -35
 - data/.codeclimate.yml +0 -20
 - 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/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/lib/shoryuken/core_ext.rb +0 -69
 - data/lib/shoryuken/polling/base.rb +0 -67
 - data/shoryuken.jpg +0 -0
 - data/spec/shoryuken/core_ext_spec.rb +0 -40
 
| 
         @@ -0,0 +1,307 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            RSpec.describe Shoryuken::Helpers::AtomicHash do
         
     | 
| 
      
 4 
     | 
    
         
            +
              subject { described_class.new }
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
              describe '#initialize' do
         
     | 
| 
      
 7 
     | 
    
         
            +
                it 'creates an empty hash' do
         
     | 
| 
      
 8 
     | 
    
         
            +
                  hash = described_class.new
         
     | 
| 
      
 9 
     | 
    
         
            +
                  expect(hash.keys).to eq([])
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
              end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              describe '#[]' do
         
     | 
| 
      
 14 
     | 
    
         
            +
                it 'returns nil for missing keys' do
         
     | 
| 
      
 15 
     | 
    
         
            +
                  expect(subject['missing']).to be_nil
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                it 'returns stored values' do
         
     | 
| 
      
 19 
     | 
    
         
            +
                  subject['key'] = 'value'
         
     | 
| 
      
 20 
     | 
    
         
            +
                  expect(subject['key']).to eq('value')
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                it 'works with different key types' do
         
     | 
| 
      
 24 
     | 
    
         
            +
                  subject[:symbol] = 'symbol_value'
         
     | 
| 
      
 25 
     | 
    
         
            +
                  subject[42] = 'number_value'
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                  expect(subject[:symbol]).to eq('symbol_value')
         
     | 
| 
      
 28 
     | 
    
         
            +
                  expect(subject[42]).to eq('number_value')
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
              end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
              describe '#[]=' do
         
     | 
| 
      
 33 
     | 
    
         
            +
                it 'stores values' do
         
     | 
| 
      
 34 
     | 
    
         
            +
                  subject['key'] = 'value'
         
     | 
| 
      
 35 
     | 
    
         
            +
                  expect(subject['key']).to eq('value')
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                it 'overwrites existing values' do
         
     | 
| 
      
 39 
     | 
    
         
            +
                  subject['key'] = 'old_value'
         
     | 
| 
      
 40 
     | 
    
         
            +
                  subject['key'] = 'new_value'
         
     | 
| 
      
 41 
     | 
    
         
            +
                  expect(subject['key']).to eq('new_value')
         
     | 
| 
      
 42 
     | 
    
         
            +
                end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                it 'returns the assigned value' do
         
     | 
| 
      
 45 
     | 
    
         
            +
                  result = (subject['key'] = 'value')
         
     | 
| 
      
 46 
     | 
    
         
            +
                  expect(result).to eq('value')
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
              end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
              describe '#clear' do
         
     | 
| 
      
 51 
     | 
    
         
            +
                it 'removes all entries' do
         
     | 
| 
      
 52 
     | 
    
         
            +
                  subject['key1'] = 'value1'
         
     | 
| 
      
 53 
     | 
    
         
            +
                  subject['key2'] = 'value2'
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                  expect(subject.keys.size).to eq(2)
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                  subject.clear
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                  expect(subject.keys).to eq([])
         
     | 
| 
      
 60 
     | 
    
         
            +
                  expect(subject['key1']).to be_nil
         
     | 
| 
      
 61 
     | 
    
         
            +
                  expect(subject['key2']).to be_nil
         
     | 
| 
      
 62 
     | 
    
         
            +
                end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                it 'returns the hash itself' do
         
     | 
| 
      
 65 
     | 
    
         
            +
                  subject['key'] = 'value'
         
     | 
| 
      
 66 
     | 
    
         
            +
                  result = subject.clear
         
     | 
| 
      
 67 
     | 
    
         
            +
                  expect(result).to eq({})
         
     | 
| 
      
 68 
     | 
    
         
            +
                end
         
     | 
| 
      
 69 
     | 
    
         
            +
              end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
              describe '#keys' do
         
     | 
| 
      
 72 
     | 
    
         
            +
                it 'returns empty array for empty hash' do
         
     | 
| 
      
 73 
     | 
    
         
            +
                  expect(subject.keys).to eq([])
         
     | 
| 
      
 74 
     | 
    
         
            +
                end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                it 'returns all keys' do
         
     | 
| 
      
 77 
     | 
    
         
            +
                  subject['key1'] = 'value1'
         
     | 
| 
      
 78 
     | 
    
         
            +
                  subject[:key2] = 'value2'
         
     | 
| 
      
 79 
     | 
    
         
            +
                  subject[42] = 'value3'
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                  keys = subject.keys
         
     | 
| 
      
 82 
     | 
    
         
            +
                  expect(keys).to contain_exactly('key1', :key2, 42)
         
     | 
| 
      
 83 
     | 
    
         
            +
                end
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                it 'reflects changes after modifications' do
         
     | 
| 
      
 86 
     | 
    
         
            +
                  subject['key'] = 'value'
         
     | 
| 
      
 87 
     | 
    
         
            +
                  expect(subject.keys).to eq(['key'])
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
                  subject.clear
         
     | 
| 
      
 90 
     | 
    
         
            +
                  expect(subject.keys).to eq([])
         
     | 
| 
      
 91 
     | 
    
         
            +
                end
         
     | 
| 
      
 92 
     | 
    
         
            +
              end
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
              describe '#fetch' do
         
     | 
| 
      
 95 
     | 
    
         
            +
                it 'returns value for existing key' do
         
     | 
| 
      
 96 
     | 
    
         
            +
                  subject['key'] = 'value'
         
     | 
| 
      
 97 
     | 
    
         
            +
                  expect(subject.fetch('key')).to eq('value')
         
     | 
| 
      
 98 
     | 
    
         
            +
                end
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                it 'returns default for missing key' do
         
     | 
| 
      
 101 
     | 
    
         
            +
                  expect(subject.fetch('missing', 'default')).to eq('default')
         
     | 
| 
      
 102 
     | 
    
         
            +
                end
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                it 'returns nil as default when no default provided' do
         
     | 
| 
      
 105 
     | 
    
         
            +
                  expect(subject.fetch('missing')).to be_nil
         
     | 
| 
      
 106 
     | 
    
         
            +
                end
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                it 'works with different default types' do
         
     | 
| 
      
 109 
     | 
    
         
            +
                  expect(subject.fetch('missing', [])).to eq([])
         
     | 
| 
      
 110 
     | 
    
         
            +
                  expect(subject.fetch('missing', {})).to eq({})
         
     | 
| 
      
 111 
     | 
    
         
            +
                  expect(subject.fetch('missing', 42)).to eq(42)
         
     | 
| 
      
 112 
     | 
    
         
            +
                end
         
     | 
| 
      
 113 
     | 
    
         
            +
              end
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
              describe 'thread safety' do
         
     | 
| 
      
 116 
     | 
    
         
            +
                it 'handles concurrent writes correctly' do
         
     | 
| 
      
 117 
     | 
    
         
            +
                  hash = described_class.new
         
     | 
| 
      
 118 
     | 
    
         
            +
                  threads = []
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
                  # 10 threads, each writing 100 different keys
         
     | 
| 
      
 121 
     | 
    
         
            +
                  10.times do |thread_id|
         
     | 
| 
      
 122 
     | 
    
         
            +
                    threads << Thread.new do
         
     | 
| 
      
 123 
     | 
    
         
            +
                      100.times do |i|
         
     | 
| 
      
 124 
     | 
    
         
            +
                        key = "thread_#{thread_id}_key_#{i}"
         
     | 
| 
      
 125 
     | 
    
         
            +
                        hash[key] = "value_#{i}"
         
     | 
| 
      
 126 
     | 
    
         
            +
                      end
         
     | 
| 
      
 127 
     | 
    
         
            +
                    end
         
     | 
| 
      
 128 
     | 
    
         
            +
                  end
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
                  threads.each(&:join)
         
     | 
| 
      
 131 
     | 
    
         
            +
             
     | 
| 
      
 132 
     | 
    
         
            +
                  # Verify all 1000 keys were written
         
     | 
| 
      
 133 
     | 
    
         
            +
                  expect(hash.keys.size).to eq(1000)
         
     | 
| 
      
 134 
     | 
    
         
            +
             
     | 
| 
      
 135 
     | 
    
         
            +
                  # Verify a sample of values
         
     | 
| 
      
 136 
     | 
    
         
            +
                  expect(hash['thread_0_key_0']).to eq('value_0')
         
     | 
| 
      
 137 
     | 
    
         
            +
                  expect(hash['thread_9_key_99']).to eq('value_99')
         
     | 
| 
      
 138 
     | 
    
         
            +
                end
         
     | 
| 
      
 139 
     | 
    
         
            +
             
     | 
| 
      
 140 
     | 
    
         
            +
                it 'handles concurrent reads correctly' do
         
     | 
| 
      
 141 
     | 
    
         
            +
                  hash = described_class.new
         
     | 
| 
      
 142 
     | 
    
         
            +
             
     | 
| 
      
 143 
     | 
    
         
            +
                  # Pre-populate hash
         
     | 
| 
      
 144 
     | 
    
         
            +
                  100.times { |i| hash["key_#{i}"] = "value_#{i}" }
         
     | 
| 
      
 145 
     | 
    
         
            +
             
     | 
| 
      
 146 
     | 
    
         
            +
                  read_results = []
         
     | 
| 
      
 147 
     | 
    
         
            +
                  threads = []
         
     | 
| 
      
 148 
     | 
    
         
            +
             
     | 
| 
      
 149 
     | 
    
         
            +
                  # 10 threads, each reading 100 times
         
     | 
| 
      
 150 
     | 
    
         
            +
                  10.times do
         
     | 
| 
      
 151 
     | 
    
         
            +
                    threads << Thread.new do
         
     | 
| 
      
 152 
     | 
    
         
            +
                      100.times do |i|
         
     | 
| 
      
 153 
     | 
    
         
            +
                        key = "key_#{i % 100}"
         
     | 
| 
      
 154 
     | 
    
         
            +
                        read_results << hash[key]
         
     | 
| 
      
 155 
     | 
    
         
            +
                      end
         
     | 
| 
      
 156 
     | 
    
         
            +
                    end
         
     | 
| 
      
 157 
     | 
    
         
            +
                  end
         
     | 
| 
      
 158 
     | 
    
         
            +
             
     | 
| 
      
 159 
     | 
    
         
            +
                  threads.each(&:join)
         
     | 
| 
      
 160 
     | 
    
         
            +
             
     | 
| 
      
 161 
     | 
    
         
            +
                  # All reads should succeed
         
     | 
| 
      
 162 
     | 
    
         
            +
                  expect(read_results.size).to eq(1000)
         
     | 
| 
      
 163 
     | 
    
         
            +
                  expect(read_results.compact.size).to eq(1000)
         
     | 
| 
      
 164 
     | 
    
         
            +
                end
         
     | 
| 
      
 165 
     | 
    
         
            +
             
     | 
| 
      
 166 
     | 
    
         
            +
                it 'handles mixed concurrent read/write operations' do
         
     | 
| 
      
 167 
     | 
    
         
            +
                  hash = described_class.new
         
     | 
| 
      
 168 
     | 
    
         
            +
                  threads = []
         
     | 
| 
      
 169 
     | 
    
         
            +
             
     | 
| 
      
 170 
     | 
    
         
            +
                  # Writer threads
         
     | 
| 
      
 171 
     | 
    
         
            +
                  5.times do |thread_id|
         
     | 
| 
      
 172 
     | 
    
         
            +
                    threads << Thread.new do
         
     | 
| 
      
 173 
     | 
    
         
            +
                      50.times do |i|
         
     | 
| 
      
 174 
     | 
    
         
            +
                        hash["writer_#{thread_id}_#{i}"] = "value_#{i}"
         
     | 
| 
      
 175 
     | 
    
         
            +
                      end
         
     | 
| 
      
 176 
     | 
    
         
            +
                    end
         
     | 
| 
      
 177 
     | 
    
         
            +
                  end
         
     | 
| 
      
 178 
     | 
    
         
            +
             
     | 
| 
      
 179 
     | 
    
         
            +
                  # Reader threads
         
     | 
| 
      
 180 
     | 
    
         
            +
                  5.times do
         
     | 
| 
      
 181 
     | 
    
         
            +
                    threads << Thread.new do
         
     | 
| 
      
 182 
     | 
    
         
            +
                      100.times do |i|
         
     | 
| 
      
 183 
     | 
    
         
            +
                        # Read existing keys
         
     | 
| 
      
 184 
     | 
    
         
            +
                        hash["writer_0_#{i % 50}"]
         
     | 
| 
      
 185 
     | 
    
         
            +
                      end
         
     | 
| 
      
 186 
     | 
    
         
            +
                    end
         
     | 
| 
      
 187 
     | 
    
         
            +
                  end
         
     | 
| 
      
 188 
     | 
    
         
            +
             
     | 
| 
      
 189 
     | 
    
         
            +
                  # Clear operations
         
     | 
| 
      
 190 
     | 
    
         
            +
                  2.times do
         
     | 
| 
      
 191 
     | 
    
         
            +
                    threads << Thread.new do
         
     | 
| 
      
 192 
     | 
    
         
            +
                      sleep(0.001) # Let some writes happen first
         
     | 
| 
      
 193 
     | 
    
         
            +
                      hash.clear
         
     | 
| 
      
 194 
     | 
    
         
            +
                    end
         
     | 
| 
      
 195 
     | 
    
         
            +
                  end
         
     | 
| 
      
 196 
     | 
    
         
            +
             
     | 
| 
      
 197 
     | 
    
         
            +
                  threads.each(&:join)
         
     | 
| 
      
 198 
     | 
    
         
            +
             
     | 
| 
      
 199 
     | 
    
         
            +
                  # The hash should be valid (not corrupted)
         
     | 
| 
      
 200 
     | 
    
         
            +
                  # After clear operations, it might be empty or have some keys
         
     | 
| 
      
 201 
     | 
    
         
            +
                  expect(hash.keys).to be_an(Array)
         
     | 
| 
      
 202 
     | 
    
         
            +
                end
         
     | 
| 
      
 203 
     | 
    
         
            +
             
     | 
| 
      
 204 
     | 
    
         
            +
                it 'provides thread-safe key enumeration' do
         
     | 
| 
      
 205 
     | 
    
         
            +
                  hash = described_class.new
         
     | 
| 
      
 206 
     | 
    
         
            +
                  threads = []
         
     | 
| 
      
 207 
     | 
    
         
            +
                  keys_snapshots = []
         
     | 
| 
      
 208 
     | 
    
         
            +
             
     | 
| 
      
 209 
     | 
    
         
            +
                  # Writer thread
         
     | 
| 
      
 210 
     | 
    
         
            +
                  writer = Thread.new do
         
     | 
| 
      
 211 
     | 
    
         
            +
                    100.times { |i| hash["key_#{i}"] = i }
         
     | 
| 
      
 212 
     | 
    
         
            +
                  end
         
     | 
| 
      
 213 
     | 
    
         
            +
             
     | 
| 
      
 214 
     | 
    
         
            +
                  # Reader threads taking snapshots of keys
         
     | 
| 
      
 215 
     | 
    
         
            +
                  5.times do
         
     | 
| 
      
 216 
     | 
    
         
            +
                    threads << Thread.new do
         
     | 
| 
      
 217 
     | 
    
         
            +
                      20.times do
         
     | 
| 
      
 218 
     | 
    
         
            +
                        keys_snapshots << hash.keys.dup
         
     | 
| 
      
 219 
     | 
    
         
            +
                      end
         
     | 
| 
      
 220 
     | 
    
         
            +
                    end
         
     | 
| 
      
 221 
     | 
    
         
            +
                  end
         
     | 
| 
      
 222 
     | 
    
         
            +
             
     | 
| 
      
 223 
     | 
    
         
            +
                  [writer, *threads].each(&:join)
         
     | 
| 
      
 224 
     | 
    
         
            +
             
     | 
| 
      
 225 
     | 
    
         
            +
                  # All snapshots should be valid arrays
         
     | 
| 
      
 226 
     | 
    
         
            +
                  keys_snapshots.each do |snapshot|
         
     | 
| 
      
 227 
     | 
    
         
            +
                    expect(snapshot).to be_an(Array)
         
     | 
| 
      
 228 
     | 
    
         
            +
                    # Keys should be valid
         
     | 
| 
      
 229 
     | 
    
         
            +
                    snapshot.each { |key| expect(key).to be_a(String) }
         
     | 
| 
      
 230 
     | 
    
         
            +
                  end
         
     | 
| 
      
 231 
     | 
    
         
            +
                end
         
     | 
| 
      
 232 
     | 
    
         
            +
              end
         
     | 
| 
      
 233 
     | 
    
         
            +
             
     | 
| 
      
 234 
     | 
    
         
            +
              describe 'mutex synchronization' do
         
     | 
| 
      
 235 
     | 
    
         
            +
                it 'ensures all operations are atomic' do
         
     | 
| 
      
 236 
     | 
    
         
            +
                  hash = described_class.new
         
     | 
| 
      
 237 
     | 
    
         
            +
             
     | 
| 
      
 238 
     | 
    
         
            +
                  # Verify basic operations work correctly
         
     | 
| 
      
 239 
     | 
    
         
            +
                  hash['key'] = 'value'
         
     | 
| 
      
 240 
     | 
    
         
            +
                  expect(hash['key']).to eq('value')
         
     | 
| 
      
 241 
     | 
    
         
            +
                  expect(hash.keys).to eq(['key'])
         
     | 
| 
      
 242 
     | 
    
         
            +
             
     | 
| 
      
 243 
     | 
    
         
            +
                  hash.clear
         
     | 
| 
      
 244 
     | 
    
         
            +
                  expect(hash.keys).to eq([])
         
     | 
| 
      
 245 
     | 
    
         
            +
                  expect(hash['key']).to be_nil
         
     | 
| 
      
 246 
     | 
    
         
            +
                end
         
     | 
| 
      
 247 
     | 
    
         
            +
              end
         
     | 
| 
      
 248 
     | 
    
         
            +
             
     | 
| 
      
 249 
     | 
    
         
            +
              describe 'drop-in replacement for Concurrent::Hash' do
         
     | 
| 
      
 250 
     | 
    
         
            +
                it 'provides the same basic API' do
         
     | 
| 
      
 251 
     | 
    
         
            +
                  # Test that our implementation has the same methods as Concurrent::Hash
         
     | 
| 
      
 252 
     | 
    
         
            +
                  expect(subject).to respond_to(:[])
         
     | 
| 
      
 253 
     | 
    
         
            +
                  expect(subject).to respond_to(:[]=)
         
     | 
| 
      
 254 
     | 
    
         
            +
                  expect(subject).to respond_to(:clear)
         
     | 
| 
      
 255 
     | 
    
         
            +
                  expect(subject).to respond_to(:keys)
         
     | 
| 
      
 256 
     | 
    
         
            +
                  expect(subject).to respond_to(:fetch)
         
     | 
| 
      
 257 
     | 
    
         
            +
                end
         
     | 
| 
      
 258 
     | 
    
         
            +
             
     | 
| 
      
 259 
     | 
    
         
            +
                it 'behaves identically to Concurrent::Hash for basic operations' do
         
     | 
| 
      
 260 
     | 
    
         
            +
                  # This test documents the expected behavior that matches Concurrent::Hash
         
     | 
| 
      
 261 
     | 
    
         
            +
                  hash = described_class.new
         
     | 
| 
      
 262 
     | 
    
         
            +
             
     | 
| 
      
 263 
     | 
    
         
            +
                  # Test assignment and retrieval
         
     | 
| 
      
 264 
     | 
    
         
            +
                  hash['queue1'] = 'Worker1'
         
     | 
| 
      
 265 
     | 
    
         
            +
                  expect(hash['queue1']).to eq('Worker1')
         
     | 
| 
      
 266 
     | 
    
         
            +
             
     | 
| 
      
 267 
     | 
    
         
            +
                  # Test keys
         
     | 
| 
      
 268 
     | 
    
         
            +
                  expect(hash.keys).to eq(['queue1'])
         
     | 
| 
      
 269 
     | 
    
         
            +
             
     | 
| 
      
 270 
     | 
    
         
            +
                  # Test fetch with default
         
     | 
| 
      
 271 
     | 
    
         
            +
                  expect(hash.fetch('queue1')).to eq('Worker1')
         
     | 
| 
      
 272 
     | 
    
         
            +
                  expect(hash.fetch('missing', 'default')).to eq('default')
         
     | 
| 
      
 273 
     | 
    
         
            +
             
     | 
| 
      
 274 
     | 
    
         
            +
                  # Test clear
         
     | 
| 
      
 275 
     | 
    
         
            +
                  hash.clear
         
     | 
| 
      
 276 
     | 
    
         
            +
                  expect(hash.keys).to eq([])
         
     | 
| 
      
 277 
     | 
    
         
            +
                  expect(hash['queue1']).to be_nil
         
     | 
| 
      
 278 
     | 
    
         
            +
                end
         
     | 
| 
      
 279 
     | 
    
         
            +
             
     | 
| 
      
 280 
     | 
    
         
            +
                it 'matches DefaultWorkerRegistry usage patterns' do
         
     | 
| 
      
 281 
     | 
    
         
            +
                  # Test the exact patterns used in DefaultWorkerRegistry
         
     | 
| 
      
 282 
     | 
    
         
            +
                  hash = described_class.new
         
     | 
| 
      
 283 
     | 
    
         
            +
             
     | 
| 
      
 284 
     | 
    
         
            +
                  # Pattern from register_worker method
         
     | 
| 
      
 285 
     | 
    
         
            +
                  queue = 'test_queue'
         
     | 
| 
      
 286 
     | 
    
         
            +
                  clazz = 'TestWorker'
         
     | 
| 
      
 287 
     | 
    
         
            +
                  hash[queue] = clazz
         
     | 
| 
      
 288 
     | 
    
         
            +
             
     | 
| 
      
 289 
     | 
    
         
            +
                  # Pattern from batch_receive_messages? method
         
     | 
| 
      
 290 
     | 
    
         
            +
                  expect(hash[queue]).to eq(clazz)
         
     | 
| 
      
 291 
     | 
    
         
            +
             
     | 
| 
      
 292 
     | 
    
         
            +
                  # Pattern from fetch_worker method  
         
     | 
| 
      
 293 
     | 
    
         
            +
                  expect(hash[queue]).to eq(clazz)
         
     | 
| 
      
 294 
     | 
    
         
            +
             
     | 
| 
      
 295 
     | 
    
         
            +
                  # Pattern from queues method
         
     | 
| 
      
 296 
     | 
    
         
            +
                  expect(hash.keys).to eq([queue])
         
     | 
| 
      
 297 
     | 
    
         
            +
             
     | 
| 
      
 298 
     | 
    
         
            +
                  # Pattern from workers method (with fetch and default)
         
     | 
| 
      
 299 
     | 
    
         
            +
                  expect(hash.fetch(queue, [])).to eq(clazz)
         
     | 
| 
      
 300 
     | 
    
         
            +
                  expect(hash.fetch('missing', [])).to eq([])
         
     | 
| 
      
 301 
     | 
    
         
            +
             
     | 
| 
      
 302 
     | 
    
         
            +
                  # Pattern from clear method
         
     | 
| 
      
 303 
     | 
    
         
            +
                  hash.clear
         
     | 
| 
      
 304 
     | 
    
         
            +
                  expect(hash.keys).to eq([])
         
     | 
| 
      
 305 
     | 
    
         
            +
                end
         
     | 
| 
      
 306 
     | 
    
         
            +
              end
         
     | 
| 
      
 307 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,145 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'spec_helper'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            RSpec.describe Shoryuken::Helpers::HashUtils do
         
     | 
| 
      
 6 
     | 
    
         
            +
              describe '.deep_symbolize_keys' do
         
     | 
| 
      
 7 
     | 
    
         
            +
                it 'converts string keys to symbols' do
         
     | 
| 
      
 8 
     | 
    
         
            +
                  input = { 'key1' => 'value1', 'key2' => 'value2' }
         
     | 
| 
      
 9 
     | 
    
         
            +
                  expected = { key1: 'value1', key2: 'value2' }
         
     | 
| 
      
 10 
     | 
    
         
            +
                  
         
     | 
| 
      
 11 
     | 
    
         
            +
                  expect(described_class.deep_symbolize_keys(input)).to eq(expected)
         
     | 
| 
      
 12 
     | 
    
         
            +
                end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                it 'leaves symbol keys unchanged' do
         
     | 
| 
      
 15 
     | 
    
         
            +
                  input = { key1: 'value1', key2: 'value2' }
         
     | 
| 
      
 16 
     | 
    
         
            +
                  expected = { key1: 'value1', key2: 'value2' }
         
     | 
| 
      
 17 
     | 
    
         
            +
                  
         
     | 
| 
      
 18 
     | 
    
         
            +
                  expect(described_class.deep_symbolize_keys(input)).to eq(expected)
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                it 'handles mixed key types' do
         
     | 
| 
      
 22 
     | 
    
         
            +
                  input = { 'string_key' => 'value1', :symbol_key => 'value2' }
         
     | 
| 
      
 23 
     | 
    
         
            +
                  expected = { string_key: 'value1', symbol_key: 'value2' }
         
     | 
| 
      
 24 
     | 
    
         
            +
                  
         
     | 
| 
      
 25 
     | 
    
         
            +
                  expect(described_class.deep_symbolize_keys(input)).to eq(expected)
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                it 'converts keys recursively in nested hashes' do
         
     | 
| 
      
 29 
     | 
    
         
            +
                  input = {
         
     | 
| 
      
 30 
     | 
    
         
            +
                    'level1' => {
         
     | 
| 
      
 31 
     | 
    
         
            +
                      'level2' => {
         
     | 
| 
      
 32 
     | 
    
         
            +
                        'level3' => 'deep_value'
         
     | 
| 
      
 33 
     | 
    
         
            +
                      },
         
     | 
| 
      
 34 
     | 
    
         
            +
                      'other_level2' => 'value'
         
     | 
| 
      
 35 
     | 
    
         
            +
                    },
         
     | 
| 
      
 36 
     | 
    
         
            +
                    'top_level' => 'value'
         
     | 
| 
      
 37 
     | 
    
         
            +
                  }
         
     | 
| 
      
 38 
     | 
    
         
            +
                  
         
     | 
| 
      
 39 
     | 
    
         
            +
                  expected = {
         
     | 
| 
      
 40 
     | 
    
         
            +
                    level1: {
         
     | 
| 
      
 41 
     | 
    
         
            +
                      level2: {
         
     | 
| 
      
 42 
     | 
    
         
            +
                        level3: 'deep_value'
         
     | 
| 
      
 43 
     | 
    
         
            +
                      },
         
     | 
| 
      
 44 
     | 
    
         
            +
                      other_level2: 'value'
         
     | 
| 
      
 45 
     | 
    
         
            +
                    },
         
     | 
| 
      
 46 
     | 
    
         
            +
                    top_level: 'value'
         
     | 
| 
      
 47 
     | 
    
         
            +
                  }
         
     | 
| 
      
 48 
     | 
    
         
            +
                  
         
     | 
| 
      
 49 
     | 
    
         
            +
                  expect(described_class.deep_symbolize_keys(input)).to eq(expected)
         
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                it 'preserves non-hash values in nested structures' do
         
     | 
| 
      
 53 
     | 
    
         
            +
                  input = {
         
     | 
| 
      
 54 
     | 
    
         
            +
                    'config' => {
         
     | 
| 
      
 55 
     | 
    
         
            +
                      'timeout' => 30,
         
     | 
| 
      
 56 
     | 
    
         
            +
                      'enabled' => true,
         
     | 
| 
      
 57 
     | 
    
         
            +
                      'tags' => ['tag1', 'tag2'],
         
     | 
| 
      
 58 
     | 
    
         
            +
                      'metadata' => nil
         
     | 
| 
      
 59 
     | 
    
         
            +
                    }
         
     | 
| 
      
 60 
     | 
    
         
            +
                  }
         
     | 
| 
      
 61 
     | 
    
         
            +
                  
         
     | 
| 
      
 62 
     | 
    
         
            +
                  expected = {
         
     | 
| 
      
 63 
     | 
    
         
            +
                    config: {
         
     | 
| 
      
 64 
     | 
    
         
            +
                      timeout: 30,
         
     | 
| 
      
 65 
     | 
    
         
            +
                      enabled: true,
         
     | 
| 
      
 66 
     | 
    
         
            +
                      tags: ['tag1', 'tag2'],
         
     | 
| 
      
 67 
     | 
    
         
            +
                      metadata: nil
         
     | 
| 
      
 68 
     | 
    
         
            +
                    }
         
     | 
| 
      
 69 
     | 
    
         
            +
                  }
         
     | 
| 
      
 70 
     | 
    
         
            +
                  
         
     | 
| 
      
 71 
     | 
    
         
            +
                  expect(described_class.deep_symbolize_keys(input)).to eq(expected)
         
     | 
| 
      
 72 
     | 
    
         
            +
                end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                it 'handles empty hash' do
         
     | 
| 
      
 75 
     | 
    
         
            +
                  expect(described_class.deep_symbolize_keys({})).to eq({})
         
     | 
| 
      
 76 
     | 
    
         
            +
                end
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                it 'handles hash with empty nested hash' do
         
     | 
| 
      
 79 
     | 
    
         
            +
                  input = { 'key' => {} }
         
     | 
| 
      
 80 
     | 
    
         
            +
                  expected = { key: {} }
         
     | 
| 
      
 81 
     | 
    
         
            +
                  
         
     | 
| 
      
 82 
     | 
    
         
            +
                  expect(described_class.deep_symbolize_keys(input)).to eq(expected)
         
     | 
| 
      
 83 
     | 
    
         
            +
                end
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                it 'returns non-hash input unchanged' do
         
     | 
| 
      
 86 
     | 
    
         
            +
                  expect(described_class.deep_symbolize_keys('string')).to eq('string')
         
     | 
| 
      
 87 
     | 
    
         
            +
                  expect(described_class.deep_symbolize_keys(123)).to eq(123)
         
     | 
| 
      
 88 
     | 
    
         
            +
                  expect(described_class.deep_symbolize_keys([])).to eq([])
         
     | 
| 
      
 89 
     | 
    
         
            +
                  expect(described_class.deep_symbolize_keys(nil)).to be_nil
         
     | 
| 
      
 90 
     | 
    
         
            +
                  expect(described_class.deep_symbolize_keys(true)).to be true
         
     | 
| 
      
 91 
     | 
    
         
            +
                end
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
                it 'handles keys that cannot be converted to symbols' do
         
     | 
| 
      
 94 
     | 
    
         
            +
                  # Create a key that will raise an exception when converted to symbol
         
     | 
| 
      
 95 
     | 
    
         
            +
                  problematic_key = Object.new
         
     | 
| 
      
 96 
     | 
    
         
            +
                  allow(problematic_key).to receive(:to_sym).and_raise(StandardError)
         
     | 
| 
      
 97 
     | 
    
         
            +
                  
         
     | 
| 
      
 98 
     | 
    
         
            +
                  input = { problematic_key => 'value', 'normal_key' => 'normal_value' }
         
     | 
| 
      
 99 
     | 
    
         
            +
                  result = described_class.deep_symbolize_keys(input)
         
     | 
| 
      
 100 
     | 
    
         
            +
                  
         
     | 
| 
      
 101 
     | 
    
         
            +
                  # The problematic key should remain as-is, normal key should be symbolized
         
     | 
| 
      
 102 
     | 
    
         
            +
                  expect(result[problematic_key]).to eq('value')
         
     | 
| 
      
 103 
     | 
    
         
            +
                  expect(result[:normal_key]).to eq('normal_value')
         
     | 
| 
      
 104 
     | 
    
         
            +
                end
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
                it 'does not modify the original hash' do
         
     | 
| 
      
 107 
     | 
    
         
            +
                  input = { 'key' => { 'nested' => 'value' } }
         
     | 
| 
      
 108 
     | 
    
         
            +
                  original_input = input.dup
         
     | 
| 
      
 109 
     | 
    
         
            +
                  
         
     | 
| 
      
 110 
     | 
    
         
            +
                  described_class.deep_symbolize_keys(input)
         
     | 
| 
      
 111 
     | 
    
         
            +
                  
         
     | 
| 
      
 112 
     | 
    
         
            +
                  expect(input).to eq(original_input)
         
     | 
| 
      
 113 
     | 
    
         
            +
                end
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
                context 'with configuration-like data' do
         
     | 
| 
      
 116 
     | 
    
         
            +
                  it 'processes typical YAML configuration' do
         
     | 
| 
      
 117 
     | 
    
         
            +
                    input = {
         
     | 
| 
      
 118 
     | 
    
         
            +
                      'database' => {
         
     | 
| 
      
 119 
     | 
    
         
            +
                        'host' => 'localhost',
         
     | 
| 
      
 120 
     | 
    
         
            +
                        'port' => 5432,
         
     | 
| 
      
 121 
     | 
    
         
            +
                        'ssl' => true
         
     | 
| 
      
 122 
     | 
    
         
            +
                      },
         
     | 
| 
      
 123 
     | 
    
         
            +
                      'queues' => {
         
     | 
| 
      
 124 
     | 
    
         
            +
                        'default' => { 'concurrency' => 5 },
         
     | 
| 
      
 125 
     | 
    
         
            +
                        'mailers' => { 'concurrency' => 2 }
         
     | 
| 
      
 126 
     | 
    
         
            +
                      }
         
     | 
| 
      
 127 
     | 
    
         
            +
                    }
         
     | 
| 
      
 128 
     | 
    
         
            +
                    
         
     | 
| 
      
 129 
     | 
    
         
            +
                    expected = {
         
     | 
| 
      
 130 
     | 
    
         
            +
                      database: {
         
     | 
| 
      
 131 
     | 
    
         
            +
                        host: 'localhost',
         
     | 
| 
      
 132 
     | 
    
         
            +
                        port: 5432,
         
     | 
| 
      
 133 
     | 
    
         
            +
                        ssl: true
         
     | 
| 
      
 134 
     | 
    
         
            +
                      },
         
     | 
| 
      
 135 
     | 
    
         
            +
                      queues: {
         
     | 
| 
      
 136 
     | 
    
         
            +
                        default: { concurrency: 5 },
         
     | 
| 
      
 137 
     | 
    
         
            +
                        mailers: { concurrency: 2 }
         
     | 
| 
      
 138 
     | 
    
         
            +
                      }
         
     | 
| 
      
 139 
     | 
    
         
            +
                    }
         
     | 
| 
      
 140 
     | 
    
         
            +
                    
         
     | 
| 
      
 141 
     | 
    
         
            +
                    expect(described_class.deep_symbolize_keys(input)).to eq(expected)
         
     | 
| 
      
 142 
     | 
    
         
            +
                  end
         
     | 
| 
      
 143 
     | 
    
         
            +
                end
         
     | 
| 
      
 144 
     | 
    
         
            +
              end
         
     | 
| 
      
 145 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,124 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'spec_helper'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            RSpec.describe Shoryuken::Helpers::StringUtils do
         
     | 
| 
      
 6 
     | 
    
         
            +
              describe '.constantize' do
         
     | 
| 
      
 7 
     | 
    
         
            +
                it 'returns a simple constant' do
         
     | 
| 
      
 8 
     | 
    
         
            +
                  expect(described_class.constantize('Object')).to eq(Object)
         
     | 
| 
      
 9 
     | 
    
         
            +
                  expect(described_class.constantize('String')).to eq(String)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  expect(described_class.constantize('Hash')).to eq(Hash)
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                it 'returns nested constants' do
         
     | 
| 
      
 14 
     | 
    
         
            +
                  expect(described_class.constantize('Shoryuken::Helpers::StringUtils')).to eq(Shoryuken::Helpers::StringUtils)
         
     | 
| 
      
 15 
     | 
    
         
            +
                  expect(described_class.constantize('Shoryuken::Helpers::AtomicCounter')).to eq(Shoryuken::Helpers::AtomicCounter)
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                it 'handles leading double colon' do
         
     | 
| 
      
 19 
     | 
    
         
            +
                  expect(described_class.constantize('::Object')).to eq(Object)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  expect(described_class.constantize('::String')).to eq(String)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  expect(described_class.constantize('::Shoryuken::Helpers::StringUtils')).to eq(Shoryuken::Helpers::StringUtils)
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                it 'handles empty string' do
         
     | 
| 
      
 25 
     | 
    
         
            +
                  expect(described_class.constantize('')).to eq(Object)
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                it 'handles string with only double colons' do
         
     | 
| 
      
 29 
     | 
    
         
            +
                  expect(described_class.constantize('::')).to eq(Object)
         
     | 
| 
      
 30 
     | 
    
         
            +
                  expect(described_class.constantize('::::')).to eq(Object)
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                it 'raises NameError for non-existent constants' do
         
     | 
| 
      
 34 
     | 
    
         
            +
                  expect { described_class.constantize('NonExistentClass') }.to raise_error(NameError)
         
     | 
| 
      
 35 
     | 
    
         
            +
                  expect { described_class.constantize('Shoryuken::NonExistentClass') }.to raise_error(NameError)
         
     | 
| 
      
 36 
     | 
    
         
            +
                  expect { described_class.constantize('NonExistent::AlsoNonExistent') }.to raise_error(NameError)
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                it 'raises NameError for partially invalid nested constants' do
         
     | 
| 
      
 40 
     | 
    
         
            +
                  expect { described_class.constantize('Shoryuken::NonExistent::AlsoNonExistent') }.to raise_error(NameError)
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                context 'with dynamically defined constants' do
         
     | 
| 
      
 44 
     | 
    
         
            +
                  before do
         
     | 
| 
      
 45 
     | 
    
         
            +
                    # Define a temporary constant for testing
         
     | 
| 
      
 46 
     | 
    
         
            +
                    Object.const_set('TempTestClass', Class.new)
         
     | 
| 
      
 47 
     | 
    
         
            +
                    TempTestClass.const_set('NestedClass', Class.new)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                  after do
         
     | 
| 
      
 51 
     | 
    
         
            +
                    # Clean up the temporary constant
         
     | 
| 
      
 52 
     | 
    
         
            +
                    Object.send(:remove_const, 'TempTestClass') if Object.const_defined?('TempTestClass')
         
     | 
| 
      
 53 
     | 
    
         
            +
                  end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                  it 'finds dynamically defined constants' do
         
     | 
| 
      
 56 
     | 
    
         
            +
                    expect(described_class.constantize('TempTestClass')).to eq(TempTestClass)
         
     | 
| 
      
 57 
     | 
    
         
            +
                  end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                  it 'finds nested dynamically defined constants' do
         
     | 
| 
      
 60 
     | 
    
         
            +
                    expect(described_class.constantize('TempTestClass::NestedClass')).to eq(TempTestClass::NestedClass)
         
     | 
| 
      
 61 
     | 
    
         
            +
                  end
         
     | 
| 
      
 62 
     | 
    
         
            +
                end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                context 'with module constants' do
         
     | 
| 
      
 65 
     | 
    
         
            +
                  it 'returns module constants' do
         
     | 
| 
      
 66 
     | 
    
         
            +
                    expect(described_class.constantize('Shoryuken')).to eq(Shoryuken)
         
     | 
| 
      
 67 
     | 
    
         
            +
                    expect(described_class.constantize('Shoryuken::Helpers')).to eq(Shoryuken::Helpers)
         
     | 
| 
      
 68 
     | 
    
         
            +
                  end
         
     | 
| 
      
 69 
     | 
    
         
            +
                end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                context 'with worker class scenarios' do
         
     | 
| 
      
 72 
     | 
    
         
            +
                  before do
         
     | 
| 
      
 73 
     | 
    
         
            +
                    # Simulate typical worker class scenarios
         
     | 
| 
      
 74 
     | 
    
         
            +
                    unless Object.const_defined?('MyApp')
         
     | 
| 
      
 75 
     | 
    
         
            +
                      Object.const_set('MyApp', Module.new)
         
     | 
| 
      
 76 
     | 
    
         
            +
                    end
         
     | 
| 
      
 77 
     | 
    
         
            +
                    
         
     | 
| 
      
 78 
     | 
    
         
            +
                    unless MyApp.const_defined?('EmailWorker')
         
     | 
| 
      
 79 
     | 
    
         
            +
                      MyApp.const_set('EmailWorker', Class.new)
         
     | 
| 
      
 80 
     | 
    
         
            +
                    end
         
     | 
| 
      
 81 
     | 
    
         
            +
                  end
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                  after do
         
     | 
| 
      
 84 
     | 
    
         
            +
                    # Clean up
         
     | 
| 
      
 85 
     | 
    
         
            +
                    MyApp.send(:remove_const, 'EmailWorker') if MyApp.const_defined?('EmailWorker')
         
     | 
| 
      
 86 
     | 
    
         
            +
                    Object.send(:remove_const, 'MyApp') if Object.const_defined?('MyApp')
         
     | 
| 
      
 87 
     | 
    
         
            +
                  end
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
                  it 'loads worker classes from string names' do
         
     | 
| 
      
 90 
     | 
    
         
            +
                    worker_class = described_class.constantize('MyApp::EmailWorker')
         
     | 
| 
      
 91 
     | 
    
         
            +
                    expect(worker_class).to eq(MyApp::EmailWorker)
         
     | 
| 
      
 92 
     | 
    
         
            +
                    expect(worker_class.new).to be_an_instance_of(MyApp::EmailWorker)
         
     | 
| 
      
 93 
     | 
    
         
            +
                  end
         
     | 
| 
      
 94 
     | 
    
         
            +
                end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                context 'edge cases' do
         
     | 
| 
      
 97 
     | 
    
         
            +
                  it 'handles constants with numbers' do
         
     | 
| 
      
 98 
     | 
    
         
            +
                    # Using existing constants that might have numbers
         
     | 
| 
      
 99 
     | 
    
         
            +
                    expect(described_class.constantize('Encoding::UTF_8')).to eq(Encoding::UTF_8)
         
     | 
| 
      
 100 
     | 
    
         
            +
                  end
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
                  it 'is case sensitive' do
         
     | 
| 
      
 103 
     | 
    
         
            +
                    expect { described_class.constantize('object') }.to raise_error(NameError)
         
     | 
| 
      
 104 
     | 
    
         
            +
                    expect { described_class.constantize('STRING') }.to raise_error(NameError)
         
     | 
| 
      
 105 
     | 
    
         
            +
                  end
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
                  it 'handles single character constant names' do
         
     | 
| 
      
 108 
     | 
    
         
            +
                    # Define a single character constant for testing
         
     | 
| 
      
 109 
     | 
    
         
            +
                    Object.const_set('A', Class.new) unless Object.const_defined?('A')
         
     | 
| 
      
 110 
     | 
    
         
            +
                    
         
     | 
| 
      
 111 
     | 
    
         
            +
                    expect(described_class.constantize('A')).to eq(A)
         
     | 
| 
      
 112 
     | 
    
         
            +
                    
         
     | 
| 
      
 113 
     | 
    
         
            +
                    # Clean up
         
     | 
| 
      
 114 
     | 
    
         
            +
                    Object.send(:remove_const, 'A') if Object.const_defined?('A')
         
     | 
| 
      
 115 
     | 
    
         
            +
                  end
         
     | 
| 
      
 116 
     | 
    
         
            +
                end
         
     | 
| 
      
 117 
     | 
    
         
            +
             
     | 
| 
      
 118 
     | 
    
         
            +
                context 'error messages' do
         
     | 
| 
      
 119 
     | 
    
         
            +
                  it 'provides meaningful error messages' do
         
     | 
| 
      
 120 
     | 
    
         
            +
                    expect { described_class.constantize('NonExistent') }.to raise_error(NameError, /NonExistent/)
         
     | 
| 
      
 121 
     | 
    
         
            +
                  end
         
     | 
| 
      
 122 
     | 
    
         
            +
                end
         
     | 
| 
      
 123 
     | 
    
         
            +
              end
         
     | 
| 
      
 124 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,96 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            RSpec.describe 'Helpers Integration' do
         
     | 
| 
      
 4 
     | 
    
         
            +
              # Integration tests for helper utility methods that replaced core extensions
         
     | 
| 
      
 5 
     | 
    
         
            +
              
         
     | 
| 
      
 6 
     | 
    
         
            +
              describe Shoryuken::Helpers::HashUtils do
         
     | 
| 
      
 7 
     | 
    
         
            +
                describe '.deep_symbolize_keys' do
         
     | 
| 
      
 8 
     | 
    
         
            +
                  it 'converts keys into symbols recursively' do
         
     | 
| 
      
 9 
     | 
    
         
            +
                    input = { :key1 => 'value1',
         
     | 
| 
      
 10 
     | 
    
         
            +
                             'key2' => 'value2',
         
     | 
| 
      
 11 
     | 
    
         
            +
                             'key3' => {
         
     | 
| 
      
 12 
     | 
    
         
            +
                               'key31' => { 'key311' => 'value311' },
         
     | 
| 
      
 13 
     | 
    
         
            +
                               'key32' => 'value32'
         
     | 
| 
      
 14 
     | 
    
         
            +
                             } }
         
     | 
| 
      
 15 
     | 
    
         
            +
                    
         
     | 
| 
      
 16 
     | 
    
         
            +
                    expected = { key1: 'value1',
         
     | 
| 
      
 17 
     | 
    
         
            +
                                key2: 'value2',
         
     | 
| 
      
 18 
     | 
    
         
            +
                                key3: { key31: { key311: 'value311' },
         
     | 
| 
      
 19 
     | 
    
         
            +
                                        key32: 'value32' } }
         
     | 
| 
      
 20 
     | 
    
         
            +
                    
         
     | 
| 
      
 21 
     | 
    
         
            +
                    expect(Shoryuken::Helpers::HashUtils.deep_symbolize_keys(input)).to eq(expected)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  it 'handles non-hash input gracefully' do
         
     | 
| 
      
 25 
     | 
    
         
            +
                    expect(Shoryuken::Helpers::HashUtils.deep_symbolize_keys('string')).to eq('string')
         
     | 
| 
      
 26 
     | 
    
         
            +
                    expect(Shoryuken::Helpers::HashUtils.deep_symbolize_keys(123)).to eq(123)
         
     | 
| 
      
 27 
     | 
    
         
            +
                    expect(Shoryuken::Helpers::HashUtils.deep_symbolize_keys(nil)).to be_nil
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  it 'handles empty hash' do
         
     | 
| 
      
 31 
     | 
    
         
            +
                    expect(Shoryuken::Helpers::HashUtils.deep_symbolize_keys({})).to eq({})
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                  it 'handles mixed value types' do
         
     | 
| 
      
 35 
     | 
    
         
            +
                    input = { 'key1' => 'string', 'key2' => 123, 'key3' => { 'nested' => true } }
         
     | 
| 
      
 36 
     | 
    
         
            +
                    expected = { key1: 'string', key2: 123, key3: { nested: true } }
         
     | 
| 
      
 37 
     | 
    
         
            +
                    
         
     | 
| 
      
 38 
     | 
    
         
            +
                    expect(Shoryuken::Helpers::HashUtils.deep_symbolize_keys(input)).to eq(expected)
         
     | 
| 
      
 39 
     | 
    
         
            +
                  end
         
     | 
| 
      
 40 
     | 
    
         
            +
                end
         
     | 
| 
      
 41 
     | 
    
         
            +
              end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
              describe Shoryuken::Helpers::StringUtils do
         
     | 
| 
      
 44 
     | 
    
         
            +
                describe '.constantize' do
         
     | 
| 
      
 45 
     | 
    
         
            +
                  class HelloWorld; end
         
     | 
| 
      
 46 
     | 
    
         
            +
                  
         
     | 
| 
      
 47 
     | 
    
         
            +
                  it 'returns a class from a string' do
         
     | 
| 
      
 48 
     | 
    
         
            +
                    expect(Shoryuken::Helpers::StringUtils.constantize('HelloWorld')).to eq(HelloWorld)
         
     | 
| 
      
 49 
     | 
    
         
            +
                  end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                  it 'handles nested constants' do
         
     | 
| 
      
 52 
     | 
    
         
            +
                    expect(Shoryuken::Helpers::StringUtils.constantize('Shoryuken::Helpers::StringUtils')).to eq(Shoryuken::Helpers::StringUtils)
         
     | 
| 
      
 53 
     | 
    
         
            +
                  end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                  it 'raises NameError for non-existent constants' do
         
     | 
| 
      
 56 
     | 
    
         
            +
                    expect { Shoryuken::Helpers::StringUtils.constantize('NonExistentClass') }.to raise_error(NameError)
         
     | 
| 
      
 57 
     | 
    
         
            +
                  end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                  it 'handles empty string' do
         
     | 
| 
      
 60 
     | 
    
         
            +
                    expect(Shoryuken::Helpers::StringUtils.constantize('')).to eq(Object)
         
     | 
| 
      
 61 
     | 
    
         
            +
                  end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                  it 'handles leading double colon' do
         
     | 
| 
      
 64 
     | 
    
         
            +
                    expect(Shoryuken::Helpers::StringUtils.constantize('::Object')).to eq(Object)
         
     | 
| 
      
 65 
     | 
    
         
            +
                  end
         
     | 
| 
      
 66 
     | 
    
         
            +
                end
         
     | 
| 
      
 67 
     | 
    
         
            +
              end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
              describe 'Integration scenarios' do
         
     | 
| 
      
 70 
     | 
    
         
            +
                it 'processes configuration data end-to-end' do
         
     | 
| 
      
 71 
     | 
    
         
            +
                  # Simulate loading YAML config and converting worker class names
         
     | 
| 
      
 72 
     | 
    
         
            +
                  config_data = {
         
     | 
| 
      
 73 
     | 
    
         
            +
                    'queues' => {
         
     | 
| 
      
 74 
     | 
    
         
            +
                      'default' => { 'worker_class' => 'Object' },
         
     | 
| 
      
 75 
     | 
    
         
            +
                      'mailers' => { 'worker_class' => 'String' }
         
     | 
| 
      
 76 
     | 
    
         
            +
                    }
         
     | 
| 
      
 77 
     | 
    
         
            +
                  }
         
     | 
| 
      
 78 
     | 
    
         
            +
                  
         
     | 
| 
      
 79 
     | 
    
         
            +
                  symbolized = Shoryuken::Helpers::HashUtils.deep_symbolize_keys(config_data)
         
     | 
| 
      
 80 
     | 
    
         
            +
                  
         
     | 
| 
      
 81 
     | 
    
         
            +
                  expect(symbolized).to eq({
         
     | 
| 
      
 82 
     | 
    
         
            +
                    queues: {
         
     | 
| 
      
 83 
     | 
    
         
            +
                      default: { worker_class: 'Object' },
         
     | 
| 
      
 84 
     | 
    
         
            +
                      mailers: { worker_class: 'String' }
         
     | 
| 
      
 85 
     | 
    
         
            +
                    }
         
     | 
| 
      
 86 
     | 
    
         
            +
                  })
         
     | 
| 
      
 87 
     | 
    
         
            +
                  
         
     | 
| 
      
 88 
     | 
    
         
            +
                  # Test constantizing the worker classes
         
     | 
| 
      
 89 
     | 
    
         
            +
                  default_worker = Shoryuken::Helpers::StringUtils.constantize(symbolized[:queues][:default][:worker_class])
         
     | 
| 
      
 90 
     | 
    
         
            +
                  mailer_worker = Shoryuken::Helpers::StringUtils.constantize(symbolized[:queues][:mailers][:worker_class])
         
     | 
| 
      
 91 
     | 
    
         
            +
                  
         
     | 
| 
      
 92 
     | 
    
         
            +
                  expect(default_worker).to eq(Object)
         
     | 
| 
      
 93 
     | 
    
         
            +
                  expect(mailer_worker).to eq(String)
         
     | 
| 
      
 94 
     | 
    
         
            +
                end
         
     | 
| 
      
 95 
     | 
    
         
            +
              end
         
     | 
| 
      
 96 
     | 
    
         
            +
            end
         
     |