lapine 1.0.1 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 17ff6eda9eaea1807ee77240e11fa2dc336f7350
4
- data.tar.gz: b94a4cbada011e8f4fe12e6873b2def2ab923e27
3
+ metadata.gz: 564e7001cbbd4468d842e013cc437610a25b0c24
4
+ data.tar.gz: 90a86db4889bed75801d7278b0710b36e0c3f38c
5
5
  SHA512:
6
- metadata.gz: f68160452a12c78121c0175e2cbf8448924a6d4dd075de8e5051628240cb097b2e3bf8ecd6bf3b5779a83b3a8b24fb020fdd7b977caa71d236628bb44ea68d13
7
- data.tar.gz: 391d24cbd65e613f44f46df60eca15c1958c20466b01eb3425faea7448225127f45f6e78758ddc7f3b2b9ef56ce4259bfa1fe283c68c768b5bbde7bd214b9dbf
6
+ metadata.gz: 9096e3d40022bfb83e8aff47aac3f299e162cd644f56679c6294ba8bbd3d3438fa8df47b1fbfffe5f466743b208df7334ea0b2cb8d032ce0ee9a0b990224e4db
7
+ data.tar.gz: 41f326c4642272bf3ea3f9a32a964dae81e07dfea940edfcc14017b5828ebe0baf77206c17430f50754f7e8b39400581f1c98a2f1c34aeaee4c5b0fec69aed74
@@ -1,6 +1,21 @@
1
1
  Change Log
2
2
  ==========
3
3
 
4
+ ## 1.1.2
5
+
6
+ * Exchanges are saved using thread variables instead of fiber variables
7
+ * Move memoization of connections and exchanges to Configuration
8
+
9
+ ## 1.1.1
10
+
11
+ * Fix potential thread safety issue with publisher connections to
12
+ RabbitMQ
13
+
14
+ ## 1.1.0
15
+
16
+ * Lapine consumer can be configured with middleware
17
+ * Error handling, json decoding, and message acknowledgement now happen in middleware
18
+
4
19
  ## 1.0.1
5
20
 
6
21
  * Increased verbosity of errors
@@ -22,17 +22,17 @@ module Lapine
22
22
  end
23
23
 
24
24
  def self.find_exchange(name)
25
- exchange = config.exchange_properties[name]
26
- raise UndefinedExchange.new("No exchange for #{name}") unless exchange
27
- return config.exchanges[name].exchange if config.exchanges[name]
28
- config.exchanges[name] = Lapine::Exchange.new(name, exchange)
25
+ exchange = config.exchanges[name]
26
+ return exchange.exchange if (exchange && exchange.connected?)
27
+
28
+ exchange_configuration = config.exchange_properties[name]
29
+ raise UndefinedExchange.new("No exchange configuration for #{name}") unless exchange_configuration
30
+
31
+ config.exchanges[name] = Lapine::Exchange.new(name, exchange_configuration)
29
32
  config.exchanges[name].exchange
30
33
  end
31
34
 
32
35
  def self.close_connections!
33
- config.exchanges.values.each do |exchange|
34
- exchange.close!
35
- config.exchanges.delete exchange.name
36
- end
36
+ config.close_connections!
37
37
  end
38
38
  end
@@ -1,5 +1,9 @@
1
1
  module Lapine
2
2
  class Configuration
3
+ def initialize
4
+ @active_connections = {}
5
+ end
6
+
3
7
  def connections
4
8
  @connections ||= {}
5
9
  end
@@ -8,12 +12,58 @@ module Lapine
8
12
  @connection_properties ||= {}
9
13
  end
10
14
 
15
+ def channels_by_exchange_id
16
+ @channels_by_exchange_id ||= {}
17
+ end
18
+
19
+ def register_channel(object_id, channel)
20
+ channels_by_exchange_id[object_id] = channel
21
+ end
22
+
23
+ def cleanup_exchange(id)
24
+ return unless channels_by_exchange_id[id]
25
+ channel = channels_by_exchange_id[id]
26
+ channel.connection.logger.info "Closing channel for exchange #{id}, thread: #{Thread.current.object_id}"
27
+ channel.close
28
+ channels_by_exchange_id[id] = nil
29
+ end
30
+
31
+ # Exchanges need to be saved in a thread-local variable, rather than a fiber-local variable,
32
+ # because in the context of some applications (such as Sidekiq, which uses Celluloid) individual
33
+ # bits of work are done in fibers that are immediately reaped.
11
34
  def exchanges
