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.
Files changed (58) hide show
  1. data/.gitignore +20 -0
  2. data/.rbenv-version +1 -0
  3. data/.relish +2 -0
  4. data/.rspec +2 -0
  5. data/.travis.yml +23 -0
  6. data/CHANGELOG.md +17 -0
  7. data/Gemfile +20 -0
  8. data/Guardfile +39 -0
  9. data/LICENSE.txt +22 -0
  10. data/README.md +36 -0
  11. data/Rakefile +23 -0
  12. data/features/.nav +12 -0
  13. data/features/CHANGELOG.md +17 -0
  14. data/features/README.md +1 -0
  15. data/features/Rails.md +1 -0
  16. data/features/amqp_specific_features/README.md +3 -0
  17. data/features/amqp_specific_features/binding_amqp_destinations.feature +50 -0
  18. data/features/amqp_specific_features/declaring_amqp_exchanges.feature +22 -0
  19. data/features/amqp_specific_features/server_named_destinations.feature +35 -0
  20. data/features/destination_metadata.feature +33 -0
  21. data/features/dynamic_destinations.feature +41 -0
  22. data/features/error_handling.feature +47 -0
  23. data/features/getting_started.md +1 -0
  24. data/features/publishing_a_message.feature +19 -0
  25. data/features/publishing_with_transactions.feature +36 -0
  26. data/features/step_definitions/dynamic_destinations_steps.rb +12 -0
  27. data/features/step_definitions/error_handling_steps.rb +11 -0
  28. data/features/step_definitions/steps.rb +41 -0
  29. data/features/support/env.rb +7 -0
  30. data/features/support/firewall_helper.rb +59 -0
  31. data/features/support/message_table_matcher.rb +11 -0
  32. data/features/support/no_error_matcher.rb +13 -0
  33. data/features/support/test_runner.rb +50 -0
  34. data/features/support/transforms.rb +17 -0
  35. data/lib/message-driver.rb +1 -0
  36. data/lib/message_driver/adapters/base.rb +29 -0
  37. data/lib/message_driver/adapters/bunny_adapter.rb +270 -0
  38. data/lib/message_driver/adapters/in_memory_adapter.rb +58 -0
  39. data/lib/message_driver/broker.rb +95 -0
  40. data/lib/message_driver/destination.rb +31 -0
  41. data/lib/message_driver/exceptions.rb +18 -0
  42. data/lib/message_driver/message.rb +13 -0
  43. data/lib/message_driver/message_publisher.rb +15 -0
  44. data/lib/message_driver/version.rb +5 -0
  45. data/lib/message_driver.rb +18 -0
  46. data/message-driver.gemspec +27 -0
  47. data/spec/integration/amqp_integration_spec.rb +146 -0
  48. data/spec/integration/message_driver/adapters/bunny_adapter_spec.rb +301 -0
  49. data/spec/spec_helper.rb +20 -0
  50. data/spec/support/shared/destination_examples.rb +41 -0
  51. data/spec/units/message_driver/adapters/base_spec.rb +44 -0
  52. data/spec/units/message_driver/adapters/in_memory_adapter_spec.rb +43 -0
  53. data/spec/units/message_driver/broker_spec.rb +98 -0
  54. data/spec/units/message_driver/destination_spec.rb +11 -0
  55. data/spec/units/message_driver/message_publisher_spec.rb +65 -0
  56. data/spec/units/message_driver/message_spec.rb +19 -0
  57. data/test_lib/broker_config.rb +25 -0
  58. 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
@@ -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