message-driver 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +20 -0
- data/.rbenv-version +1 -0
- data/.relish +2 -0
- data/.rspec +2 -0
- data/.travis.yml +23 -0
- data/CHANGELOG.md +17 -0
- data/Gemfile +20 -0
- data/Guardfile +39 -0
- data/LICENSE.txt +22 -0
- data/README.md +36 -0
- data/Rakefile +23 -0
- data/features/.nav +12 -0
- data/features/CHANGELOG.md +17 -0
- data/features/README.md +1 -0
- data/features/Rails.md +1 -0
- data/features/amqp_specific_features/README.md +3 -0
- data/features/amqp_specific_features/binding_amqp_destinations.feature +50 -0
- data/features/amqp_specific_features/declaring_amqp_exchanges.feature +22 -0
- data/features/amqp_specific_features/server_named_destinations.feature +35 -0
- data/features/destination_metadata.feature +33 -0
- data/features/dynamic_destinations.feature +41 -0
- data/features/error_handling.feature +47 -0
- data/features/getting_started.md +1 -0
- data/features/publishing_a_message.feature +19 -0
- data/features/publishing_with_transactions.feature +36 -0
- data/features/step_definitions/dynamic_destinations_steps.rb +12 -0
- data/features/step_definitions/error_handling_steps.rb +11 -0
- data/features/step_definitions/steps.rb +41 -0
- data/features/support/env.rb +7 -0
- data/features/support/firewall_helper.rb +59 -0
- data/features/support/message_table_matcher.rb +11 -0
- data/features/support/no_error_matcher.rb +13 -0
- data/features/support/test_runner.rb +50 -0
- data/features/support/transforms.rb +17 -0
- data/lib/message-driver.rb +1 -0
- data/lib/message_driver/adapters/base.rb +29 -0
- data/lib/message_driver/adapters/bunny_adapter.rb +270 -0
- data/lib/message_driver/adapters/in_memory_adapter.rb +58 -0
- data/lib/message_driver/broker.rb +95 -0
- data/lib/message_driver/destination.rb +31 -0
- data/lib/message_driver/exceptions.rb +18 -0
- data/lib/message_driver/message.rb +13 -0
- data/lib/message_driver/message_publisher.rb +15 -0
- data/lib/message_driver/version.rb +5 -0
- data/lib/message_driver.rb +18 -0
- data/message-driver.gemspec +27 -0
- data/spec/integration/amqp_integration_spec.rb +146 -0
- data/spec/integration/message_driver/adapters/bunny_adapter_spec.rb +301 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/support/shared/destination_examples.rb +41 -0
- data/spec/units/message_driver/adapters/base_spec.rb +44 -0
- data/spec/units/message_driver/adapters/in_memory_adapter_spec.rb +43 -0
- data/spec/units/message_driver/broker_spec.rb +98 -0
- data/spec/units/message_driver/destination_spec.rb +11 -0
- data/spec/units/message_driver/message_publisher_spec.rb +65 -0
- data/spec/units/message_driver/message_spec.rb +19 -0
- data/test_lib/broker_config.rb +25 -0
- metadata +203 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'message_driver/version'
|
2
|
+
|
3
|
+
require 'message_driver/exceptions'
|
4
|
+
require 'message_driver/broker'
|
5
|
+
require 'message_driver/message'
|
6
|
+
require 'message_driver/destination'
|
7
|
+
require 'message_driver/adapters/base'
|
8
|
+
require 'message_driver/message_publisher'
|
9
|
+
|
10
|
+
module MessageDriver
|
11
|
+
def self.configure(options={})
|
12
|
+
Broker.configure(options)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.stop
|
16
|
+
Broker.stop
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'message_driver/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "message-driver"
|
8
|
+
gem.version = Message::Driver::VERSION
|
9
|
+
gem.authors = ["Matt Campbell"]
|
10
|
+
gem.email = ["matt@soupmatt.com"]
|
11
|
+
gem.description = %q{Easy message queues for ruby using AMQ, STOMP and others}
|
12
|
+
gem.summary = %q{Easy message queues for ruby}
|
13
|
+
gem.homepage = "https://github.com/soupmatt/message_driver"
|
14
|
+
gem.license = "MIT"
|
15
|
+
|
16
|
+
gem.files = `git ls-files`.split($/)
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
|
+
gem.require_paths = ["lib"]
|
20
|
+
|
21
|
+
gem.required_ruby_version = '>= 1.9.2'
|
22
|
+
|
23
|
+
gem.add_development_dependency "rake"
|
24
|
+
gem.add_development_dependency "rspec", "~> 2.13.0"
|
25
|
+
gem.add_development_dependency "cucumber"
|
26
|
+
gem.add_development_dependency "bunny", "~> 0.9.0.pre7"
|
27
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "AMQP Integration", :bunny, type: :integration do
|
4
|
+
before do
|
5
|
+
MessageDriver.configure BrokerConfig.config
|
6
|
+
end
|
7
|
+
|
8
|
+
context "when a queue can't be found" do
|
9
|
+
let(:queue_name) { "my.lost.queue" }
|
10
|
+
it "raises a MessageDriver::QueueNotFound error" do
|
11
|
+
expect {
|
12
|
+
MessageDriver::Broker.dynamic_destination(queue_name, passive: true)
|
13
|
+
}.to raise_error(MessageDriver::QueueNotFound, /#{queue_name}/) do |err|
|
14
|
+
expect(err.queue_name).to eq(queue_name)
|
15
|
+
expect(err.other).to be_a Bunny::NotFound
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "when a channel level exception occurs" do
|
21
|
+
it "raises a MessageDriver::WrappedException error" do
|
22
|
+
expect {
|
23
|
+
MessageDriver::Broker.dynamic_destination("not.a.queue", passive: true)
|
24
|
+
}.to raise_error(MessageDriver::WrappedException) { |err| err.other.should be_a Bunny::ChannelLevelException }
|
25
|
+
end
|
26
|
+
|
27
|
+
it "reestablishes the channel transparently" do
|
28
|
+
expect {
|
29
|
+
MessageDriver::Broker.dynamic_destination("not.a.queue", passive: true)
|
30
|
+
}.to raise_error(MessageDriver::WrappedException)
|
31
|
+
expect {
|
32
|
+
MessageDriver::Broker.dynamic_destination("", exclusive: true)
|
33
|
+
}.to_not raise_error
|
34
|
+
end
|
35
|
+
|
36
|
+
context "when in a transaction" do
|
37
|
+
it "sets the channel_context as rollback-only until the transaction is finished" do
|
38
|
+
MessageDriver::Broker.with_transaction do
|
39
|
+
expect {
|
40
|
+
MessageDriver::Broker.dynamic_destination("not.a.queue", passive: true)
|
41
|
+
}.to raise_error(MessageDriver::WrappedException)
|
42
|
+
expect {
|
43
|
+
MessageDriver::Broker.dynamic_destination("", exclusive: true)
|
44
|
+
}.to raise_error(MessageDriver::TransactionRollbackOnly)
|
45
|
+
end
|
46
|
+
expect {
|
47
|
+
MessageDriver::Broker.dynamic_destination("", exclusive: true)
|
48
|
+
}.to_not raise_error
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "when the broker connection fails" do
|
54
|
+
after(:each) do
|
55
|
+
MessageDriver::Broker.stop
|
56
|
+
end
|
57
|
+
def disrupt_connection
|
58
|
+
#yes, this is very implementation specific
|
59
|
+
MessageDriver::Broker.adapter.connection.instance_variable_get(:@transport).close
|
60
|
+
end
|
61
|
+
|
62
|
+
it "raises a MessageDriver::ConnectionException" do
|
63
|
+
dest = MessageDriver::Broker.dynamic_destination("", exclusive: true)
|
64
|
+
disrupt_connection
|
65
|
+
expect {
|
66
|
+
dest.publish("Reconnection Test")
|
67
|
+
}.to raise_error(MessageDriver::ConnectionException) do |err|
|
68
|
+
expect(err.other).to be_a Bunny::NetworkErrorWrapper
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def create_destination(queue_name)
|
73
|
+
MessageDriver::Broker.dynamic_destination(queue_name, exclusive: true)
|
74
|
+
end
|
75
|
+
it "seemlessly reconnects" do
|
76
|
+
dest = create_destination("seemless.reconnect.queue")
|
77
|
+
disrupt_connection
|
78
|
+
expect {
|
79
|
+
dest.publish("Reconnection Test 1")
|
80
|
+
}.to raise_error(MessageDriver::ConnectionException)
|
81
|
+
dest = create_destination("seemless.reconnect.queue")
|
82
|
+
dest.publish("Reconnection Test 2")
|
83
|
+
msg = dest.pop_message
|
84
|
+
expect(msg).to_not be_nil
|
85
|
+
expect(msg.body).to eq("Reconnection Test 2")
|
86
|
+
end
|
87
|
+
|
88
|
+
context "when in a transaction" do
|
89
|
+
it "raises a MessageDriver::ConnectionException" do
|
90
|
+
expect {
|
91
|
+
MessageDriver::Broker.with_transaction do
|
92
|
+
disrupt_connection
|
93
|
+
MessageDriver::Broker.dynamic_destination("", exclusive: true)
|
94
|
+
end
|
95
|
+
}.to raise_error(MessageDriver::ConnectionException)
|
96
|
+
end
|
97
|
+
|
98
|
+
it "sets the channel_context as rollback-only until the transaction is finished" do
|
99
|
+
MessageDriver::Broker.with_transaction do
|
100
|
+
disrupt_connection
|
101
|
+
expect {
|
102
|
+
MessageDriver::Broker.dynamic_destination("", exclusive: true)
|
103
|
+
}.to raise_error(MessageDriver::ConnectionException)
|
104
|
+
expect {
|
105
|
+
MessageDriver::Broker.dynamic_destination("", exclusive: true)
|
106
|
+
}.to raise_error(MessageDriver::TransactionRollbackOnly)
|
107
|
+
end
|
108
|
+
expect {
|
109
|
+
MessageDriver::Broker.dynamic_destination("", exclusive: true)
|
110
|
+
}.to_not raise_error
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context "when an unhandled expection occurs in a transaction" do
|
116
|
+
let(:destination) { MessageDriver::Broker.dynamic_destination("", exclusive: true) }
|
117
|
+
|
118
|
+
it "rolls back the transaction" do
|
119
|
+
expect {
|
120
|
+
MessageDriver::Broker.with_transaction do
|
121
|
+
destination.publish("Test Message")
|
122
|
+
raise "unhandled error"
|
123
|
+
end
|
124
|
+
}.to raise_error "unhandled error"
|
125
|
+
expect(destination.pop_message).to be_nil
|
126
|
+
end
|
127
|
+
|
128
|
+
it "allows the next transaction to continue" do
|
129
|
+
expect {
|
130
|
+
MessageDriver::Broker.with_transaction do
|
131
|
+
destination.publish("Test Message 1")
|
132
|
+
raise "unhandled error"
|
133
|
+
end
|
134
|
+
}.to raise_error "unhandled error"
|
135
|
+
expect(destination.pop_message).to be_nil
|
136
|
+
|
137
|
+
MessageDriver::Broker.with_transaction do
|
138
|
+
destination.publish("Test Message 2")
|
139
|
+
end
|
140
|
+
|
141
|
+
msg = destination.pop_message
|
142
|
+
expect(msg).to_not be_nil
|
143
|
+
expect(msg.body).to eq("Test Message 2")
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,301 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'message_driver/adapters/bunny_adapter'
|
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
|
+
shared_examples "raises an error" do
|
13
|
+
it "raises an error" do
|
14
|
+
stub_const("Bunny::VERSION", version)
|
15
|
+
expect {
|
16
|
+
described_class.new(valid_connection_attrs)
|
17
|
+
}.to raise_error MessageDriver::Exception, "bunny 0.9.0.pre7 or later is required for the bunny adapter"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
shared_examples "doesn't raise an error" do
|
21
|
+
it "doesn't raise an an error" do
|
22
|
+
stub_const("Bunny::VERSION", version)
|
23
|
+
adapter = nil
|
24
|
+
expect {
|
25
|
+
adapter = described_class.new(valid_connection_attrs)
|
26
|
+
}.to_not raise_error
|
27
|
+
end
|
28
|
+
end
|
29
|
+
%w(0.8.0 0.9.0.pre6).each do |v|
|
30
|
+
context "bunny version #{v}" do
|
31
|
+
let(:version) { v }
|
32
|
+
include_examples "raises an error"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
%w(0.9.0.pre7 0.9.0.rc1 0.9.0 0.9.1).each do |v|
|
36
|
+
context "bunny version #{v}" do
|
37
|
+
let(:version) { v }
|
38
|
+
include_examples "doesn't raise an error"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
it "connects to the rabbit broker" do
|
44
|
+
adapter = described_class.new(valid_connection_attrs)
|
45
|
+
|
46
|
+
expect(adapter.connection).to be_a Bunny::Session
|
47
|
+
expect(adapter.connection).to be_open
|
48
|
+
end
|
49
|
+
|
50
|
+
it "connects to the rabbit broker lazily" do
|
51
|
+
adapter = described_class.new(valid_connection_attrs)
|
52
|
+
|
53
|
+
expect(adapter.connection(false)).to_not be_open
|
54
|
+
end
|
55
|
+
|
56
|
+
it "forces bunny into non-threaded mode" do
|
57
|
+
#FIXME can be changed once ruby-amqp/bunny#112 is fixed
|
58
|
+
adapter = described_class.new(valid_connection_attrs)
|
59
|
+
expect(adapter.connection(false).threaded).to be_false
|
60
|
+
|
61
|
+
adapter = described_class.new(valid_connection_attrs.merge(threaded: true))
|
62
|
+
expect(adapter.connection(false).threaded).to be_false
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
shared_context "a connected adapter" do
|
67
|
+
let(:adapter) { described_class.new(valid_connection_attrs) }
|
68
|
+
let(:connection) { adapter.connection }
|
69
|
+
|
70
|
+
after do
|
71
|
+
connection.close
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
shared_context "with a queue" do
|
76
|
+
include_context "a connected adapter"
|
77
|
+
|
78
|
+
let(:channel) { connection.create_channel }
|
79
|
+
let(:tmp_queue_name) { "my_temp_queue" }
|
80
|
+
let(:tmp_queue) { channel.queue(tmp_queue_name, exclusive: true) }
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "#pop_message" do
|
84
|
+
include_context "with a queue"
|
85
|
+
it "needs some real tests"
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "#create_destination" do
|
89
|
+
include_context "a connected adapter"
|
90
|
+
|
91
|
+
context "with defaults" do
|
92
|
+
context "the resulting destination" do
|
93
|
+
let(:dest_name) { "my_dest" }
|
94
|
+
subject(:result) { adapter.create_destination(dest_name, exclusive: true) }
|
95
|
+
|
96
|
+
it { should be_a BunnyAdapter::QueueDestination }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context "the type is queue" do
|
101
|
+
context "and there is no destination name given" do
|
102
|
+
subject(:destination) { adapter.create_destination("", type: :queue, exclusive: true) }
|
103
|
+
it { should be_a BunnyAdapter::QueueDestination }
|
104
|
+
its(:name) { should be_a String }
|
105
|
+
its(:name) { should_not be_empty }
|
106
|
+
end
|
107
|
+
context "the resulting destination" do
|
108
|
+
let(:dest_name) { "my_dest" }
|
109
|
+
subject(:destination) { adapter.create_destination(dest_name, type: :queue, exclusive: true) }
|
110
|
+
before do
|
111
|
+
destination
|
112
|
+
end
|
113
|
+
|
114
|
+
it { should be_a BunnyAdapter::QueueDestination }
|
115
|
+
its(:name) { should be_a String }
|
116
|
+
its(:name) { should eq(dest_name) }
|
117
|
+
|
118
|
+
include_examples "supports #message_count"
|
119
|
+
|
120
|
+
it "strips off the type so it isn't set on the destination" do
|
121
|
+
expect(subject.dest_options).to_not have_key :type
|
122
|
+
end
|
123
|
+
it "ensures the queue is declared" do
|
124
|
+
expect {
|
125
|
+
connection.with_channel do |ch|
|
126
|
+
ch.queue(dest_name, passive: true)
|
127
|
+
end
|
128
|
+
}.to_not raise_error
|
129
|
+
end
|
130
|
+
context "publishing a message" do
|
131
|
+
let(:body) { "Testing the QueueDestination" }
|
132
|
+
let(:headers) { {"foo" => "bar"} }
|
133
|
+
let(:properties) { {persistent: false} }
|
134
|
+
before do
|
135
|
+
subject.publish(body, headers, properties)
|
136
|
+
end
|
137
|
+
it "publishes via the default exchange" do
|
138
|
+
msg = subject.pop_message
|
139
|
+
expect(msg.body).to eq(body)
|
140
|
+
expect(msg.headers).to eq(headers)
|
141
|
+
expect(msg.properties[:delivery_mode]).to eq(1)
|
142
|
+
expect(msg.delivery_info.exchange).to eq("")
|
143
|
+
expect(msg.delivery_info.routing_key).to eq(subject.name)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
it_behaves_like "a destination"
|
147
|
+
end
|
148
|
+
context "and bindings are provided" do
|
149
|
+
let(:dest_name) { "binding_test_queue" }
|
150
|
+
let(:exchange) { adapter.create_destination("amq.direct", type: :exchange) }
|
151
|
+
|
152
|
+
it "raises an exception if you don't provide a source" do
|
153
|
+
expect {
|
154
|
+
adapter.create_destination("bad_bind_queue", type: :queue, exclusive: true, bindings: [{args: {routing_key: "test_exchange_bind"}}])
|
155
|
+
}.to raise_error MessageDriver::Exception, /must provide a source/
|
156
|
+
end
|
157
|
+
|
158
|
+
it "routes message to the queue through the exchange" do
|
159
|
+
destination = adapter.create_destination(dest_name, type: :queue, exclusive: true, bindings: [{source: "amq.direct", args: {routing_key: "test_queue_bind"}}])
|
160
|
+
exchange.publish("test queue bindings", {}, {routing_key: "test_queue_bind"})
|
161
|
+
message = destination.pop_message
|
162
|
+
expect(message).to_not be_nil
|
163
|
+
expect(message.body).to eq("test queue bindings")
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
context "we are not yet connected to the broker and :no_declare is provided" do
|
168
|
+
it "doesn't cause a connection to the broker" do
|
169
|
+
connection.stop
|
170
|
+
adapter.create_destination("test_queue", no_declare: true, type: :queue, exclusive: true)
|
171
|
+
expect(adapter.connection(false)).to_not be_open
|
172
|
+
end
|
173
|
+
|
174
|
+
context "with a server-named queue" do
|
175
|
+
it "raises an error" do
|
176
|
+
expect {
|
177
|
+
adapter.create_destination("", no_declare: true, type: :queue, exclusive: true)
|
178
|
+
}.to raise_error MessageDriver::Exception, "server-named queues must be declared, but you provided :no_declare => true"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
context "with bindings" do
|
183
|
+
it "raises an error" do
|
184
|
+
expect {
|
185
|
+
adapter.create_destination("tmp_queue", no_declare: true, bindings: [{source: "amq.fanout"}], type: :queue, exclusive: true)
|
186
|
+
}.to raise_error MessageDriver::Exception, "queues with bindings must be declared, but you provided :no_declare => true"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
context "the type is exchange" do
|
193
|
+
context "the resulting destination" do
|
194
|
+
let(:dest_name) { "my_dest" }
|
195
|
+
subject(:destination) { adapter.create_destination(dest_name, type: :exchange) }
|
196
|
+
|
197
|
+
it { should be_a BunnyAdapter::ExchangeDestination }
|
198
|
+
include_examples "doesn't support #message_count"
|
199
|
+
|
200
|
+
it "strips off the type so it isn't set on the destination" do
|
201
|
+
expect(subject.dest_options).to_not have_key :type
|
202
|
+
end
|
203
|
+
|
204
|
+
it "raises an error when pop_message is called" do
|
205
|
+
expect {
|
206
|
+
subject.pop_message(dest_name)
|
207
|
+
}.to raise_error MessageDriver::Exception, "You can't pop a message off an exchange"
|
208
|
+
end
|
209
|
+
|
210
|
+
context "publishing a message" do
|
211
|
+
let(:body) { "Testing the ExchangeDestination" }
|
212
|
+
let(:headers) { {"foo" => "bar"} }
|
213
|
+
let(:properties) { {persistent: false} }
|
214
|
+
before { connection.with_channel { |ch| ch.fanout(dest_name, auto_delete: true) } }
|
215
|
+
let!(:queue) do
|
216
|
+
q = nil
|
217
|
+
connection.with_channel do |ch|
|
218
|
+
q = ch.queue("", exclusive: true)
|
219
|
+
q.bind(dest_name)
|
220
|
+
end
|
221
|
+
q
|
222
|
+
end
|
223
|
+
before do
|
224
|
+
subject.publish(body, headers, properties)
|
225
|
+
end
|
226
|
+
|
227
|
+
it "publishes to the specified exchange" do
|
228
|
+
connection.with_channel do |ch|
|
229
|
+
q = ch.queue(queue.name, passive: true)
|
230
|
+
msg = q.pop
|
231
|
+
expect(msg[2]).to eq(body)
|
232
|
+
expect(msg[0].exchange).to eq(dest_name)
|
233
|
+
expect(msg[1][:headers]).to eq(headers)
|
234
|
+
expect(msg[1][:delivery_mode]).to eq(1)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
context "declaring an exchange on the broker" do
|
241
|
+
let(:dest_name) { "my.cool.exchange" }
|
242
|
+
|
243
|
+
it "creates the exchange if you include 'declare' in the options" do
|
244
|
+
exchange = adapter.create_destination(dest_name, type: :exchange, declare: {type: :fanout, auto_delete: true})
|
245
|
+
queue = adapter.create_destination("", type: :queue, exclusive: true, bindings: [{source: dest_name}])
|
246
|
+
exchange.publish("test declaring exchange")
|
247
|
+
message = queue.pop_message
|
248
|
+
expect(message).to_not be_nil
|
249
|
+
expect(message.body).to eq("test declaring exchange")
|
250
|
+
end
|
251
|
+
|
252
|
+
it "raises an error if you don't provide a type" do
|
253
|
+
expect {
|
254
|
+
adapter.create_destination(dest_name, type: :exchange, declare: {auto_delete: true})
|
255
|
+
}.to raise_error MessageDriver::Exception, /you must provide a valid exchange type/
|
256
|
+
end
|
257
|
+
|
258
|
+
end
|
259
|
+
|
260
|
+
context "and bindings are provided" do
|
261
|
+
let(:dest_name) { "binding_exchange_queue" }
|
262
|
+
let(:exchange) { adapter.create_destination("amq.direct", type: :exchange) }
|
263
|
+
|
264
|
+
it "raises an exception if you don't provide a source" do
|
265
|
+
expect {
|
266
|
+
adapter.create_destination("amq.fanout", type: :exchange, bindings: [{args: {routing_key: "test_exchange_bind"}}])
|
267
|
+
}.to raise_error MessageDriver::Exception, /must provide a source/
|
268
|
+
end
|
269
|
+
|
270
|
+
it "routes message to the queue through the exchange" do
|
271
|
+
adapter.create_destination("amq.fanout", type: :exchange, bindings: [{source: "amq.direct", args: {routing_key: "test_exchange_bind"}}])
|
272
|
+
destination = adapter.create_destination(dest_name, type: :queue, exclusive: true, bindings: [{source: "amq.fanout"}])
|
273
|
+
exchange.publish("test exchange bindings", {}, {routing_key: "test_exchange_bind"})
|
274
|
+
message = destination.pop_message
|
275
|
+
expect(message).to_not be_nil
|
276
|
+
expect(message.body).to eq("test exchange bindings")
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
context "we are not yet connected to the broker" do
|
281
|
+
before do
|
282
|
+
connection.stop
|
283
|
+
end
|
284
|
+
|
285
|
+
it "doesn't cause a connection to the broker" do
|
286
|
+
adapter.create_destination("amq.fanout", type: :exchange)
|
287
|
+
expect(adapter.connection(false)).to_not be_open
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
context "the type is invalid" do
|
293
|
+
it "raises in an error" do
|
294
|
+
expect {
|
295
|
+
adapter.create_destination("my_dest", type: :foo_bar)
|
296
|
+
}.to raise_error MessageDriver::Exception, "invalid destination type #{:foo_bar}"
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'message_driver'
|
2
|
+
|
3
|
+
require File.join(File.dirname(__FILE__), '..', 'test_lib', 'broker_config')
|
4
|
+
|
5
|
+
Dir["./spec/support/**/*.rb"].sort.each {|f| require f}
|
6
|
+
|
7
|
+
RSpec.configure do |c|
|
8
|
+
c.treat_symbols_as_metadata_keys_with_true_values = true
|
9
|
+
c.order = 'random'
|
10
|
+
c.filter_run :focus
|
11
|
+
|
12
|
+
c.reporter.message("Acceptance Tests running with broker config: #{BrokerConfig.config}")
|
13
|
+
|
14
|
+
c.filter_run_excluding :no_travis if ENV['TRAVIS']=='true' && ENV['ADAPTER']=='bunny'
|
15
|
+
if c.inclusion_filter[:all_adapters] == true
|
16
|
+
c.filter_run BrokerConfig.current_adapter
|
17
|
+
else
|
18
|
+
c.run_all_when_everything_filtered = true
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
shared_examples "a destination" do
|
2
|
+
describe "#pop_message" do
|
3
|
+
let(:body) { "The message body" }
|
4
|
+
let(:headers) { { "foo" => "bar", "bar" => "baz"} }
|
5
|
+
let(:properties) { {persistent: true, client_ack: true} }
|
6
|
+
|
7
|
+
before do
|
8
|
+
destination.publish(body, headers, properties)
|
9
|
+
end
|
10
|
+
|
11
|
+
context "the result" do
|
12
|
+
let!(:message) { destination.pop_message }
|
13
|
+
subject { message }
|
14
|
+
|
15
|
+
it { should be_a MessageDriver::Message::Base }
|
16
|
+
its(:body) { should eq(body) }
|
17
|
+
its(:headers) { should eq(headers) }
|
18
|
+
its(:properties) { should_not be_nil }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
shared_examples "doesn't support #message_count" do
|
24
|
+
describe "#message_count" do
|
25
|
+
it "raises an error" do
|
26
|
+
expect {
|
27
|
+
destination.message_count
|
28
|
+
}.to raise_error "#message_count is not supported by #{destination.class}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
shared_examples "supports #message_count" do
|
34
|
+
#FIXME this example fails on travis with the bunny adapter :(
|
35
|
+
it "reports it's message_count", :no_travis do
|
36
|
+
expect {
|
37
|
+
destination.publish("msg1")
|
38
|
+
destination.publish("msg2")
|
39
|
+
}.to change{destination.message_count}.by(2)
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module MessageDriver::Adapters
|
4
|
+
describe Base do
|
5
|
+
class TestAdapter < described_class
|
6
|
+
def initialize(configuration)
|
7
|
+
|
8
|
+
end
|
9
|
+
end
|
10
|
+
subject { TestAdapter.new({}) }
|
11
|
+
|
12
|
+
describe "#publish" do
|
13
|
+
it "raises an error" do
|
14
|
+
expect {
|
15
|
+
subject.publish(:destination, {foo: "bar"})
|
16
|
+
}.to raise_error "Must be implemented in subclass"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#pop_message" do
|
21
|
+
it "raises an error" do
|
22
|
+
expect {
|
23
|
+
subject.pop_message(:destination)
|
24
|
+
}.to raise_error "Must be implemented in subclass"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#stop" do
|
29
|
+
it "raises an error" do
|
30
|
+
expect {
|
31
|
+
subject.stop
|
32
|
+
}.to raise_error "Must be implemented in subclass"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#create_destination" do
|
37
|
+
it "raises an error" do
|
38
|
+
expect {
|
39
|
+
subject.create_destination("foo")
|
40
|
+
}.to raise_error "Must be implemented in subclass"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'message_driver/adapters/in_memory_adapter'
|
4
|
+
|
5
|
+
module MessageDriver::Adapters
|
6
|
+
describe InMemoryAdapter do
|
7
|
+
let(:adapter) { described_class.new }
|
8
|
+
|
9
|
+
describe "#create_destination" do
|
10
|
+
describe "the resulting destination" do
|
11
|
+
let!(:destination) { adapter.create_destination("my_test_dest") }
|
12
|
+
it_behaves_like "a destination"
|
13
|
+
|
14
|
+
subject { destination }
|
15
|
+
|
16
|
+
it { should be_a InMemoryAdapter::Destination }
|
17
|
+
|
18
|
+
include_examples "supports #message_count"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#reset_after_tests" do
|
23
|
+
#make some destinations
|
24
|
+
# throw some messages on it
|
25
|
+
# assert the messages are in the destinations
|
26
|
+
# empty the queues on each destination via method
|
27
|
+
# assert destinations are empty
|
28
|
+
|
29
|
+
it "empties all the destination queues" do
|
30
|
+
destinations = (1..3).map(&adapter.method(:create_destination))
|
31
|
+
destinations.each do |destination|
|
32
|
+
destination.publish("There's always money in the banana stand!", {}, {})
|
33
|
+
end
|
34
|
+
|
35
|
+
adapter.reset_after_tests
|
36
|
+
|
37
|
+
destinations.each do |destination|
|
38
|
+
expect(destination.pop_message).to be_nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|