12
- @exchanges ||= {}
35
+ Thread.current.thread_variable_get(:lapine_exchanges) ||
36
+ Thread.current.thread_variable_set(:lapine_exchanges, {})
13
37
  end
14
38
 
15
39
  def exchange_properties
16
40
  @exchange_properties ||= {}
17
41
  end
42
+
43
+ def active_connection(name)
44
+ conn = @active_connections[name]
45
+ return conn if (conn && conn.connected?)
46
+
47
+ @active_connections[name] = begin
48
+ @conn = Bunny.new(connection_props_for(name)).tap do |conn|
49
+ conn.start
50
+ end
51
+ end
52
+ end
53
+
54
+ def close_connections!
55
+ @active_connections.values.map(&:close)
56
+ @active_connections = {}
57
+ Thread.current.thread_variable_set(:lapine_exchanges, nil)
58
+ end
59
+
60
+ def connection_props_for(name)
61
+ return unless connection_properties[name]
62
+ connection_properties[name].dup.tap do |props|
63
+ if defined?(Rails)
64
+ props.merge!(logger: Rails.logger)
65
+ end
66
+ end
67
+ end
18
68
  end
19
69
  end
@@ -5,13 +5,12 @@ module Lapine
5
5
  module Consumer
6
6
  class Dispatcher
7
7
  class DefaultErrorHandler
8
-
9
8
  def call(e, data, _metadata)
10
9
  $stderr.puts "Lapine::Dispatcher unable to dispatch, #{e.message}, data: #{data}"
11
10
  end
12
11
  end
13
12
 
14
- attr_reader :delegate_class, :raw_payload, :metadata, :logger
13
+ attr_reader :delegate_class, :message, :payload
15
14
 
16
15
  def self.error_handler=(handler)
17
16
  @error_handler = handler
@@ -21,24 +20,16 @@ module Lapine
21
20
  @error_handler || DefaultErrorHandler.new
22
21
  end
23
22
 
24
- def initialize(delegate_class, raw_payload, metadata, logger)
23
+ def initialize(delegate_class, message)
25
24
  @delegate_class = delegate_class
26
- @raw_payload = raw_payload
27
- @metadata = metadata
28
- @logger = logger
25
+ @message = message
26
+ @payload = message.decoded_payload
29
27
  end
30
28
 
31
29
  def dispatch
32
- Lapine::DTrace.fire!(:dispatch_enter, delegate_class.name, raw_payload)
33
- begin
34
- json = Oj.load(raw_payload)
35
- with_timed_logging(json) { do_dispatch(json) }
36
- rescue Oj::Error => e
37
- self.class.error_handler.call(e, raw_payload, metadata)
38
- rescue StandardError => e
39
- self.class.error_handler.call(e, json, metadata)
40
- end
41
- Lapine::DTrace.fire!(:dispatch_return, delegate_class.name, raw_payload)
30
+ Lapine::DTrace.fire!(:dispatch_enter, delegate_class.name, message.payload)
31
+ with_timed_logging(payload) { do_dispatch(payload) }
32
+ Lapine::DTrace.fire!(:dispatch_return, delegate_class.name, message.payload)
42
33
  end
43
34
 
44
35
  private
@@ -48,7 +39,7 @@ module Lapine
48
39
  ret = yield
49
40
  time_end = Time.now
50
41
  duration = (time_end - time) * 1000
51
- logger.info "Processing rabbit message handler:#{delegate_class.name} duration(ms):#{duration} payload:#{json.inspect}"
42
+ message.logger.info "Processing rabbit message handler:#{delegate_class.name} duration(ms):#{duration} payload:#{json.inspect}"
52
43
  ret
53
44
  end
54
45
 
@@ -58,7 +49,7 @@ module Lapine
58
49
 
