hutch 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/Gemfile +18 -0
- data/Guardfile +5 -0
- data/README.md +136 -0
- data/Rakefile +14 -0
- data/bin/hutch +8 -0
- data/circle.yml +3 -0
- data/examples/consumer.rb +13 -0
- data/examples/producer.rb +10 -0
- data/hutch.gemspec +22 -0
- data/lib/hutch.rb +40 -0
- data/lib/hutch/broker.rb +175 -0
- data/lib/hutch/cli.rb +151 -0
- data/lib/hutch/config.rb +66 -0
- data/lib/hutch/consumer.rb +33 -0
- data/lib/hutch/error_handlers/logger.rb +16 -0
- data/lib/hutch/error_handlers/sentry.rb +23 -0
- data/lib/hutch/exceptions.rb +5 -0
- data/lib/hutch/logging.rb +32 -0
- data/lib/hutch/message.rb +26 -0
- data/lib/hutch/version.rb +4 -0
- data/lib/hutch/worker.rb +104 -0
- data/spec/hutch/broker_spec.rb +157 -0
- data/spec/hutch/config_spec.rb +69 -0
- data/spec/hutch/consumer_spec.rb +80 -0
- data/spec/hutch/error_handlers/logger_spec.rb +15 -0
- data/spec/hutch/error_handlers/sentry_spec.rb +20 -0
- data/spec/hutch/logger_spec.rb +28 -0
- data/spec/hutch/message_spec.rb +35 -0
- data/spec/hutch/worker_spec.rb +80 -0
- data/spec/hutch_spec.rb +16 -0
- data/spec/spec_helper.rb +23 -0
- metadata +144 -0
| @@ -0,0 +1,157 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
            require 'hutch/broker'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            describe Hutch::Broker do
         | 
| 5 | 
            +
              let(:config) { deep_copy(Hutch::Config.user_config) }
         | 
| 6 | 
            +
              subject(:broker) { Hutch::Broker.new(config) }
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              describe '#connect' do
         | 
| 9 | 
            +
                before { broker.stub(:set_up_amqp_connection) }
         | 
| 10 | 
            +
                before { broker.stub(:set_up_api_connection) }
         | 
| 11 | 
            +
                before { broker.stub(:disconnect) }
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                it 'sets up the amqp connection' do
         | 
| 14 | 
            +
                  broker.should_receive(:set_up_amqp_connection)
         | 
| 15 | 
            +
                  broker.connect
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                it 'sets up the api connection' do
         | 
| 19 | 
            +
                  broker.should_receive(:set_up_api_connection)
         | 
| 20 | 
            +
                  broker.connect
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                it 'does not disconnect' do
         | 
| 24 | 
            +
                  broker.should_not_receive(:disconnect)
         | 
| 25 | 
            +
                  broker.connect
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                context 'when given a block' do
         | 
| 29 | 
            +
                  it 'disconnects' do
         | 
| 30 | 
            +
                    broker.should_receive(:disconnect).once
         | 
| 31 | 
            +
                    broker.connect { }
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
              describe '#set_up_amqp_connection', rabbitmq: true do
         | 
| 37 | 
            +
                context 'with valid details' do
         | 
| 38 | 
            +
                  before { broker.set_up_amqp_connection }
         | 
| 39 | 
            +
                  after  { broker.disconnect }
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  its(:connection) { should be_a Bunny::Session }
         | 
| 42 | 
            +
                  its(:channel)    { should be_a Bunny::Channel }
         | 
| 43 | 
            +
                  its(:exchange)   { should be_a Bunny::Exchange }
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                context 'when given invalid details' do
         | 
| 47 | 
            +
                  before { config[:mq_host] = 'notarealhost' }
         | 
| 48 | 
            +
                  let(:set_up_amqp_connection) { ->{ broker.set_up_amqp_connection } }
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  specify { set_up_amqp_connection.should raise_error }
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
              end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
              describe '#set_up_api_connection', rabbitmq: true do
         | 
