message-driver 0.4.0 → 0.5.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.
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