59
50
  def do_dispatch(payload)
60
51
  delegate_method_names.each do |meth|
61
- return delegate_class.send(meth, payload, metadata) if delegate_class.respond_to?(meth)
52
+ return delegate_class.send(meth, payload, message.metadata) if delegate_class.respond_to?(meth)
62
53
  end
63
54
  end
64
55
  end
@@ -0,0 +1,28 @@
1
+ module Lapine
2
+ module Consumer
3
+ class Message < Hash
4
+ def initialize(payload, metadata, logger)
5
+ super(nil)
6
+ self['payload'] = payload
7
+ self['metadata'] = metadata
8
+ self['logger'] = logger
9
+ end
10
+
11
+ def payload
12
+ self['payload']
13
+ end
14
+
15
+ def decoded_payload
16
+ self['decoded_payload']
17
+ end
18
+
19
+ def metadata
20
+ self['metadata']
21
+ end
22
+
23
+ def logger
24
+ self['logger']
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,129 @@
1
+ require 'lapine/consumer/middleware/error_handler'
2
+ require 'lapine/consumer/middleware/message_ack_handler'
3
+ require 'lapine/consumer/middleware/json_decoder'
4
+
5
+ module Lapine
6
+ module Consumer
7
+ #
8
+ # Consumer middleware can be registered as follows:
9
+ #
10
+ # Lapine::Consumer::Middleware.add MyClass
11
+ # Lapine::Consumer::Middleware.add MyClass, argument
12
+ # Lapine::Consumer::Middleware.add_before MyClass, MyOtherClass, argument
13
+ # Lapine::Consumer::Middleware.add_after MyClass, MyOtherClass, argument
14
+ #
15
+ # Middleware should follow the pattern:
16
+ #
17
+ # class MyMiddleware
18
+ # attr_reader :app
19
+ #
20
+ # def initialize(app, *arguments)
21
+ # @app = app
22
+ # end
23
+ #
24
+ # def call(message)
25
+ # # do stuff
26
+ # app.call(message)
27
+ # end
28
+ # end
29
+ #
30
+ module Middleware
31
+ # A Register of a middleware class that messages will be passed through
32
+ # on the way to being dispatched.
33
+ class Register < Struct.new(:klass, :args)
34
+ def create_new(app)
35
+ klass.new(app, *args)
36
+ end
37
+ end
38
+
39
+ DEFAULT_MIDDLEWARE = [
40
+ Register.new(MessageAckHandler),
41
+ Register.new(ErrorHandler),
42
+ Register.new(JsonDecoder)
43
+ ].freeze
44
+
45
+ class << self
46
+ def add(klass, *args)
47
+ registry << [klass, args]
48
+ end
49
+
50
+ def add_before(before_klass, klass, *args)
51
+ idx = registry.index_of(before_klass)
52
+ raise MiddlewareNotFound.new("#{before_klass} not registered in Lapine middleware") unless idx
53
+ registry.insert(idx, klass, args)
54
+ end
55
+
56
+ def add_after(after_klass, klass, *args)
57
+ idx = registry.index_of(after_klass)
58
+ raise MiddlewareNotFound.new("#{after_klass} not registered in Lapine middleware") unless idx
59
+ registry.insert(idx + 1, klass, args)
60
+ end
61
+
62
+ def delete(klass)
63
+ registry.delete(klass)
64
+ end
65
+
66
+ def registry
67
+ @registry ||= Registry.new(DEFAULT_MIDDLEWARE.dup)
68
+ end
69
+
70
+ def create_chain(app)
71
+ registry.map { |r| r.create_new(app) }
72
+ end
73
+
74
+ def app
75
+ App.new.tap do |app|
76
+ app.chain = create_chain(app)
77
+ end
78
+ end
79
+ end
80
+
81
+ class App
82
+ attr_accessor :chain
83
+
84
+ def call(message, &block)
85
+ chain << block if block_given?
86
+ current_register = chain.shift
87
+ current_register.call(message) if current_register
88
+ end
89
+ end
90
+
91
+ # Registry holds records of each middleware class that is added to the
92
+ # consumer middleware chain.
93
+ class Registry
94
+ include Enumerable
95
+
96
+ attr_reader :registry
97
+
98
+ def initialize(registry = [])
99
+ @registry = registry
100
+ end
101
+
102
+ def all
103
+ registry
104
+ end
105
+
106
+ def each(&blk)
107
+ all.each(&blk)
108
+ end
109
+
110
+ def delete(klass)
111
+ registry.reject! { |register| register.klass == klass }
112
+ end
113
+
114
+ def <<(klass_args)
115
+ insert(-1, klass_args[0], klass_args[1])
116
+ end
117
+
118
+ def index_of(klass)
119
+ registry.find_index { |register| register.klass == klass }
120
+ end
121
+
122
+ def insert(index, klass, args)
123
+ raise Lapine::DuplicateMiddleware if index_of(klass)
124
+ registry.insert(index, Register.new(klass, args))
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,19 @@
1
+ module Lapine
2
+ module Consumer
3
+ module Middleware
4
+ class ErrorHandler
5
+ attr_reader :app
6
+
7
+ def initialize(app)
8
+ @app = app
9
+ end
10
+
11
+ def call(message)
12
+ app.call(message)
13
+ rescue StandardError => e
14
+ Lapine::Consumer::Dispatcher.error_handler.call(e, message.payload, message.metadata)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ require 'oj'
2
+
3
+ module Lapine
4
+ module Consumer
5
+ module Middleware
6
+ class JsonDecoder
7
+ attr_reader :app
8
+
9
+ def initialize(app)
10
+ @app = app
11
+ end
12
+
13
+ def call(message)
14
+ message['decoded_payload'] = Oj.load(message.payload)
15
+ app.call(message)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ module Lapine
2
+ module Consumer
3
+ module Middleware
4
+ class MessageAckHandler
5
+ attr_reader :app
6
+
7
+ def initialize(app)
8
+ @app = app
9
+ end
10
+
11
+ def call(message)
12
+ app.call(message)
13
+ ensure
14
+ message.metadata.ack
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -5,6 +5,8 @@ require 'lapine/annotated_logger'
5
5
  require 'lapine/consumer/config'
