message-driver 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.coveralls.yml +2 -0
  3. data/.rubocop.yml +26 -2
  4. data/.rubocop_todo.yml +15 -123
  5. data/.travis.yml +2 -1
  6. data/CHANGELOG.md +10 -1
  7. data/Gemfile +15 -6
  8. data/Rakefile +23 -12
  9. data/ci/reset_vhost +8 -3
  10. data/ci/travis_setup +0 -3
  11. data/features/.nav +6 -1
  12. data/features/CHANGELOG.md +10 -1
  13. data/features/amqp_specific_features/binding_amqp_destinations.feature +1 -0
  14. data/features/amqp_specific_features/declaring_amqp_exchanges.feature +1 -0
  15. data/features/amqp_specific_features/server_named_destinations.feature +1 -0
  16. data/features/destination_metadata.feature +26 -0
  17. data/features/logging.feature +1 -1
  18. data/features/middleware/middleware_basics.feature +91 -0
  19. data/features/middleware/middleware_ordering.feature +60 -0
  20. data/features/middleware/middleware_parameters.feature +43 -0
  21. data/features/middleware/middleware_with_blocks.feature +85 -0
  22. data/features/step_definitions/dynamic_destinations_steps.rb +1 -1
  23. data/features/step_definitions/message_consumers_steps.rb +5 -0
  24. data/features/step_definitions/middleware_steps.rb +10 -0
  25. data/features/step_definitions/steps.rb +10 -2
  26. data/features/support/env.rb +4 -3
  27. data/features/support/firewall_helper.rb +1 -1
  28. data/features/support/message_table_matcher.rb +3 -2
  29. data/features/support/no_error_matcher.rb +2 -2
  30. data/features/support/test_runner.rb +11 -57
  31. data/features/support/transforms.rb +12 -10
  32. data/lib/message_driver.rb +3 -1
  33. data/lib/message_driver/adapters/base.rb +11 -11
  34. data/lib/message_driver/adapters/bunny_adapter.rb +189 -132
  35. data/lib/message_driver/adapters/in_memory_adapter.rb +51 -34
  36. data/lib/message_driver/adapters/stomp_adapter.rb +22 -23
  37. data/lib/message_driver/broker.rb +21 -16
  38. data/lib/message_driver/client.rb +15 -16
  39. data/lib/message_driver/destination.rb +26 -8
  40. data/lib/message_driver/message.rb +5 -4
  41. data/lib/message_driver/middleware.rb +8 -0
  42. data/lib/message_driver/middleware/base.rb +19 -0
  43. data/lib/message_driver/middleware/block_middleware.rb +33 -0
  44. data/lib/message_driver/middleware/middleware_stack.rb +61 -0
  45. data/lib/message_driver/subscription.rb +2 -2
  46. data/lib/message_driver/version.rb +1 -1
  47. data/message-driver.gemspec +3 -4
  48. data/spec/integration/bunny/amqp_integration_spec.rb +21 -82
  49. data/spec/integration/bunny/bunny_adapter_spec.rb +288 -268
  50. data/spec/integration/in_memory/in_memory_adapter_spec.rb +93 -90
  51. data/spec/integration/stomp/stomp_adapter_spec.rb +126 -93
  52. data/spec/spec_helper.rb +11 -9
  53. data/spec/support/shared/adapter_examples.rb +1 -1
  54. data/spec/support/shared/client_ack_examples.rb +4 -4
  55. data/spec/support/shared/context_examples.rb +6 -4
  56. data/spec/support/shared/destination_examples.rb +54 -14
  57. data/spec/support/shared/subscription_examples.rb +33 -26
  58. data/spec/support/shared/transaction_examples.rb +12 -12
  59. data/spec/support/utils.rb +1 -1
  60. data/spec/units/message_driver/adapters/base_spec.rb +42 -40
  61. data/spec/units/message_driver/broker_spec.rb +38 -38
  62. data/spec/units/message_driver/client_spec.rb +87 -87
  63. data/spec/units/message_driver/destination_spec.rb +16 -11
  64. data/spec/units/message_driver/message_spec.rb +96 -70
  65. data/spec/units/message_driver/middleware/base_spec.rb +30 -0
  66. data/spec/units/message_driver/middleware/block_middleware_spec.rb +82 -0
  67. data/spec/units/message_driver/middleware/middleware_stack_spec.rb +165 -0
  68. data/spec/units/message_driver/subscription_spec.rb +18 -16
  69. data/test_lib/broker_config.rb +21 -5
  70. data/test_lib/coverage.rb +11 -0
  71. data/test_lib/provider/base.rb +59 -0
  72. data/test_lib/provider/in_memory.rb +6 -0
  73. data/test_lib/provider/rabbitmq.rb +67 -0
  74. metadata +46 -35
@@ -3,16 +3,17 @@ module MessageDriver
3
3
  class Base
4
4
  include Logging
5
5
 
6
- attr_reader :ctx, :body, :headers, :properties
6
+ attr_reader :ctx, :body, :raw_body, :headers, :properties
7
7
 
8
- def initialize(ctx, body, headers, properties)
8
+ def initialize(ctx, body, headers, properties, raw_body = nil)
9
9
  @ctx = ctx
10
10
  @body = body
11
11
  @headers = headers
12
12
  @properties = properties
13
+ @raw_body = raw_body.nil? ? body : raw_body
13
14
  end
14
15
 
15
- def ack(options={})
16
+ def ack(options = {})
16
17
  if ctx.supports_client_acks?
17
18
  ctx.ack_message(self, options)
18
19
  else
@@ -20,7 +21,7 @@ module MessageDriver
20
21
  end
21
22
  end
22
23
 
23
- def nack(options={})
24
+ def nack(options = {})
24
25
  if ctx.supports_client_acks?