| 55 | 
            +
                context 'with valid details' do
         | 
| 56 | 
            +
                  before { broker.set_up_api_connection }
         | 
| 57 | 
            +
                  after  { broker.disconnect }
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                  its(:api_client) { should be_a CarrotTop }
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                context 'when given invalid details' do
         | 
| 63 | 
            +
                  before { config[:mq_api_host] = 'notarealhost' }
         | 
| 64 | 
            +
                  after  { broker.disconnect }
         | 
| 65 | 
            +
                  let(:set_up_api_connection) { ->{ broker.set_up_api_connection } }
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                  specify { set_up_api_connection.should raise_error }
         | 
| 68 | 
            +
                end
         | 
| 69 | 
            +
              end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
              describe '#bindings', rabbitmq: true do
         | 
| 72 | 
            +
                around { |example| broker.connect { example.run } }
         | 
| 73 | 
            +
                subject { broker.bindings }
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                context 'with no bindings' do
         | 
| 76 | 
            +
                  its(:keys) { should_not include 'test' }
         | 
| 77 | 
            +
                end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                context 'with a binding' do
         | 
| 80 | 
            +
                  around do |example|
         | 
| 81 | 
            +
                    queue = broker.queue('test').bind(broker.exchange, routing_key: 'key')
         | 
| 82 | 
            +
                    example.run
         | 
| 83 | 
            +
                    queue.unbind(broker.exchange, routing_key: 'key').delete
         | 
| 84 | 
            +
                  end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                  it { should include({ 'test' => ['key'] }) }
         | 
| 87 | 
            +
                end
         | 
| 88 | 
            +
              end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
              describe '#bind_queue' do
         | 
| 91 | 
            +
                around { |example| broker.connect { example.run } }
         | 
| 92 | 
            +
                let(:routing_keys) { %w( a b c ) }
         | 
| 93 | 
            +
                let(:queue) { double('Queue', bind: nil, unbind: nil, name: 'consumer') }
         | 
| 94 | 
            +
                before { broker.stub(bindings: { 'consumer' => ['d'] }) }
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                it 'calls bind for each routing key' do
         | 
| 97 | 
            +
                  routing_keys.each do |key|
         | 
| 98 | 
            +
                    queue.should_receive(:bind).with(broker.exchange, routing_key: key)
         | 
| 99 | 
            +
                  end
         | 
| 100 | 
            +
                  broker.bind_queue(queue, routing_keys)
         | 
| 101 | 
            +
                end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                it 'calls unbind for each redundant existing binding' do
         | 
| 104 | 
            +
                  queue.should_receive(:unbind).with(broker.exchange, routing_key: 'd')
         | 
| 105 | 
            +
                  broker.bind_queue(queue, routing_keys)
         | 
| 106 | 
            +
                end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                context '(rabbitmq integration test)', rabbitmq: true do
         | 
| 109 | 
            +
                  let(:queue) { broker.queue('consumer') }
         | 
| 110 | 
            +
                  let(:routing_key) { 'key' }
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                  before { broker.unstub(:bindings) }
         | 
| 113 | 
            +
                  before { queue.bind(broker.exchange, routing_key: 'redundant-key') }
         | 
| 114 | 
            +
                  after { queue.unbind(broker.exchange, routing_key: routing_key).delete }
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                  it 'results in the correct bindings' do
         | 
| 117 | 
            +
                    broker.bind_queue(queue, [routing_key])
         | 
| 118 | 
            +
                    broker.bindings.should include({ queue.name => [routing_key] })
         | 
| 119 | 
            +
                  end
         | 
| 120 | 
            +
                end
         | 
| 121 | 
            +
              end
         | 
| 122 | 
            +
             | 
| 123 | 
            +
              describe '#wait_on_threads' do
         | 
| 124 | 
            +
                let(:thread) { double('Thread') }
         | 
| 125 | 
            +
                before { broker.stub(work_pool_threads: threads) }
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                context 'when all threads finish within the timeout' do
         | 