6
6
  require 'lapine/consumer/connection'
7
7
  require 'lapine/consumer/environment'
8
+ require 'lapine/consumer/message'
9
+ require 'lapine/consumer/middleware'
8
10
  require 'lapine/consumer/topology'
9
11
  require 'lapine/consumer/dispatcher'
10
12
 
@@ -29,15 +31,18 @@ module Lapine
29
31
  topology.each_binding do |q, conn, routing_key, classes|
30
32
  queue = conn.channel.queue(q, @queue_properties).bind(conn.exchange, routing_key: routing_key)
31
33
  queue.subscribe(ack: true) do |metadata, payload|
32
- classes.each do |clazz|
33
- Lapine::Consumer::Dispatcher.new(clazz, payload, metadata, logger).dispatch
34
- end
35
34
 
36
- if config.debug?
37
- @message_count += 1
38
- @running_message_count += 1
35
+ message = Consumer::Message.new(payload, metadata, logger)
36
+ Middleware.app.call(message) do |message|
37
+ classes.each do |clazz|
38
+ Lapine::Consumer::Dispatcher.new(clazz, message).dispatch
39
+ end
40
+
41
+ if config.debug?
42
+ @message_count += 1
43
+ @running_message_count += 1
44
+ end
39
45
  end
40
- metadata.ack
41
46
 
42
47
  EventMachine.stop_event_loop if should_exit?
43
48
  end
@@ -3,4 +3,7 @@ module Lapine
3
3
  class UndefinedConnection < LapineError; end
4
4
  class UndefinedExchange < LapineError; end
5
5
  class NilExchange < LapineError; end
6
+
7
+ class MiddlewareNotFound < LapineError; end
8
+ class DuplicateMiddleware < LapineError; end
6
9
  end
@@ -9,24 +9,26 @@ module Lapine
9
9
  @props = properties.dup
10
10
  @connection_name = props.delete(:connection)
11
11
  @exchange_type = props.delete(:type)
