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.
- checksums.yaml +4 -4
- data/.coveralls.yml +2 -0
- data/.rubocop.yml +26 -2
- data/.rubocop_todo.yml +15 -123
- data/.travis.yml +2 -1
- data/CHANGELOG.md +10 -1
- data/Gemfile +15 -6
- data/Rakefile +23 -12
- data/ci/reset_vhost +8 -3
- data/ci/travis_setup +0 -3
- data/features/.nav +6 -1
- data/features/CHANGELOG.md +10 -1
- data/features/amqp_specific_features/binding_amqp_destinations.feature +1 -0
- data/features/amqp_specific_features/declaring_amqp_exchanges.feature +1 -0
- data/features/amqp_specific_features/server_named_destinations.feature +1 -0
- data/features/destination_metadata.feature +26 -0
- data/features/logging.feature +1 -1
- data/features/middleware/middleware_basics.feature +91 -0
- data/features/middleware/middleware_ordering.feature +60 -0
- data/features/middleware/middleware_parameters.feature +43 -0
- data/features/middleware/middleware_with_blocks.feature +85 -0
- data/features/step_definitions/dynamic_destinations_steps.rb +1 -1
- data/features/step_definitions/message_consumers_steps.rb +5 -0
- data/features/step_definitions/middleware_steps.rb +10 -0
- data/features/step_definitions/steps.rb +10 -2
- data/features/support/env.rb +4 -3
- data/features/support/firewall_helper.rb +1 -1
- data/features/support/message_table_matcher.rb +3 -2
- data/features/support/no_error_matcher.rb +2 -2
- data/features/support/test_runner.rb +11 -57
- data/features/support/transforms.rb +12 -10
- data/lib/message_driver.rb +3 -1
- data/lib/message_driver/adapters/base.rb +11 -11
- data/lib/message_driver/adapters/bunny_adapter.rb +189 -132
- data/lib/message_driver/adapters/in_memory_adapter.rb +51 -34
- data/lib/message_driver/adapters/stomp_adapter.rb +22 -23
- data/lib/message_driver/broker.rb +21 -16
- data/lib/message_driver/client.rb +15 -16
- data/lib/message_driver/destination.rb +26 -8
- data/lib/message_driver/message.rb +5 -4
- data/lib/message_driver/middleware.rb +8 -0
- data/lib/message_driver/middleware/base.rb +19 -0
- data/lib/message_driver/middleware/block_middleware.rb +33 -0
- data/lib/message_driver/middleware/middleware_stack.rb +61 -0
- data/lib/message_driver/subscription.rb +2 -2
- data/lib/message_driver/version.rb +1 -1
- data/message-driver.gemspec +3 -4
- data/spec/integration/bunny/amqp_integration_spec.rb +21 -82
- data/spec/integration/bunny/bunny_adapter_spec.rb +288 -268
- data/spec/integration/in_memory/in_memory_adapter_spec.rb +93 -90
- data/spec/integration/stomp/stomp_adapter_spec.rb +126 -93
- data/spec/spec_helper.rb +11 -9
- data/spec/support/shared/adapter_examples.rb +1 -1
- data/spec/support/shared/client_ack_examples.rb +4 -4
- data/spec/support/shared/context_examples.rb +6 -4
- data/spec/support/shared/destination_examples.rb +54 -14
- data/spec/support/shared/subscription_examples.rb +33 -26
- data/spec/support/shared/transaction_examples.rb +12 -12
- data/spec/support/utils.rb +1 -1
- data/spec/units/message_driver/adapters/base_spec.rb +42 -40
- data/spec/units/message_driver/broker_spec.rb +38 -38
- data/spec/units/message_driver/client_spec.rb +87 -87
- data/spec/units/message_driver/destination_spec.rb +16 -11
- data/spec/units/message_driver/message_spec.rb +96 -70
- data/spec/units/message_driver/middleware/base_spec.rb +30 -0
- data/spec/units/message_driver/middleware/block_middleware_spec.rb +82 -0
- data/spec/units/message_driver/middleware/middleware_stack_spec.rb +165 -0
- data/spec/units/message_driver/subscription_spec.rb +18 -16
- data/test_lib/broker_config.rb +21 -5
- data/test_lib/coverage.rb +11 -0
- data/test_lib/provider/base.rb +59 -0
- data/test_lib/provider/in_memory.rb +6 -0
- data/test_lib/provider/rabbitmq.rb +67 -0
- 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,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
|
-
|
15
|
+
fail 'must be implemented in subclass'
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
data/message-driver.gemspec
CHANGED
@@ -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(
|
18
|
-
gem.test_files = gem.files.grep(
|
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', '~>
|
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
|
-
|
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
|
-
|
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
|
-
|
29
|
-
expect
|
27
|
+
end.to raise_error(MessageDriver::WrappedError)
|
28
|
+
expect do
|
30
29
|
broker.dynamic_destination('', exclusive: true)
|
31
|
-
|
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
|
-
|
40
|
-
expect
|
38
|
+
end.to raise_error(MessageDriver::WrappedError)
|
39
|
+
expect do
|
41
40
|
broker.dynamic_destination('', exclusive: true)
|
42
|
-
|
41
|
+
end.to raise_error(MessageDriver::TransactionRollbackOnly)
|
43
42
|
end
|
44
|
-
expect
|
43
|
+
expect do
|
45
44
|
broker.dynamic_destination('', exclusive: true)
|
46
|
-
|
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
|
-
|
57
|
+
fail 'unhandled error'
|
119
58
|
end
|
120
|
-
|
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
|
-
|
67
|
+
fail 'unhandled error'
|
129
68
|
end
|
130
|
-
|
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
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
-
|
45
|
-
|
46
|
-
|
44
|
+
it 'connects to the rabbit broker' do
|
45
|
+
broker = double(:broker)
|
46
|
+
adapter = described_class.new(broker, valid_connection_attrs)
|
47
47
|
|
48
|
-
|
49
|
-
|
50
|
-
|
48
|
+
expect(adapter.connection).to be_a Bunny::Session
|
49
|
+
expect(adapter.connection).to be_open
|
50
|
+
end
|
51
51
|
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
-
|
56
|
+
expect(adapter.connection(false)).to be_nil
|
57
|
+
end
|
57
58
|
end
|
58
|
-
end
|
59
59
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
66
|
-
|
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
|
-
|
83
|
-
|
70
|
+
shared_context 'with a queue' do
|
71
|
+
include_context 'a connected bunny adapter'
|
84
72
|
|
85
|
-
|
86
|
-
|
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
|
-
|
91
|
-
|
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
|
-
|
100
|
-
|
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
|
-
|
105
|
-
|
106
|
-
|
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
|
110
|
-
|
111
|
-
|
112
|
-
|
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
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
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
|
-
|
127
|
-
|
104
|
+
describe '#pop_message' do
|
105
|
+
include_context 'with a queue'
|
106
|
+
it 'needs some real tests'
|
128
107
|
end
|
129
108
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
expect(
|
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
|
-
|
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
|
-
|
155
|
-
|
156
|
-
|
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
|
-
|
126
|
+
it { is_expected.to be_a BunnyAdapter::QueueDestination }
|
127
|
+
end
|
128
|
+
end
|
159
129
|
|
160
|
-
|
161
|
-
|
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
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
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
|
-
|
150
|
+
end
|
169
151
|
end
|
170
|
-
|
171
|
-
|
172
|
-
let(:
|
173
|
-
|
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
|
-
|
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 '
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
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
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
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
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
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
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
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
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
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
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
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
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
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
|
-
|
239
|
-
|
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
|
-
|
242
|
-
|
243
|
-
|
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
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
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
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
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
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
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
|
-
|
283
|
-
|
301
|
+
context 'declaring an exchange on the broker' do
|
302
|
+
let(:dest_name) { 'my.cool.exchange' }
|
284
303
|
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
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
|
-
|
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
|
-
|
303
|
-
|
304
|
-
|
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
|
-
|
307
|
-
|
308
|
-
adapter_context.create_destination(
|
309
|
-
|
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
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
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 '
|
323
|
-
it
|
324
|
-
|
325
|
-
|
326
|
-
|
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
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
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
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
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
|