| 128 | 
            +
                  let(:threads) { [double(join: thread), double(join: thread)] }
         | 
| 129 | 
            +
                  specify { expect(broker.wait_on_threads(1)).to be_true }
         | 
| 130 | 
            +
                end
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                context 'when timeout expires for one thread' do
         | 
| 133 | 
            +
                  let(:threads) { [double(join: thread), double(join: nil)] }
         | 
| 134 | 
            +
                  specify { expect(broker.wait_on_threads(1)).to be_false }
         | 
| 135 | 
            +
                end
         | 
| 136 | 
            +
              end
         | 
| 137 | 
            +
             | 
| 138 | 
            +
              describe '#publish' do
         | 
| 139 | 
            +
                context 'with a valid connection' do
         | 
| 140 | 
            +
                  before { broker.set_up_amqp_connection }
         | 
| 141 | 
            +
                  after  { broker.disconnect }
         | 
| 142 | 
            +
             | 
| 143 | 
            +
                  it 'publishes to the exchange' do
         | 
| 144 | 
            +
                    broker.exchange.should_receive(:publish).once
         | 
| 145 | 
            +
                    broker.publish('test.key', 'message')
         | 
| 146 | 
            +
                  end
         | 
| 147 | 
            +
                end
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                context 'without a valid connection' do
         | 
| 150 | 
            +
                  it 'logs an error' do
         | 
| 151 | 
            +
                    broker.logger.should_receive(:error)
         | 
| 152 | 
            +
                    broker.publish('test.key', 'message')
         | 
| 153 | 
            +
                  end
         | 
| 154 | 
            +
                end
         | 
| 155 | 
            +
              end
         | 
| 156 | 
            +
            end
         | 
| 157 | 
            +
             | 
| @@ -0,0 +1,69 @@ | |
| 1 | 
            +
            require 'hutch/config'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe Hutch::Config do
         | 
| 4 | 
            +
              let(:new_value) { 'not-localhost' }
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              describe '.get' do
         | 
| 7 | 
            +
                context 'for valid attributes' do
         | 
| 8 | 
            +
                  subject { Hutch::Config.get(:mq_host) }
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  context 'with no overridden value' do
         | 
| 11 | 
            +
                    it { should == 'localhost' }
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  context 'with an overridden value' do
         | 
| 15 | 
            +
                    before  { Hutch::Config.stub(user_config: { mq_host: new_value }) }
         | 
| 16 | 
            +
                    it { should == new_value }
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                context 'for invalid attributes' do
         | 
| 21 | 
            +
                  let(:invalid_get) { ->{ Hutch::Config.get(:invalid_attr) } }
         | 
| 22 | 
            +
                  specify { invalid_get.should raise_error Hutch::UnknownAttributeError }
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              describe '.set' do
         | 
| 27 | 
            +
                context 'for valid attributes' do
         | 
| 28 | 
            +
                  before  { Hutch::Config.set(:mq_host, new_value) }
         | 
| 29 | 
            +
                  subject { Hutch::Config.user_config[:mq_host] }
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  context 'sets value in user config hash' do
         | 
| 32 | 
            +
                    it { should == new_value }
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                context 'for invalid attributes' do
         | 
| 37 | 
            +
                  let(:invalid_set) { ->{ Hutch::Config.set(:invalid_attr, new_value) } }
         | 
| 38 | 
            +
                  specify { invalid_set.should raise_error Hutch::UnknownAttributeError }
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
              describe 'a magic getter' do
         | 
| 43 | 
            +
                context 'for a valid attribute' do
         | 
| 44 | 
            +
                  it 'calls get' do
         | 
| 45 | 
            +
                    Hutch::Config.should_receive(:get).with(:mq_host)
         | 
| 46 | 
            +
                    Hutch::Config.mq_host
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                context 'for an invalid attribute' do
         | 
| 51 | 
            +
                  let(:invalid_getter) { ->{ Hutch::Config.invalid_attr } }
         | 