12
+ ObjectSpace.define_finalizer(self, proc { |id| Lapine.config.cleanup_exchange(id) })
12
13
  end
13
14
 
14
- def exchange
15
- reconnect unless @exchange && conn && conn.connected?
16
- raise Lapine::NilExchange unless @exchange
17
- @exchange
15
+ def connected?
16
+ @exchange.channel.connection.connected? &&
17
+ @exchange.channel.open?
18
18
  end
19
19
 
20
- def reconnect
21
- connection_props = Lapine.config.connection_properties[connection_name]
22
- @conn = Bunny.new(connection_props)
23
- conn.start
24
- channel = conn.create_channel
25
- @exchange = Bunny::Exchange.new(channel, exchange_type, name, props)
20
+ def exchange
21
+ @exchange ||= begin
22
+ conn = Lapine.config.active_connection(connection_name)
23
+ conn.logger.info "Creating channel for #{self.object_id}, thread: #{Thread.current.object_id}"
24
+ channel = conn.create_channel
25
+ Lapine.config.register_channel(self.object_id, channel)
26
+ Bunny::Exchange.new(channel, exchange_type, name, props)
27
+ end
26
28
  end
27
29
 
28
- def close!
29
- conn.close if conn.connected?
30
+ def close
31
+ @exchange.channel.close
30
32
  end
31
33
  end
32
34
  end
@@ -84,6 +84,10 @@ module Lapine
84
84
  @exchange = nil
85
85
  true
86
86
  end
87
+
88
+ def connected?
89
+ true
90
+ end
87
91
  end
88
92
  end
89
93
  end
@@ -1,3 +1,3 @@
1
1
  module Lapine
2
- VERSION = '1.0.1'
2
+ VERSION = '1.1.2'
3
3
  end
@@ -3,12 +3,18 @@ require 'lapine/consumer/dispatcher'
3
3
 
4
4
  RSpec.describe Lapine::Consumer::Dispatcher do
5
5
 
6
- subject(:dispatcher) { Lapine::Consumer::Dispatcher.new(delegate, json, metadata, logger) }
6
+ subject(:dispatcher) { Lapine::Consumer::Dispatcher.new(delegate, message) }
7
+ let(:message) do
8
+ Lapine::Consumer::Message.new(json, metadata, logger).tap do |message|
9
+ message['decoded_payload'] = hash
10
+ end
11
+ end
12
+
7
13
  let(:logger) { double('logger') }
8
14
  let(:hash) { {'foo' => 'bar'} }
9
15
  let(:json) { Oj.dump(hash) }
10
- let(:metadata) { double("metadata") }
11
- let(:delegate) { double("delegate", name: "ClassName") }
16
+ let(:metadata) { double('metadata') }
17
+ let(:delegate) { double('delegate', name: 'ClassName') }
12
18
 
13
19
  let(:caught_errors) { [] }
14
20
 
@@ -16,22 +22,22 @@ RSpec.describe Lapine::Consumer::Dispatcher do
16
22
  Lapine::Consumer::Dispatcher.error_handler = nil
17
23
  end
18
24
 
19
- describe "#delegation" do
20
- context "success cases" do
25
+ describe '#delegation' do
26
+ context 'success cases' do
21
27
  before do
22
28
  expect(logger).to receive(:info).once.with(/Processing(.*)ClassName/)
23
29
  end
24
30
 
25
- context ".handle_lapine_payload method" do
26
- it "receives handle_lapine_payload" do
31
+ context '.handle_lapine_payload method' do
32
+ it 'receives handle_lapine_payload' do
27
33
  expect(delegate).to receive(:respond_to?).with(:handle_lapine_payload).and_return(true)
28
34
  expect(delegate).to receive(:handle_lapine_payload).once
29
35
  dispatcher.dispatch
30
36
  end
31
37
  end
32
38
 
33
- context ".perform_async method" do
34
- it "receives perform_async" do
39
+ context '.perform_async method' do
40
+ it 'receives perform_async' do
35
41
  expect(delegate).to receive(:respond_to?).with(:handle_lapine_payload).and_return(false)
36
42
  expect(delegate).to receive(:respond_to?).with(:perform_async).and_return(true)