25
26
  ctx.nack_message(self, options)
26
27
  else
@@ -0,0 +1,8 @@
1
+ require 'message_driver/middleware/base'
2
+ require 'message_driver/middleware/block_middleware'
3
+ require 'message_driver/middleware/middleware_stack'
4
+
5
+ module MessageDriver
6
+ module Middleware
7
+ end
8
+ end
@@ -0,0 +1,19 @@
1
+ module MessageDriver
2
+ module Middleware
3
+ class Base
4
+ attr_reader :destination
5
+
6
+ def initialize(*args)
7
+ @destination = args.shift
8
+ end
9
+
10
+ def on_publish(body, headers, properties)
11
+ [body, headers, properties]
12
+ end
13
+
14
+ def on_consume(body, headers, properties)
15
+ [body, headers, properties]
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,33 @@
1
+ module MessageDriver
2
+ module Middleware
3
+ class BlockMiddleware < Base
4
+ attr_reader :on_publish_block, :on_consume_block
5
+
6
+ def initialize(destination, opts)
7
+ super(destination)
8
+ fail ArgumentError, 'you must provide at least one of :on_publish and :on_consume' \
9
+ unless opts.keys.any? { |k| [:on_publish, :on_consume].include? k }
10
+ @on_publish_block = opts[:on_publish]
11
+ @on_consume_block = opts[:on_consume]
12
+ end
13
+
14
+ def on_publish(body, headers, properties)
15
+ delegate_to_block(on_publish_block, body, headers, properties)
16
+ end
17
+
18
+ def on_consume(body, headers, properties)
19
+ delegate_to_block(on_consume_block, body, headers, properties)
20
+ end
21
+
22
+ private
23
+
24
+ def delegate_to_block(block, body, headers, properties)
25
+ if block.nil?
26
+ [body, headers, properties]
27
+ else
28
+ block.call(body, headers, properties)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,61 @@
1
+ module MessageDriver
2
+ module Middleware
3
+ class MiddlewareStack
4
+ include Enumerable
5
+
6
+ attr_reader :destination
7
+
8
+ def initialize(destination)
9
+ @destination = destination
10
+ @middlewares = []
11
+ end
12
+
13
+ def middlewares
14
+ @middlewares.dup.freeze
15
+ end
16
+
17
+ def append(middleware_class, *args)
18
+ middleware = build_middleware(middleware_class, *args)
19
+ @middlewares << middleware
20
+ middleware
21
+ end
22
+
23
+ def prepend(middleware_class, *args)
24
+ middleware = build_middleware(middleware_class, *args)
25
+ @middlewares.unshift middleware
26
+ middleware
27
+ end
28
+
29
+ def on_publish(body, headers, properties)
30
+ @middlewares.reduce([body, headers, properties]) do |args, middleware|
31
+ middleware.on_publish(*args)
32
+ end
33
+ end
34
+
35
+ def on_consume(body, headers, properties)
36
+ @middlewares.reverse.reduce([body, headers, properties]) do |args, middleware|
37
+ middleware.on_consume(*args)
38
+ end
39
+ end
40
+
41
+ def empty?
42
+ @middlewares.empty?
43
+ end
44
+
45
+ def each
46
+ @middlewares.each { |x| yield x }
47
+ end
48
+
49
+ private
50
+
51
+ def build_middleware(middleware_type, *args)
52
+ case middleware_type
53
+ when Hash
54
+ BlockMiddleware.new(destination, middleware_type)
55
+ else
56
+ middleware_type.new(destination, *args)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -4,7 +4,7 @@ module MessageDriver
4
4
  include Logging
5
5
  attr_reader :adapter, :destination, :consumer, :options
6
6
 
7
- def initialize(adapter, destination, consumer, options={})
7
+ def initialize(adapter, destination, consumer, options = {})
8
8
  @adapter = adapter
9
9
  @destination = destination
10
10
  @consumer = consumer
@@ -12,7 +12,7 @@ module MessageDriver
12
12
  end
13
13
 
14
14
  def unsubscribe
15
- raise 'must be implemented in subclass'
15
+ fail 'must be implemented in subclass'
16
16
  end
17
17
  end
18
18
  end
@@ -1,5 +1,5 @@
1
1
  module Message
2
2
  module Driver
3
- VERSION = '0.4.0'
3
+ VERSION = '0.5.0'
4
4
  end
5
5
  end
@@ -14,15 +14,14 @@ Gem::Specification.new do |gem|
14
14
  gem.license = 'MIT'
15
15
 
16
16
  gem.files = `git ls-files`.split($RS)