| 52 | 
            +
                  specify { invalid_getter.should raise_error NoMethodError }
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
              end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
              describe 'a magic setter' do
         | 
| 57 | 
            +
                context 'for a valid attribute' do
         | 
| 58 | 
            +
                  it 'calls set' do
         | 
| 59 | 
            +
                    Hutch::Config.should_receive(:set).with(:mq_host, new_value)
         | 
| 60 | 
            +
                    Hutch::Config.mq_host = new_value
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                context 'for an invalid attribute' do
         | 
| 65 | 
            +
                  let(:invalid_setter) { ->{ Hutch::Config.invalid_attr = new_value } }
         | 
| 66 | 
            +
                  specify { invalid_setter.should raise_error NoMethodError }
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
              end
         | 
| 69 | 
            +
            end
         | 
| @@ -0,0 +1,80 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe Hutch::Consumer do
         | 
| 4 | 
            +
              around(:each) do |example|
         | 
| 5 | 
            +
                isolate_constants do
         | 
| 6 | 
            +
                  example.run
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              let(:simple_consumer) do
         | 
| 11 | 
            +
                unless defined? SimpleConsumer
         | 
| 12 | 
            +
                  class SimpleConsumer
         | 
| 13 | 
            +
                    include Hutch::Consumer
         | 
| 14 | 
            +
                    consume 'hutch.test1'
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
                SimpleConsumer
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              let(:complex_consumer) do
         | 
| 21 | 
            +
                unless defined? ComplexConsumer
         | 
| 22 | 
            +
                  class ComplexConsumer
         | 
| 23 | 
            +
                    include Hutch::Consumer
         | 
| 24 | 
            +
                    consume 'hutch.test1', 'hutch.test2'
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
                ComplexConsumer
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              describe 'module inclusion' do
         | 
| 31 | 
            +
                it 'registers the class as a consumer' do
         | 
| 32 | 
            +
                  Hutch.should_receive(:register_consumer) do |klass|
         | 
| 33 | 
            +
                    klass.should == simple_consumer
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  simple_consumer
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
             | 
| 41 | 
            +
              describe '.consume' do
         | 
| 42 | 
            +
                it 'saves the routing key to the consumer' do
         | 
| 43 | 
            +
                  simple_consumer.routing_keys.should include 'hutch.test1'
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                context 'with multiple routing keys' do
         | 
| 47 | 
            +
                  it 'registers the class once for each routing key' do
         | 
| 48 | 
            +
                    complex_consumer.routing_keys.should include 'hutch.test1'
         | 
| 49 | 
            +
                    complex_consumer.routing_keys.should include 'hutch.test2'
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                context 'when given the same routing key multiple times' do
         | 
| 54 | 
            +
                  subject { simple_consumer.routing_keys }
         | 
| 55 | 
            +
                  before { simple_consumer.consume 'hutch.test1' }
         | 
| 56 | 
            +
                  its(:length) { should == 1}
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
              end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
              describe '.queue_name' do
         | 
| 61 | 
            +
                it 'replaces module separators with colons' do
         | 
| 62 | 
            +
                  module Foo
         | 
| 63 | 
            +
                    class Bar
         | 
| 64 | 
            +
                      include Hutch::Consumer
         | 
| 65 | 
            +
                    end
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  Foo::Bar.queue_name.should == 'foo:bar'
         | 
| 69 | 
            +
                end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                it 'converts camelcase class names to snake case' do
         | 
| 72 | 
            +
                  class FooBarBAZ
         | 
| 73 | 
            +
                    include Hutch::Consumer
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                  FooBarBAZ.queue_name.should == 'foo_bar_baz'
         | 
| 77 | 
            +
                end
         | 
| 78 | 
            +
              end
         | 
| 79 | 
            +
            end
         | 
| 80 | 
            +
             | 
| @@ -0,0 +1,15 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe Hutch::ErrorHandlers::Logger do
         | 
| 4 | 
            +
              let(:error_handler) { Hutch::ErrorHandlers::Logger.new }
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              describe '#handle' do
         | 