37
43
  expect(delegate).to receive(:perform_async).once
@@ -39,45 +45,16 @@ RSpec.describe Lapine::Consumer::Dispatcher do
39
45
  end
40
46
  end
41
47
  end
48
+ end
49
+ end
42
50
 
43
- describe 'error cases' do
44
-
45
- context 'custom error handler' do
46
- before do
47
- Lapine::Consumer::Dispatcher.error_handler = ->(error, data, metadata) {
48
- caught_errors << [error, data, metadata]
49
- }
50
- end
51
- context 'with invalid json' do
52
- let(:json) { 'oh boy I am not actually JSON' }
53
-
54
- it 'notifies the error handler with the raw payload' do
55
- dispatcher.dispatch
56
- expect(caught_errors).to include([an_instance_of(Oj::ParseError), json, metadata])
57
- end
58
- end
59
-
60
- context 'with any other error' do
61
- before { allow(dispatcher).to receive(:do_dispatch).and_raise(ArgumentError) }
62
-
63
- it 'notifies error handler with the parsed json' do
64
- dispatcher.dispatch
65
- expect(caught_errors).to include([an_instance_of(ArgumentError), hash, metadata])
66
- end
67
- end
68
- end
69
-
70
- context 'default error handler' do
71
- before { allow(dispatcher).to receive(:do_dispatch).and_raise(ArgumentError) }
72
-
73
- it 'notifies default error handler' do
74
- expect($stderr).to receive(:puts)
75
- dispatcher.dispatch
76
- end
77
- end
78
-
51
+ RSpec.describe Lapine::Consumer::Dispatcher::DefaultErrorHandler do
52
+ let(:payload) { double('payload') }
53
+ let(:metadata) { double('metadata') }
79
54
 
80
- end
55
+ it 'puts to stderr' do
56
+ expect($stderr).to receive(:puts)
57
+ Lapine::Consumer::Dispatcher::DefaultErrorHandler.new.call(StandardError.new, payload, metadata)
81
58
  end
82
59
  end
83
60
 