17
- gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
18
- gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+ gem.executables = gem.files.grep(/^bin\//).map { |f| File.basename(f) }
18
+ gem.test_files = gem.files.grep(/^(test|spec|features)\//)
19
19
  gem.require_paths = %w(lib)
20
20
 
21
21
  gem.required_ruby_version = '>= 1.9.2'
22
22
 
23
23
  gem.add_development_dependency 'rake'
24
- gem.add_development_dependency 'rspec', '~> 2.14.0'
24
+ gem.add_development_dependency 'rspec', '~> 3.1.0'
25
25
  gem.add_development_dependency 'cucumber'
26
26
  gem.add_development_dependency 'aruba'
27
- gem.add_development_dependency 'rubocop'
28
27
  end
@@ -1,15 +1,14 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe 'AMQP Integration', :bunny, type: :integration do
3
+ RSpec.describe 'AMQP Integration', :bunny, type: :integration do
4
4
  let!(:broker) { MessageDriver::Broker.configure BrokerConfig.config }
5
5
 
6
6
  context "when a queue can't be found" do
7
7
  let(:queue_name) { 'my.lost.queue' }
8
8
  it 'raises a MessageDriver::QueueNotFound error' do
9
- expect {
9
+ expect do
10
10
  broker.dynamic_destination(queue_name, passive: true)
11
- }.to raise_error(MessageDriver::QueueNotFound) do |err|
12
- expect(err.queue_name).to eq(queue_name)
11
+ end.to raise_error(MessageDriver::QueueNotFound) do |err|
13
12
  expect(err.nested).to be_a Bunny::NotFound
14
13
  end
15
14
  end
@@ -17,93 +16,33 @@ describe 'AMQP Integration', :bunny, type: :integration do
17
16
 
18
17
  context 'when a channel level exception occurs' do
19
18
  it 'raises a MessageDriver::WrappedError error' do
20
- expect {
19
+ expect do
21
20
  broker.dynamic_destination('not.a.queue', passive: true)
22
- }.to raise_error(MessageDriver::WrappedError) { |err| err.nested.should be_a Bunny::ChannelLevelException }
21
+ end.to raise_error(MessageDriver::WrappedError) { |err| expect(err.nested).to be_a Bunny::ChannelLevelException }
23
22
  end
24
23
 
25
24
  it 'reestablishes the channel transparently' do
26
- expect {
25
+ expect do
27
26
  broker.dynamic_destination('not.a.queue', passive: true)
28
- }.to raise_error(MessageDriver::WrappedError)
29
- expect {
27
+ end.to raise_error(MessageDriver::WrappedError)
28
+ expect do
30
29
  broker.dynamic_destination('', exclusive: true)
31
- }.to_not raise_error
30
+ end.to_not raise_error
32
31
  end
33
32
 
34
33
  context 'when in a transaction' do
35
34
  it 'sets the channel_context as rollback-only until the transaction is finished' do
36
35
  MessageDriver::Client.with_message_transaction do
37
- expect {
36
+ expect do
38
37
  broker.dynamic_destination('not.a.queue', passive: true)
39
- }.to raise_error(MessageDriver::WrappedError)
40
- expect {
38
+ end.to raise_error(MessageDriver::WrappedError)
39
+ expect do
41
40
  broker.dynamic_destination('', exclusive: true)
42
- }.to raise_error(MessageDriver::TransactionRollbackOnly)
41
+ end.to raise_error(MessageDriver::TransactionRollbackOnly)
43
42
  end
44
- expect {
43
+ expect do
45
44
  broker.dynamic_destination('', exclusive: true)
46
- }.to_not raise_error
47
- end
48
- end
49
- end
50
-
51
- context 'when the broker connection fails', pending: 'these spec are busted' do
52
- def disrupt_connection
53
- #yes, this is very implementation specific
54
- broker.adapter.connection.instance_variable_get(:@transport).close
55
- end
56
-
57
- def create_destination(queue_name)
58
- broker.dynamic_destination(queue_name, exclusive: true)
59
- end
60
-
61
- it 'raises a MessageDriver::ConnectionError' do
62
- dest = create_destination('test_queue')
63
- disrupt_connection
64
- expect {
65
- dest.publish('Reconnection Test')
66
- }.to raise_error(MessageDriver::ConnectionError) do |err|
67
- expect(err.nested).to be_a Bunny::NetworkErrorWrapper
68
- end
69
- end
70
-
71
- it 'seemlessly reconnects' do
72
- dest = create_destination('seemless.reconnect.queue')
73
- disrupt_connection
74
- expect {
75
- dest.publish('Reconnection Test 1')
76
- }.to raise_error(MessageDriver::ConnectionError)
77
- dest = create_destination('seemless.reconnect.queue')
78
- dest.publish('Reconnection Test 2')
79
- msg = dest.pop_message
80
- expect(msg).to_not be_nil
81
- expect(msg.body).to eq('Reconnection Test 2')
82
- end
83
-
84
- context 'when in a transaction' do
85
- it 'raises a MessageDriver::ConnectionError' do
86
- expect {
87
- MessageDriver::Client.with_message_transaction do
88
- disrupt_connection
89
- broker.dynamic_destination('', exclusive: true)
90
- end
91
- }.to raise_error(MessageDriver::ConnectionError)
92
- end
93
-
94
- it 'sets the channel_context as rollback-only until the transaction is finished' do
95
- MessageDriver::Client.with_message_transaction do
96
- disrupt_connection
97
- expect {
98
- broker.dynamic_destination('', exclusive: true)
99
- }.to raise_error(MessageDriver::ConnectionError)
100
- expect {
101
- broker.dynamic_destination('', exclusive: true)
102
- }.to raise_error(MessageDriver::TransactionRollbackOnly)
103
- end
104
- expect {
105
- broker.dynamic_destination('', exclusive: true)
106
- }.to_not raise_error
45
+ end.to_not raise_error
107
46
  end
108
47
  end
109
48
  end
@@ -112,22 +51,22 @@ describe 'AMQP Integration', :bunny, type: :integration do
112
51
  let(:destination) { broker.dynamic_destination('', exclusive: true) }
113
52
 
114
53
  it 'rolls back the transaction' do
115
- expect {
54
+ expect do
116
55
  MessageDriver::Client.with_message_transaction do
117
56
  destination.publish('Test Message')
118
- raise 'unhandled error'
57
+ fail 'unhandled error'
119
58
  end
120
- }.to raise_error 'unhandled error'
59
+ end.to raise_error 'unhandled error'
121
60
  expect(destination.pop_message).to be_nil
122
61
  end
123
62
 
124
63
  it 'allows the next transaction to continue' do
125
- expect {
64
+ expect do
126
65
  MessageDriver::Client.with_message_transaction do
127
66
  destination.publish('Test Message 1')
128
- raise 'unhandled error'
67
+ fail 'unhandled error'
129
68
  end
130
- }.to raise_error 'unhandled error'
69
+ end.to raise_error 'unhandled error'
131
70
  expect(destination.pop_message).to be_nil
132
71
 
133
72
  MessageDriver::Client.with_message_transaction do
@@ -2,351 +2,371 @@ require 'spec_helper'
2
2
 
3
3
  require 'message_driver/adapters/bunny_adapter'
4
4
 
5
- module MessageDriver::Adapters
6
- describe BunnyAdapter, :bunny, type: :integration do
7
-
8
- let(:valid_connection_attrs) { BrokerConfig.config }
9
-
10
- describe '#initialize' do
11
- context 'differing bunny versions' do
12
- let(:broker) { double('broker') }
13
- shared_examples 'raises an error' do
14
- it 'raises an error' do
15
- stub_const('Bunny::VERSION', version)
16
- expect {
17
- described_class.new(broker, valid_connection_attrs)
18
- }.to raise_error MessageDriver::Error, 'bunny 1.2.2 or later is required for the bunny adapter'
5
+ module MessageDriver
6
+ module Adapters
7
+ RSpec.describe BunnyAdapter, :bunny, type: :integration do
8
+ let(:valid_connection_attrs) { BrokerConfig.config }
9
+
10
+ describe '#initialize' do
11
+ context 'differing bunny versions' do
12
+ let(:broker) { double('broker') }
13
+ shared_examples 'raises an error' do
14
+ it 'raises an error' do
15
+ stub_const('Bunny::VERSION', version)
16
+ expect do
17
+ described_class.new(broker, valid_connection_attrs)
18
+ end.to raise_error MessageDriver::Error, 'bunny 1.2.2 or later is required for the bunny adapter'
19
+ end
19
20
  end
20
- end
21
- shared_examples "doesn't raise an error" do
22
- it "doesn't raise an an error" do
23
- stub_const('Bunny::VERSION', version)
24
- adapter = nil
25
- expect {
26
- adapter = described_class.new(broker, valid_connection_attrs)
27
- }.to_not raise_error
21
+ shared_examples "doesn't raise an error" do
22
+ it "doesn't raise an an error" do
23
+ stub_const('Bunny::VERSION', version)
24
+ adapter = nil
25
+ expect do
26
+ adapter = described_class.new(broker, valid_connection_attrs)
27
+ end.to_not raise_error
28
+ end
28
29
  end
29
- end
30
- %w(0.8.0 0.9.0 0.9.8 0.10.7 1.0.3 1.1.2 1.2.1).each do |v|
31
- context "bunny version #{v}" do
32
- let(:version) { v }
33
- include_examples 'raises an error'
30
+ %w(0.8.0 0.9.0 0.9.8 0.10.7 1.0.3 1.1.2 1.2.1).each do |v|
31
+ context "bunny version #{v}" do
32
+ let(:version) { v }
33
+ include_examples 'raises an error'
34
+ end
34
35
  end
35
- end
36
- %w(1.2.2 1.3.2 1.4.0).each do |v|
37
- context "bunny version #{v}" do
38
- let(:version) { v }
39
- include_examples "doesn't raise an error"
36
+ %w(1.2.2 1.3.2 1.4.0).each do |v|
37
+ context "bunny version #{v}" do
38
+ let(:version) { v }
39
+ include_examples "doesn't raise an error"
40
+ end
40
41
  end
41
42
  end
42
- end
43
43
 
44
- it 'connects to the rabbit broker' do
45
- broker = double(:broker)
46
- adapter = described_class.new(broker, valid_connection_attrs)
44
+ it 'connects to the rabbit broker' do
45
+ broker = double(:broker)
46
+ adapter = described_class.new(broker, valid_connection_attrs)
47
47
 
48
- expect(adapter.connection).to be_a Bunny::Session
49
- expect(adapter.connection).to be_open
50
- end
48
+ expect(adapter.connection).to be_a Bunny::Session
49
+ expect(adapter.connection).to be_open
50
+ end
51
51
 
52
- it 'connects to the rabbit broker lazily' do
53
- broker = double(:broker)
54
- adapter = described_class.new(broker, valid_connection_attrs)
52
+ it 'connects to the rabbit broker lazily' do
53
+ broker = double(:broker)
54
+ adapter = described_class.new(broker, valid_connection_attrs)
55
55
 
56
- expect(adapter.connection(false)).to be_nil
56
+ expect(adapter.connection(false)).to be_nil
57
+ end
57
58
  end
58
- end
59
59
 
60
- shared_context 'a connected bunny adapter' do
61
- let(:broker) { MessageDriver::Broker.configure(valid_connection_attrs) }
62
- subject(:adapter) { broker.adapter }
63
- let(:connection) { adapter.connection }
60
+ shared_context 'a connected bunny adapter' do
61
+ let(:broker) { MessageDriver::Broker.configure(valid_connection_attrs) }
62
+ subject(:adapter) { broker.adapter }
63
+ let(:connection) { adapter.connection }
64
64
 
65
- after do
66
- adapter.stop
65
+ after do
66
+ adapter.stop
67
+ end
67
68
  end
68
- end
69
-
70
- shared_context 'with a queue' do
71
- include_context 'a connected bunny adapter'
72
-
73
- let(:channel) { connection.create_channel }
74
- let(:tmp_queue_name) { 'my_temp_queue' }
75
- let(:tmp_queue) { channel.queue(tmp_queue_name, exclusive: true) }
76
- end
77
-
78
- it_behaves_like 'an adapter' do
79
- include_context 'a connected bunny adapter'
80
- end
81
69
 
82
- describe '#new_context' do
83
- include_context 'a connected bunny adapter'
70
+ shared_context 'with a queue' do
71
+ include_context 'a connected bunny adapter'
84
72
 
85
- it 'returns a BunnyAdapter::BunnyContext' do
86
- expect(subject.new_context).to be_a BunnyAdapter::BunnyContext
73
+ let(:channel) { connection.create_channel }
74
+ let(:tmp_queue_name) { 'my_temp_queue' }
75
+ let(:tmp_queue) { channel.queue(tmp_queue_name, exclusive: true) }
87
76
  end
88
- end
89
77
 
90
- describe BunnyAdapter::BunnyContext do
91
- include_context 'a connected bunny adapter'
92
- subject(:adapter_context) { adapter.new_context }
93
- around(:each) do |ex|
94
- MessageDriver::Client.with_adapter_context(adapter_context) do
95
- ex.run
96
- end
78
+ it_behaves_like 'an adapter' do
79
+ include_context 'a connected bunny adapter'
97
80
  end
98
81
 
99
- it_behaves_like 'an adapter context'
100
- it_behaves_like 'transactions are supported'
101
- it_behaves_like 'client acks are supported'
102
- it_behaves_like 'subscriptions are supported', BunnyAdapter::Subscription
82
+ describe '#new_context' do
83
+ include_context 'a connected bunny adapter'
103
84
 
104
- describe '#pop_message' do
105
- include_context 'with a queue'
106
- it 'needs some real tests'
85
+ it 'returns a BunnyAdapter::BunnyContext' do
86
+ expect(subject.new_context).to be_a BunnyAdapter::BunnyContext
87
+ end
107
88
  end
108
89
 
109
- describe '#invalidate' do
110
- it 'closes the channel' do
111
- subject.with_channel(false) do |ch|
112
- expect(ch).to be_open
90
+ describe BunnyAdapter::BunnyContext do
91
+ include_context 'a connected bunny adapter'
92
+ subject(:adapter_context) { adapter.new_context }
93
+ around(:example) do |ex|
94
+ MessageDriver::Client.with_adapter_context(adapter_context) do
95
+ ex.run
113
96
  end
114
- subject.invalidate
115
- expect(subject.instance_variable_get(:@channel)).to be_closed
116
97
  end
117
- end
118
-
119
- describe '#create_destination' do
120
98
 
121
- context 'with defaults' do
122
- context 'the resulting destination' do
123
- let(:dest_name) { 'my_dest' }
124
- subject(:result) { adapter_context.create_destination(dest_name, exclusive: true) }
99
+ it_behaves_like 'an adapter context'
100
+ it_behaves_like 'transactions are supported'
101
+ it_behaves_like 'client acks are supported'
102
+ it_behaves_like 'subscriptions are supported', BunnyAdapter::Subscription
125
103
 
126
- it { should be_a BunnyAdapter::QueueDestination }
127
- end
104
+ describe '#pop_message' do
105
+ include_context 'with a queue'
106
+ it 'needs some real tests'
128
107
  end
129
108
 
130
- shared_examples 'supports publisher confirmations' do
131
- let(:properties) { {persistent: false, confirm: true} }
132
- it 'switches the channel to confirms mode' do
133
- expect(adapter_context.channel.using_publisher_confirms?).to eq(true)
134
- end
135
- it 'waits until the confirm comes in' do
136
- expect(adapter_context.channel.unconfirmed_set).to be_empty
109
+ describe '#invalidate' do
110
+ it 'closes the channel' do
111
+ subject.with_channel(false) do |ch|
112
+ expect(ch).to be_open
113
+ end
114
+ subject.invalidate
115
+ expect(subject.instance_variable_get(:@channel)).to be_closed
137
116
  end
138
117
  end
139
118
 
140
- context 'the type is queue' do
141
- context 'and there is no destination name given' do
142
- subject(:destination) { adapter_context.create_destination('', type: :queue, exclusive: true) }
143
- it { should be_a BunnyAdapter::QueueDestination }
144
- its(:name) { should be_a String }
145
- its(:name) { should_not be_empty }
146
- end
147
- context 'the resulting destination' do
148
- let(:dest_name) { 'my_dest' }
149
- subject(:destination) { adapter_context.create_destination(dest_name, type: :queue, exclusive: true) }
150
- before do
151
- destination
152
- end
119
+ describe '#create_destination' do
153
120
 
154
- it { should be_a BunnyAdapter::QueueDestination }
155
- its(:name) { should be_a String }
156
- its(:name) { should eq(dest_name) }
121
+ context 'with defaults' do
122
+ context 'the resulting destination' do
123
+ let(:dest_name) { 'my_dest' }
124
+ subject(:result) { adapter_context.create_destination(dest_name, exclusive: true) }
157
125
 
158
- include_examples 'supports #message_count'
126
+ it { is_expected.to be_a BunnyAdapter::QueueDestination }
127
+ end
128
+ end
159
129
 
160
- it "strips off the type so it isn't set on the destination" do
161
- expect(subject.dest_options).to_not have_key :type
130
+ shared_examples 'supports publisher confirmations' do
131
+ let(:properties) { { persistent: false, confirm: true } }
132
+ it 'switches the channel to confirms mode' do
133
+ expect(adapter_context.channel.using_publisher_confirms?).to eq(true)
134
+ end
135
+ it 'waits until the confirm comes in' do
136
+ expect(adapter_context.channel.unconfirmed_set).to be_empty
162
137
  end
163
- it 'ensures the queue is declared' do
164
- expect {
165
- connection.with_channel do |ch|
166
- ch.queue(dest_name, passive: true)
138
+ end
139
+
140
+ context 'the type is queue' do
141
+ context 'and there is no destination name given' do
142
+ subject(:destination) { adapter_context.create_destination('', type: :queue, exclusive: true) }
143
+ it { is_expected.to be_a BunnyAdapter::QueueDestination }
144
+
145
+ describe '#name' do
146
+ it 'is a non-empty String' do
147
+ expect(subject.name).to be_a String
148
+ expect(subject.name).not_to be_empty
167
149
  end
168
- }.to_not raise_error
150
+ end
169
151
  end
170
- context 'publishing a message' do
171
- let(:body) { 'Testing the QueueDestination' }
172
- let(:headers) { {'foo' => 'bar'} }
173
- let(:properties) { {persistent: false} }
152
+
153
+ context 'the resulting destination' do
154
+ let(:dest_name) { 'my_dest' }
155
+ subject(:destination) { adapter_context.create_destination(dest_name, type: :queue, exclusive: true) }
174
156
  before do
175
- subject.publish(body, headers, properties)
157
+ destination
158
+ end
159
+
160
+ it { is_expected.to be_a BunnyAdapter::QueueDestination }
161
+
162
+ describe '#name' do
163
+ it 'is the destination name' do
164
+ expect(subject.name).to be_a String
165
+ expect(subject.name).to eq(dest_name)
166
+ end
167
+ end
168
+
169
+ include_examples 'supports #message_count'
170
+ include_examples 'supports #consumer_count'
171
+
172
+ it "strips off the type so it isn't set on the destination" do
173
+ expect(subject.dest_options).to_not have_key :type
176
174
  end
177
- it 'publishes via the default exchange' do
178
- msg = subject.pop_message
179
- expect(msg.body).to eq(body)
180
- expect(msg.headers).to eq(headers)
181
- expect(msg.properties[:delivery_mode]).to eq(1)
182
- expect(msg.delivery_info.exchange).to eq('')
183
- expect(msg.delivery_info.routing_key).to eq(subject.name)
175
+ it 'ensures the queue is declared' do
176
+ expect do
177
+ connection.with_channel do |ch|
178
+ ch.queue(dest_name, passive: true)
179
+ end
180
+ end.to_not raise_error
184
181
  end
185
- include_examples 'supports publisher confirmations'
186
- end
187
- it_behaves_like 'a destination'
188
- end
189
- context 'and bindings are provided' do
190
- let(:dest_name) { 'binding_test_queue' }
191
- let(:exchange) { adapter_context.create_destination('amq.direct', type: :exchange) }
192
-
193
- it "raises an exception if you don't provide a source" do
194
- expect {
195
- adapter_context.create_destination('bad_bind_queue', type: :queue, exclusive: true, bindings: [{args: {routing_key: 'test_exchange_bind'}}])
196
- }.to raise_error MessageDriver::Error, /must provide a source/
182
+ context 'publishing a message' do
183
+ let(:body) { 'Testing the QueueDestination' }
184
+ let(:headers) { { 'foo' => 'bar' } }
185
+ let(:properties) { { persistent: false } }
186
+ before do
187
+ subject.publish(body, headers, properties)
188
+ end
189
+ it 'publishes via the default exchange' do
190
+ msg = subject.pop_message
191
+ expect(msg.body).to eq(body)
192
+ expect(msg.headers).to eq(headers)
193
+ expect(msg.properties[:delivery_mode]).to eq(1)
194
+ expect(msg.delivery_info.exchange).to eq('')
195
+ expect(msg.delivery_info.routing_key).to eq(subject.name)
196
+ end
197
+ include_examples 'supports publisher confirmations'
198
+ end
199
+ it_behaves_like 'a destination'
197
200
  end
201
+ context 'and bindings are provided' do
202
+ let(:dest_name) { 'binding_test_queue' }
203
+ let(:exchange) { adapter_context.create_destination('amq.direct', type: :exchange) }
204
+
205
+ it "raises an exception if you don't provide a source" do
206
+ expect do
207
+ adapter_context.create_destination('bad_bind_queue', type: :queue, exclusive: true, bindings: [{ args: { routing_key: 'test_exchange_bind' } }])
208
+ end.to raise_error MessageDriver::Error, /must provide a source/
209
+ end
198
210
 
199
- it 'routes message to the queue through the exchange' do
200
- destination = adapter_context.create_destination(dest_name, type: :queue, exclusive: true, bindings: [{source: 'amq.direct', args: {routing_key: 'test_queue_bind'}}])
201
- exchange.publish('test queue bindings', {}, routing_key: 'test_queue_bind')
202
- message = destination.pop_message
203
- expect(message).to_not be_nil
204
- expect(message.body).to eq('test queue bindings')
211
+ it 'routes message to the queue through the exchange' do
212
+ destination = adapter_context.create_destination(dest_name, type: :queue,
213
+ exclusive: true,
214
+ bindings: [{
215
+ source: 'amq.direct',
216
+ args: { routing_key: 'test_queue_bind' }
217
+ }]
218
+ )
219
+ exchange.publish('test queue bindings', {}, routing_key: 'test_queue_bind')
220
+ message = destination.pop_message
221
+ expect(message).to_not be_nil
222
+ expect(message.body).to eq('test queue bindings')
223
+ end
205
224
  end
206
- end
207
225
 
208
- context 'we are not yet connected to the broker and :no_declare is provided' do
209
- it "doesn't cause a connection to the broker" do
210
- connection.stop
211
- adapter_context.create_destination('test_queue', no_declare: true, type: :queue, exclusive: true)
212
- expect(adapter.connection(false)).to_not be_open
213
- end
226
+ context 'we are not yet connected to the broker and :no_declare is provided' do
227
+ it "doesn't cause a connection to the broker" do
228
+ connection.stop
229
+ adapter_context.create_destination('test_queue', no_declare: true, type: :queue, exclusive: true)
230
+ expect(adapter.connection(false)).to_not be_open
231
+ end
214
232
 
215
- context 'with a server-named queue' do
216
- it 'raises an error' do
217
- expect {
218
- adapter_context.create_destination('', no_declare: true, type: :queue, exclusive: true)
219
- }.to raise_error MessageDriver::Error, 'server-named queues must be declared, but you provided :no_declare => true'
233
+ context 'with a server-named queue' do
234
+ it 'raises an error' do
235
+ expect do
236
+ adapter_context.create_destination('', no_declare: true, type: :queue, exclusive: true)
237
+ end.to raise_error MessageDriver::Error, 'server-named queues must be declared, but you provided :no_declare => true'
238
+ end
220
239
  end
221
- end
222
240
 
223
- context 'with bindings' do
224
- it 'raises an error' do
225
- expect {
226
- adapter_context.create_destination('tmp_queue', no_declare: true, bindings: [{source: 'amq.fanout'}], type: :queue, exclusive: true)
227
- }.to raise_error MessageDriver::Error, 'queues with bindings must be declared, but you provided :no_declare => true'
241
+ context 'with bindings' do
242
+ it 'raises an error' do
243
+ expect do
244
+ adapter_context.create_destination('tmp_queue', no_declare: true, bindings: [{ source: 'amq.fanout' }], type: :queue, exclusive: true)
245
+ end.to raise_error MessageDriver::Error, 'queues with bindings must be declared, but you provided :no_declare => true'
246
+ end
228
247
  end
229
248
  end
230
249
  end
231
- end
232
250
 
233
- context 'the type is exchange' do
234
- context 'the resulting destination' do
235
- let(:dest_name) { 'my_dest' }
236
- subject(:destination) { adapter_context.create_destination(dest_name, type: :exchange) }
251
+ context 'the type is exchange' do
252
+ context 'the resulting destination' do
253
+ let(:dest_name) { 'my_dest' }
254
+ subject(:destination) { adapter_context.create_destination(dest_name, type: :exchange) }
237
255
 
238
- it { should be_a BunnyAdapter::ExchangeDestination }
239
- include_examples "doesn't support #message_count"
256
+ it { is_expected.to be_a BunnyAdapter::ExchangeDestination }
257
+ include_examples "doesn't support #message_count"
258
+ include_examples "doesn't support #consumer_count"
240
259
 
241
- it "strips off the type so it isn't set on the destination" do
242
- expect(subject.dest_options).to_not have_key :type
243
- end
260
+ it "strips off the type so it isn't set on the destination" do
261
+ expect(subject.dest_options).to_not have_key :type
262
+ end
244
263
 
245
- it 'raises an error when pop_message is called' do
246
- expect {
247
- subject.pop_message(dest_name)
248
- }.to raise_error MessageDriver::Error, "You can't pop a message off an exchange"
249
- end
264
+ it 'raises an error when pop_message is called' do
265
+ expect do
266
+ subject.pop_message(dest_name)
267
+ end.to raise_error MessageDriver::Error, "You can't pop a message off an exchange"
268
+ end
250
269
 
251
- context 'publishing a message' do
252
- let(:body) { 'Testing the ExchangeDestination' }
253
- let(:headers) { {'foo' => 'bar'} }
254
- let(:properties) { {persistent: false} }
255
- before { connection.with_channel { |ch| ch.fanout(dest_name, auto_delete: true) } }
256
- let!(:queue) do
257
- q = nil
258
- connection.with_channel do |ch|
259
- q = ch.queue('', exclusive: true)
260
- q.bind(dest_name)
270
+ context 'publishing a message' do
271
+ let(:body) { 'Testing the ExchangeDestination' }
272
+ let(:headers) { { 'foo' => 'bar' } }
273
+ let(:properties) { { persistent: false } }
274
+ before { connection.with_channel { |ch| ch.fanout(dest_name, auto_delete: true) } }
275
+ let!(:queue) do
276
+ q = nil
277
+ connection.with_channel do |ch|
278
+ q = ch.queue('', exclusive: true)
279
+ q.bind(dest_name)
280
+ end
281
+ q
282
+ end
283
+ before do
284
+ subject.publish(body, headers, properties)
261
285
  end
262
- q
263
- end
264
- before do
265
- subject.publish(body, headers, properties)
266
- end
267
286
 
268
- it 'publishes to the specified exchange' do
269
- connection.with_channel do |ch|
270
- q = ch.queue(queue.name, passive: true)
271
- msg = q.pop
272
- expect(msg[2]).to eq(body)
273
- expect(msg[0].exchange).to eq(dest_name)
274
- expect(msg[1][:headers]).to eq(headers)
275
- expect(msg[1][:delivery_mode]).to eq(1)
287
+ it 'publishes to the specified exchange' do
288
+ connection.with_channel do |ch|
289
+ q = ch.queue(queue.name, passive: true)
290
+ msg = q.pop
291
+ expect(msg[2]).to eq(body)
292
+ expect(msg[0].exchange).to eq(dest_name)
293
+ expect(msg[1][:headers]).to eq(headers)
294
+ expect(msg[1][:delivery_mode]).to eq(1)
295
+ end
276
296
  end
297
+ include_examples 'supports publisher confirmations'
277
298
  end
278
- include_examples 'supports publisher confirmations'
279
299
  end
280
- end
281
300
 
282
- context 'declaring an exchange on the broker' do
283
- let(:dest_name) { 'my.cool.exchange' }
301
+ context 'declaring an exchange on the broker' do
302
+ let(:dest_name) { 'my.cool.exchange' }
284
303
 
285
- it "creates the exchange if you include 'declare' in the options" do
286
- exchange = adapter_context.create_destination(dest_name, type: :exchange, declare: {type: :fanout, auto_delete: true})
287
- queue = adapter_context.create_destination('', type: :queue, exclusive: true, bindings: [{source: dest_name}])
288
- exchange.publish('test declaring exchange')
289
- message = queue.pop_message
290
- expect(message).to_not be_nil
291
- expect(message.body).to eq('test declaring exchange')
292
- end
304
+ it "creates the exchange if you include 'declare' in the options" do
305
+ exchange = adapter_context.create_destination(dest_name, type: :exchange, declare: { type: :fanout, auto_delete: true })
306
+ queue = adapter_context.create_destination('', type: :queue, exclusive: true, bindings: [{ source: dest_name }])
307
+ exchange.publish('test declaring exchange')
308
+ message = queue.pop_message
309
+ expect(message).to_not be_nil
310
+ expect(message.body).to eq('test declaring exchange')
311
+ end
312
+
313
+ it "raises an error if you don't provide a type" do
314
+ expect do
315
+ adapter_context.create_destination(dest_name, type: :exchange, declare: { auto_delete: true })
316
+ end.to raise_error MessageDriver::Error, /you must provide a valid exchange type/
317
+ end
293
318
 
294
- it "raises an error if you don't provide a type" do
295
- expect {
296
- adapter_context.create_destination(dest_name, type: :exchange, declare: {auto_delete: true})
297
- }.to raise_error MessageDriver::Error, /you must provide a valid exchange type/
298
319
  end
299
320
 
300
- end
321
+ context 'and bindings are provided' do
322
+ let(:dest_name) { 'binding_exchange_queue' }
323
+ let(:exchange) { adapter_context.create_destination('amq.direct', type: :exchange) }
301
324
 
302
- context 'and bindings are provided' do
303
- let(:dest_name) { 'binding_exchange_queue' }
304
- let(:exchange) { adapter_context.create_destination('amq.direct', type: :exchange) }
325
+ it "raises an exception if you don't provide a source" do
326
+ expect do
327
+ adapter_context.create_destination('amq.fanout', type: :exchange, bindings: [{ args: { routing_key: 'test_exchange_bind' } }])
328
+ end.to raise_error MessageDriver::Error, /must provide a source/
329
+ end
305
330
 
306
- it "raises an exception if you don't provide a source" do
307
- expect {
308
- adapter_context.create_destination('amq.fanout', type: :exchange, bindings: [{args: {routing_key: 'test_exchange_bind'}}])
309
- }.to raise_error MessageDriver::Error, /must provide a source/
331
+ it 'routes message to the queue through the exchange' do
332
+ adapter_context.create_destination('amq.fanout', type: :exchange, bindings: [{ source: 'amq.direct', args: { routing_key: 'test_exchange_bind' } }])
333
+ destination = adapter_context.create_destination(dest_name, type: :queue, exclusive: true, bindings: [{ source: 'amq.fanout' }])
334
+ exchange.publish('test exchange bindings', {}, routing_key: 'test_exchange_bind')
335
+ message = destination.pop_message
336
+ expect(message).to_not be_nil
337
+ expect(message.body).to eq('test exchange bindings')
338
+ end
310
339
  end
311
340
 
312
- it 'routes message to the queue through the exchange' do
313
- adapter_context.create_destination('amq.fanout', type: :exchange, bindings: [{source: 'amq.direct', args: {routing_key: 'test_exchange_bind'}}])
314
- destination = adapter_context.create_destination(dest_name, type: :queue, exclusive: true, bindings: [{source: 'amq.fanout'}])
315
- exchange.publish('test exchange bindings', {}, routing_key: 'test_exchange_bind')
316
- message = destination.pop_message
317
- expect(message).to_not be_nil
318
- expect(message.body).to eq('test exchange bindings')
341
+ context 'we are not yet connected to the broker' do
342
+ it "doesn't cause a connection to the broker" do
343
+ connection.stop
344
+ adapter_context.create_destination('amq.fanout', type: :exchange)
345
+ expect(adapter.connection(false)).to_not be_open
346
+ end
319
347
  end
320
348
  end
321
349
 
322
- context 'we are not yet connected to the broker' do
323
- it "doesn't cause a connection to the broker" do
324
- connection.stop
325
- adapter_context.create_destination('amq.fanout', type: :exchange)
326
- expect(adapter.connection(false)).to_not be_open
350
+ context 'the type is invalid' do
351
+ it 'raises in an error' do
352
+ expect do
353
+ adapter_context.create_destination('my_dest', type: :foo_bar)
354
+ end.to raise_error MessageDriver::Error, "invalid destination type #{:foo_bar}"
327
355
  end
328
356
  end
329
357
  end
330
358
 
331
- context 'the type is invalid' do
332
- it 'raises in an error' do
333
- expect {
334
- adapter_context.create_destination('my_dest', type: :foo_bar)
335
- }.to raise_error MessageDriver::Error, "invalid destination type #{:foo_bar}"
336
- end
337
- end
338
- end
339
-
340
- describe '#subscribe' do
341
- context 'the destination is an ExchangeDestination' do
342
- let(:dest_name) { 'my_dest' }
343
- let(:destination) { adapter_context.create_destination(dest_name, type: :exchange) }
344
- let(:consumer) { lambda do |_|; end }
359
+ describe '#subscribe' do
360
+ context 'the destination is an ExchangeDestination' do
361
+ let(:dest_name) { 'my_dest' }
362
+ let(:destination) { adapter_context.create_destination(dest_name, type: :exchange) }
363
+ let(:consumer) { ->(_) {} }
345
364
 
346
- it 'raises an error' do
347
- expect {
348
- adapter_context.subscribe(destination, &consumer)
349
- }.to raise_error MessageDriver::Error, /QueueDestination/
365
+ it 'raises an error' do
366
+ expect do
367
+ adapter_context.subscribe(destination, &consumer)
368
+ end.to raise_error MessageDriver::Error, /QueueDestination/
369
+ end
350
370
  end
351
371
  end
352
372
  end