| 7 | 
            +
                let(:error) { stub(message: "Stuff went wrong", class: "RuntimeError",
         | 
| 8 | 
            +
                                   backtrace: ["line 1", "line 2"]) }
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                it "logs three separate lines" do
         | 
| 11 | 
            +
                  Hutch::Logging.logger.should_receive(:error).exactly(3).times
         | 
| 12 | 
            +
                  error_handler.handle("1", stub, error)
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
            end
         | 
| @@ -0,0 +1,20 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe Hutch::ErrorHandlers::Sentry do
         | 
| 4 | 
            +
              let(:error_handler) { Hutch::ErrorHandlers::Sentry.new }
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              describe '#handle' do
         | 
| 7 | 
            +
                let(:error) do
         | 
| 8 | 
            +
                  begin
         | 
| 9 | 
            +
                    raise "Stuff went wrong"
         | 
| 10 | 
            +
                  rescue RuntimeError => err
         | 
| 11 | 
            +
                    err
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                it "logs the error to Sentry" do
         | 
| 16 | 
            +
                  Raven.should_receive(:capture_exception).with(error)
         | 
| 17 | 
            +
                  error_handler.handle("1", stub, error)
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
            end
         | 
| @@ -0,0 +1,28 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe Hutch::Logging do
         | 
| 4 | 
            +
              let(:dummy_object) do
         | 
| 5 | 
            +
                class DummyObject
         | 
| 6 | 
            +
                  include Hutch::Logging
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              describe '#logger' do
         | 
| 11 | 
            +
                context 'with the default logger' do
         | 
| 12 | 
            +
                  subject { Hutch::Logging.logger }
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  it { should be_instance_of(Logger) }
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                context 'with a custom logger' do
         | 
| 18 | 
            +
                  let(:dummy_logger) { mock("Dummy logger", warn: true, info: true) }
         | 
| 19 | 
            +
                  after { Hutch::Logging.setup_logger }
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  it "users the custom logger" do
         | 
| 22 | 
            +
                    Hutch::Logging.logger = dummy_logger
         | 
| 23 | 
            +
                    Hutch::Logging.logger.should == dummy_logger
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
            end
         | 
| 28 | 
            +
             | 
| @@ -0,0 +1,35 @@ | |
| 1 | 
            +
            require 'hutch/message'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe Hutch::Message do
         | 
| 4 | 
            +
              let(:delivery_info) { double('Delivery Info') }
         | 
| 5 | 
            +
              let(:props) { double('Properties') }
         | 
| 6 | 
            +
              let(:body) {{ foo: 'bar' }}
         | 
| 7 | 
            +
              let(:json_body) { MultiJson.dump(body) }
         | 
| 8 | 
            +
              subject(:message) { Hutch::Message.new(delivery_info, props, json_body) }
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              its(:body) { should == body }
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              describe '[]' do
         | 
| 13 | 
            +
                subject { message[:foo] }
         | 
| 14 | 
            +
                it { should == 'bar' }
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              [:message_id, :timestamp].each do |method|
         | 
| 18 | 
            +
                describe method.to_s do
         | 
| 19 | 
            +
                  it 'delegates to @properties' do
         | 
| 20 | 
            +
                    props.should_receive(method)
         | 
| 21 | 
            +
                    message.send(method)
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              [:routing_key, :exchange].each do |method|
         | 
| 27 | 
            +
                describe method.to_s do
         | 
| 28 | 
            +
                  it 'delegates to @delivery_info' do
         | 
| 29 | 
            +
                    delivery_info.should_receive(method)
         | 
| 30 | 
            +
                    message.send(method)
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
              end
         | 
| 34 | 
            +
            end
         | 
| 35 | 
            +
             | 
| @@ -0,0 +1,80 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
            require 'hutch/worker'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            describe Hutch::Worker do
         | 
| 5 | 
            +
              before { Raven.as_null_object }
         | 
| 6 | 
            +
              let(:consumer) { double('Consumer', routing_keys: %w( a b c ),
         | 
| 7 | 
            +
                                      queue_name: 'consumer') }
         | 