@@ -0,0 +1,166 @@
1
+ require 'spec_helper'
2
+ require 'lapine/consumer/dispatcher'
3
+ require 'lapine/consumer/middleware'
4
+
5
+ RSpec.describe Lapine::Consumer::Middleware do
6
+ class MiddlewareAddLetter
7
+ def initialize(app, letter)
8
+ @app = app
9
+ @letter = letter
10
+ end
11
+
12
+ def call(message)
13
+ message['letter'] = @letter
14
+ @app.call(message)
15
+ end
16
+ end
17
+
18
+ class MiddlewareCopyLetter
19
+ def initialize(app)
20
+ @app = app
21
+ end
22
+
23
+ def call(message)
24
+ message['middleware.copy_letter.ran'] = true
25
+ message['duplicate_letter'] = message['letter']
26
+ @app.call(message)
27
+ end
28
+ end
29
+
30
+ class RaisingMiddleware
31
+ def initialize(app)
32
+ @app = app
33
+ end
34
+
35
+ def call(_message)
36
+ raise StandardError.new('Raise')
37
+ end
38
+ end
39
+
40
+ class CatchingMiddleWare
41
+ def initialize(app)
42
+ @app = app
43
+ end
44
+
45
+ def call(message)
46
+ @app.call(message)
47
+ rescue StandardError => e
48
+ message['error_message'] = e.message
49
+ end
50
+ end
51
+
52
+ let(:metadata) { double('metadata', ack: true) }
53
+ let(:payload) { '{}' }
54
+ let(:message) { Lapine::Consumer::Message.new(payload, metadata, nil) }
55
+
56
+ describe '.add' do
57
+ before do
58
+ Lapine::Consumer::Middleware.tap do |middleware|
59
+ middleware.add MiddlewareAddLetter, 'f'
60
+ end
61
+ end
62
+
63
+ it 'is adds letter to hash' do
64
+ Lapine::Consumer::Middleware.app.call(message) do |message|
65
+ expect(message['letter']).to eq('f')
66
+ end
67
+ end
68
+
69
+ context 'when duplicate middleware is added' do
70
+ it 'raises' do
71
+ expect {
72
+ Lapine::Consumer::Middleware.tap do |middleware|
73
+ middleware.add MiddlewareAddLetter, 'f'
74
+ end
75
+ }.to raise_error(Lapine::DuplicateMiddleware)
76
+ end
77
+ end
78
+ end
79
+
80
+ describe '.delete' do
81
+ let(:registry) { Lapine::Consumer::Middleware.registry }
82
+
83
+ before do
84
+ Lapine::Consumer::Middleware.tap do |middleware|
85
+ middleware.add MiddlewareAddLetter, 'f'
86
+ end
87
+ end
88
+
89
+ it 'removes register that matches class name' do
90
+ expect(registry.index_of(MiddlewareAddLetter)).to be
91
+ Lapine::Consumer::Middleware.delete(MiddlewareAddLetter)
92
+ expect(registry.index_of(MiddlewareAddLetter)).not_to be
93
+ end
94
+ end
95
+
96
+ describe 'error handling' do
97
+ describe 'with default middleware' do
98
+ let(:error) { StandardError.new('doh') }
99
+
100
+ it 'runs through the dispatcher error_handler' do
101
+ errors = []
102
+ Lapine::Consumer::Dispatcher.error_handler = ->(e, data, md) {
103
+ errors << [e, data, md]
104
+ }
105
+ Lapine::Consumer::Middleware.app.call(message) { raise error }
106
+ expect(errors).to include([error, message.payload, message.metadata])
107
+ end
108
+ end
109
+
110
+ describe 'with custom middleware' do
111
+ before do
112
+ Lapine::Consumer::Middleware.tap do |middleware|
113
+ middleware.add CatchingMiddleWare
114
+ middleware.add RaisingMiddleware
115
+ end
116
+ end
117
+
118
+ it 'catches error' do
119
+ Lapine::Consumer::Middleware.app.call(message)
120
+ expect(message['error_message']).to eq('Raise')
121
+ end
122
+
123
+ it 'halts execution' do
124
+ expectation = double(called: true)
125
+ Lapine::Consumer::Middleware.app.call(message) do
126
+ expectation.called
127
+ end
128
+ expect(expectation).not_to have_received(:called)
129
+ end
130
+ end
131
+ end
132
+
133
+ describe '.add_before' do
134
+ before do
135
+ Lapine::Consumer::Middleware.tap do |middleware|
136
+ middleware.add MiddlewareAddLetter, 'f'
137
+ middleware.add_before MiddlewareAddLetter, MiddlewareCopyLetter
138
+ end
139
+
140
+ it 'prepends middleware' do
141
+ Lapine::Consumer::Middleware.app.call(message) do |message|
142
+ expect(message['letter']).to eq('f')
143
+ expect(message['duplicate_letter']).to be nil
144
+ expect(message['middleware.copy_letter.ran']).to be true
145
+ end
146
+ end
147
+ end
148
+ end
149
+
150
+ context '.add_after' do
151
+ before do
152
+ Lapine::Consumer::Middleware.tap do |middleware|
153
+ middleware.add MiddlewareAddLetter, 'f'
154
+ middleware.add_after MiddlewareAddLetter, MiddlewareCopyLetter
155
+ end
156
+
157
+ it 'prepends middleware' do
158
+ Lapine::Consumer::Middleware.app.call(message) do |message|
159
+ expect(message['letter']).to eq('f')
160
+ expect(message['duplicate_letter']).to be 'f'
161
+ expect(message['middleware.copy_letter.ran']).to be true
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
@@ -1,8 +1,10 @@
1
1
  require 'spec_helper'
2
2
  require 'lapine'
3
+ require 'lapine/consumer'
3
4
 
4
5
  RSpec.describe Lapine do
5
- let(:connection) { double('connection') }
6
+ let(:connection) { double('connection', close: true, logger: logger) }
7
+ let(:logger) { double('bunny logger', info: true) }
6
8
  let(:connection_properties) do