| 8 | 
            +
              let(:consumers) { [consumer, double('Consumer')] }
         | 
| 9 | 
            +
              let(:broker) { Hutch::Broker.new }
         | 
| 10 | 
            +
              subject(:worker) { Hutch::Worker.new(broker, consumers) }
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              describe '#setup_queues' do
         | 
| 13 | 
            +
                it 'sets up queues for each of the consumers' do
         | 
| 14 | 
            +
                  consumers.each do |consumer|
         | 
| 15 | 
            +
                    worker.should_receive(:setup_queue).with(consumer)
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
                  worker.setup_queues
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
              describe '#setup_queue' do
         | 
| 22 | 
            +
                let(:queue) { double('Queue', bind: nil, subscribe: nil) }
         | 
| 23 | 
            +
                before { worker.stub(consumer_queue: queue) }
         | 
| 24 | 
            +
                before { broker.stub(queue: queue, bind_queue: nil) }
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                it 'creates a queue' do
         | 
| 27 | 
            +
                  broker.should_receive(:queue).with(consumer.queue_name).and_return(queue)
         | 
| 28 | 
            +
                  worker.setup_queue(consumer)
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                it 'binds the queue to each of the routing keys' do
         | 
| 32 | 
            +
                  broker.should_receive(:bind_queue).with(queue, %w( a b c ))
         | 
| 33 | 
            +
                  worker.setup_queue(consumer)
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                it 'sets up a subscription' do
         | 
| 37 | 
            +
                  queue.should_receive(:subscribe).with(ack: true)
         | 
| 38 | 
            +
                  worker.setup_queue(consumer)
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
              describe '#handle_message' do
         | 
| 43 | 
            +
                let(:payload) { '{}' }
         | 
| 44 | 
            +
                let(:consumer_instance) { double('Consumer instance') }
         | 
| 45 | 
            +
                let(:delivery_info) { double('Delivery Info', routing_key: '',
         | 
| 46 | 
            +
                                             delivery_tag: 'dt') }
         | 
| 47 | 
            +
                let(:properties) { double('Properties', message_id: nil) }
         | 
| 48 | 
            +
                before { consumer.stub(new: consumer_instance) }
         | 
| 49 | 
            +
                before { broker.stub(:ack) }
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                it 'passes the message to the consumer' do
         | 
| 52 | 
            +
                  consumer_instance.should_receive(:process).
         | 
| 53 | 
            +
                                    with(an_instance_of(Hutch::Message))
         | 
| 54 | 
            +
                  worker.handle_message(consumer, delivery_info, properties, payload)
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                it 'acknowledges the message' do
         | 
| 58 | 
            +
                  consumer_instance.stub(:process)
         | 
| 59 | 
            +
                  broker.should_receive(:ack).with(delivery_info.delivery_tag)
         | 
| 60 | 
            +
                  worker.handle_message(consumer, delivery_info, properties, payload)
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                context 'when the consumer raises an exception' do
         | 
| 64 | 
            +
                  before { consumer_instance.stub(:process).and_raise('a consumer error') }
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                  it 'logs the error' do
         | 
| 67 | 
            +
                    Hutch::Config[:error_handlers].each do |backend|
         | 
| 68 | 
            +
                      backend.should_receive(:handle)
         | 
| 69 | 
            +
                    end
         | 
| 70 | 
            +
                    worker.handle_message(consumer, delivery_info, properties, payload)
         | 
| 71 | 
            +
                  end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                  it 'acknowledges the message' do
         | 
| 74 | 
            +
                    broker.should_receive(:ack).with(delivery_info.delivery_tag)
         | 
| 75 | 
            +
                    worker.handle_message(consumer, delivery_info, properties, payload)
         | 
| 76 | 
            +
                  end
         | 
| 77 | 
            +
                end
         | 
| 78 | 
            +
              end
         | 
| 79 | 
            +
            end
         | 
| 80 | 
            +
             |