7
9
  {
8
10
  host: 'someplace.com'
@@ -47,18 +49,20 @@ RSpec.describe Lapine do
47
49
  end
48
50
 
49
51
  describe '.close_connections!' do
50
- let(:connection1) { double('blah connection', close!: true, name: 'blah') }
51
- let(:connection2) { double('blargh connection', close!: true, name: 'blargh') }
52
+ let(:connection1) { double('blah connection', close: true, name: 'blah') }
53
+ let(:connection2) { double('blargh connection', close: true, name: 'blargh') }
52
54
 
53
55
  before do
54
- config.exchanges['blah'] = connection1
55
- config.exchanges['blargh'] = connection2
56
+ config.instance_variable_set(:@active_connections, {
57
+ 'blah' => connection1,
58
+ 'blargh' => connection2
59
+ })
56
60
  end
57
61
 
58
62
  it 'calls close on each connection' do
59
63
  Lapine.close_connections!
60
- expect(connection1).to have_received(:close!)
61
- expect(connection2).to have_received(:close!)
64
+ expect(connection1).to have_received(:close)
65
+ expect(connection2).to have_received(:close)
62
66
  end
63
67
 
64
68
  it 'clears the exchanges' do
@@ -81,8 +85,8 @@ RSpec.describe Lapine do
81
85
  end
82
86
 
83
87
  context 'when exchange has been registered' do
84
- let(:channel) { double('channel') }
85
- let(:exchange) { double('exchange') }
88
+ let(:channel) { double('channel', connection: connection, open?: true) }
89
+ let(:exchange) { double('exchange', channel: channel) }
86
90
 
87
91
  before do
88
92
  allow(connection).to receive(:start)
@@ -20,7 +20,13 @@ RSpec.configure do |config|
20
20
  Kernel.srand config.seed
21
21
 
22
22
  config.before :each do
23
+ Lapine::Consumer::Dispatcher.error_handler = nil
23
24
  Lapine.instance_variable_set(:@config, nil)
25
+ Lapine::Consumer::Middleware.instance_variable_set(:@registry,nil)
26
+ Thread.current.thread_variable_set(:lapine_exchanges, nil)
27
+ end
24
28
 
29
+ config.after :each do
30
+ Lapine.close_connections!
25
31
  end
26
32
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lapine
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Saxby
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-02-27 00:00:00.000000000 Z
12
+ date: 2015-03-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: amqp
@@ -181,6 +181,11 @@ files:
181
181
  - lib/lapine/consumer/connection.rb
182
182
  - lib/lapine/consumer/dispatcher.rb
183
183
  - lib/lapine/consumer/environment.rb
184
+ - lib/lapine/consumer/message.rb
185
+ - lib/lapine/consumer/middleware.rb
186
+ - lib/lapine/consumer/middleware/error_handler.rb
187
+ - lib/lapine/consumer/middleware/json_decoder.rb
188
+ - lib/lapine/consumer/middleware/message_ack_handler.rb
184
189
  - lib/lapine/consumer/runner.rb
185
190
  - lib/lapine/consumer/topology.rb
186
191
  - lib/lapine/dtrace.rb
@@ -193,6 +198,7 @@ files:
193
198
  - spec/lib/lapine/consumer/config_spec.rb
194
199
  - spec/lib/lapine/consumer/connection_spec.rb
195
200
  - spec/lib/lapine/consumer/dispatcher_spec.rb
201
+ - spec/lib/lapine/consumer/middleware_spec.rb
196
202
  - spec/lib/lapine/consumer/runner_spec.rb
197
203
  - spec/lib/lapine/consumer/topology_spec.rb
198
204
  - spec/lib/lapine/publisher_spec.rb
@@ -228,6 +234,7 @@ test_files:
228
234
  - spec/lib/lapine/consumer/config_spec.rb
229
235
  - spec/lib/lapine/consumer/connection_spec.rb
230
236
  - spec/lib/lapine/consumer/dispatcher_spec.rb
237
+ - spec/lib/lapine/consumer/middleware_spec.rb
231
238
  - spec/lib/lapine/consumer/runner_spec.rb
232
239
  - spec/lib/lapine/consumer/topology_spec.rb
233
240
  - spec/lib/lapine/publisher_spec.rb