message-driver 0.1.0 → 0.2.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.travis.yml +18 -7
  4. data/CHANGELOG.md +12 -2
  5. data/Gemfile +17 -0
  6. data/Guardfile +8 -4
  7. data/README.md +14 -5
  8. data/Rakefile +44 -11
  9. data/examples/basic_producer_and_consumer/Gemfile +5 -0
  10. data/examples/basic_producer_and_consumer/common.rb +17 -0
  11. data/examples/basic_producer_and_consumer/consumer.rb +24 -0
  12. data/examples/basic_producer_and_consumer/producer.rb +33 -0
  13. data/features/.nav +8 -0
  14. data/features/CHANGELOG.md +12 -2
  15. data/features/amqp_specific_features/binding_amqp_destinations.feature +7 -7
  16. data/features/amqp_specific_features/declaring_amqp_exchanges.feature +3 -3
  17. data/features/amqp_specific_features/nack_redelivered_messages.feature +92 -0
  18. data/features/amqp_specific_features/requeueing_on_nack.feature +44 -0
  19. data/features/amqp_specific_features/server_named_destinations.feature +5 -5
  20. data/features/client_acks.feature +92 -0
  21. data/features/destination_metadata.feature +9 -11
  22. data/features/dynamic_destinations.feature +7 -7
  23. data/features/error_handling.feature +11 -9
  24. data/features/logging.feature +14 -0
  25. data/features/message_consumers/auto_ack_consumers.feature +79 -0
  26. data/features/message_consumers/manual_ack_consumers.feature +95 -0
  27. data/features/message_consumers/transactional_ack_consumers.feature +77 -0
  28. data/features/message_consumers.feature +54 -0
  29. data/features/publishing_a_message.feature +6 -10
  30. data/features/publishing_with_transactions.feature +10 -14
  31. data/features/rabbitmq_specific_features/dead_letter_queueing.feature +116 -0
  32. data/features/step_definitions/dynamic_destinations_steps.rb +3 -3
  33. data/features/step_definitions/error_handling_steps.rb +4 -2
  34. data/features/step_definitions/logging_steps.rb +28 -0
  35. data/features/step_definitions/message_consumers_steps.rb +29 -0
  36. data/features/step_definitions/steps.rb +60 -9
  37. data/features/support/broker_config_helper.rb +19 -0
  38. data/features/support/env.rb +1 -0
  39. data/features/support/firewall_helper.rb +8 -11
  40. data/features/support/message_table_matcher.rb +21 -5
  41. data/features/support/test_runner.rb +39 -16
  42. data/lib/message_driver/adapters/base.rb +51 -4
  43. data/lib/message_driver/adapters/bunny_adapter.rb +251 -127
  44. data/lib/message_driver/adapters/in_memory_adapter.rb +97 -18
  45. data/lib/message_driver/adapters/stomp_adapter.rb +127 -0
  46. data/lib/message_driver/broker.rb +23 -24
  47. data/lib/message_driver/client.rb +157 -0
  48. data/lib/message_driver/destination.rb +7 -4
  49. data/lib/message_driver/errors.rb +27 -0
  50. data/lib/message_driver/logging.rb +11 -0
  51. data/lib/message_driver/message.rb +8 -0
  52. data/lib/message_driver/subscription.rb +18 -0
  53. data/lib/message_driver/vendor/.document +0 -0
  54. data/lib/message_driver/vendor/nesty/nested_error.rb +26 -0
  55. data/lib/message_driver/vendor/nesty.rb +1 -0
  56. data/lib/message_driver/version.rb +1 -1
  57. data/lib/message_driver.rb +4 -2
  58. data/message-driver.gemspec +4 -4
  59. data/spec/integration/{amqp_integration_spec.rb → bunny/amqp_integration_spec.rb} +29 -28
  60. data/spec/integration/bunny/bunny_adapter_spec.rb +339 -0
  61. data/spec/integration/in_memory/in_memory_adapter_spec.rb +126 -0
  62. data/spec/integration/stomp/stomp_adapter_spec.rb +142 -0
  63. data/spec/spec_helper.rb +5 -2
  64. data/spec/support/shared/adapter_examples.rb +17 -0
  65. data/spec/support/shared/client_ack_examples.rb +18 -0
  66. data/spec/support/shared/context_examples.rb +14 -0
  67. data/spec/support/shared/destination_examples.rb +4 -5
  68. data/spec/support/shared/subscription_examples.rb +146 -0
  69. data/spec/support/shared/transaction_examples.rb +43 -0
  70. data/spec/support/utils.rb +14 -0
  71. data/spec/units/message_driver/adapters/base_spec.rb +38 -19
  72. data/spec/units/message_driver/broker_spec.rb +71 -18
  73. data/spec/units/message_driver/client_spec.rb +375 -0
  74. data/spec/units/message_driver/destination_spec.rb +9 -0
  75. data/spec/units/message_driver/logging_spec.rb +18 -0
  76. data/spec/units/message_driver/message_spec.rb +36 -0
  77. data/spec/units/message_driver/subscription_spec.rb +24 -0
  78. data/test_lib/broker_config.rb +50 -20
  79. metadata +83 -45
  80. data/.rbenv-version +0 -1
  81. data/lib/message_driver/exceptions.rb +0 -18
  82. data/lib/message_driver/message_publisher.rb +0 -15
  83. data/spec/integration/message_driver/adapters/bunny_adapter_spec.rb +0 -301
  84. data/spec/units/message_driver/adapters/in_memory_adapter_spec.rb +0 -43
  85. data/spec/units/message_driver/message_publisher_spec.rb +0 -65
@@ -1,33 +1,36 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe "AMQP Integration", :bunny, type: :integration do
4
- before do
4
+ before(:each) do
5
5
  MessageDriver.configure BrokerConfig.config
6
6
  end
7
+ after(:each) do
8
+ MessageDriver::Broker.stop
9
+ end
7
10
 
8
11
  context "when a queue can't be found" do
9
12
  let(:queue_name) { "my.lost.queue" }
10
13
  it "raises a MessageDriver::QueueNotFound error" do
11
14
  expect {
12
15
  MessageDriver::Broker.dynamic_destination(queue_name, passive: true)
13
- }.to raise_error(MessageDriver::QueueNotFound, /#{queue_name}/) do |err|
16
+ }.to raise_error(MessageDriver::QueueNotFound) do |err|
14
17
  expect(err.queue_name).to eq(queue_name)
15
- expect(err.other).to be_a Bunny::NotFound
18
+ expect(err.nested).to be_a Bunny::NotFound
16
19
  end
17
20
  end
18
21
  end
19
22
 
20
23
  context "when a channel level exception occurs" do
21
- it "raises a MessageDriver::WrappedException error" do
24
+ it "raises a MessageDriver::WrappedError error" do
22
25
  expect {
23
26
  MessageDriver::Broker.dynamic_destination("not.a.queue", passive: true)
24
- }.to raise_error(MessageDriver::WrappedException) { |err| err.other.should be_a Bunny::ChannelLevelException }
27
+ }.to raise_error(MessageDriver::WrappedError) { |err| err.nested.should be_a Bunny::ChannelLevelException }
25
28
  end
26
29
 
27
30
  it "reestablishes the channel transparently" do
28
31
  expect {
29
32
  MessageDriver::Broker.dynamic_destination("not.a.queue", passive: true)
30
- }.to raise_error(MessageDriver::WrappedException)
33
+ }.to raise_error(MessageDriver::WrappedError)
31
34
  expect {
32
35
  MessageDriver::Broker.dynamic_destination("", exclusive: true)
33
36
  }.to_not raise_error
@@ -35,10 +38,10 @@ describe "AMQP Integration", :bunny, type: :integration do
35
38
 
36
39
  context "when in a transaction" do
37
40
  it "sets the channel_context as rollback-only until the transaction is finished" do
38
- MessageDriver::Broker.with_transaction do
41
+ MessageDriver::Client.with_message_transaction do
39
42
  expect {
40
43
  MessageDriver::Broker.dynamic_destination("not.a.queue", passive: true)
41
- }.to raise_error(MessageDriver::WrappedException)
44
+ }.to raise_error(MessageDriver::WrappedError)
42
45
  expect {
43
46
  MessageDriver::Broker.dynamic_destination("", exclusive: true)
44
47
  }.to raise_error(MessageDriver::TransactionRollbackOnly)
@@ -50,34 +53,32 @@ describe "AMQP Integration", :bunny, type: :integration do
50
53
  end
51
54
  end
52
55
 
53
- context "when the broker connection fails" do
54
- after(:each) do
55
- MessageDriver::Broker.stop
56
- end
56
+ context "when the broker connection fails", pending: "these spec are busted" do
57
57
  def disrupt_connection
58
58
  #yes, this is very implementation specific
59
59
  MessageDriver::Broker.adapter.connection.instance_variable_get(:@transport).close
60
60
  end
61
61
 
62
- it "raises a MessageDriver::ConnectionException" do
63
- dest = MessageDriver::Broker.dynamic_destination("", exclusive: true)
62
+ def create_destination(queue_name)
63
+ MessageDriver::Broker.dynamic_destination(queue_name, exclusive: true)
64
+ end
65
+
66
+ it "raises a MessageDriver::ConnectionError" do
67
+ dest = create_destination("test_queue")
64
68
  disrupt_connection
65
69
  expect {
66
70
  dest.publish("Reconnection Test")
67
- }.to raise_error(MessageDriver::ConnectionException) do |err|
68
- expect(err.other).to be_a Bunny::NetworkErrorWrapper
71
+ }.to raise_error(MessageDriver::ConnectionError) do |err|
72
+ expect(err.nested).to be_a Bunny::NetworkErrorWrapper
69
73
  end
70
74
  end
71
75
 
72
- def create_destination(queue_name)
73
- MessageDriver::Broker.dynamic_destination(queue_name, exclusive: true)
74
- end
75
76
  it "seemlessly reconnects" do
76
77
  dest = create_destination("seemless.reconnect.queue")
77
78
  disrupt_connection
78
79
  expect {
79
80
  dest.publish("Reconnection Test 1")
80
- }.to raise_error(MessageDriver::ConnectionException)
81
+ }.to raise_error(MessageDriver::ConnectionError)
81
82
  dest = create_destination("seemless.reconnect.queue")
82
83
  dest.publish("Reconnection Test 2")
83
84
  msg = dest.pop_message
@@ -86,21 +87,21 @@ describe "AMQP Integration", :bunny, type: :integration do
86
87
  end
87
88
 
88
89
  context "when in a transaction" do
89
- it "raises a MessageDriver::ConnectionException" do
90
+ it "raises a MessageDriver::ConnectionError" do
90
91
  expect {
91
- MessageDriver::Broker.with_transaction do
92
+ MessageDriver::Client.with_message_transaction do
92
93
  disrupt_connection
93
94
  MessageDriver::Broker.dynamic_destination("", exclusive: true)
94
95
  end
95
- }.to raise_error(MessageDriver::ConnectionException)
96
+ }.to raise_error(MessageDriver::ConnectionError)
96
97
  end
97
98
 
98
99
  it "sets the channel_context as rollback-only until the transaction is finished" do
99
- MessageDriver::Broker.with_transaction do
100
+ MessageDriver::Client.with_message_transaction do
100
101
  disrupt_connection
101
102
  expect {
102
103
  MessageDriver::Broker.dynamic_destination("", exclusive: true)
103
- }.to raise_error(MessageDriver::ConnectionException)
104
+ }.to raise_error(MessageDriver::ConnectionError)
104
105
  expect {
105
106
  MessageDriver::Broker.dynamic_destination("", exclusive: true)
106
107
  }.to raise_error(MessageDriver::TransactionRollbackOnly)
@@ -117,7 +118,7 @@ describe "AMQP Integration", :bunny, type: :integration do
117
118
 
118
119
  it "rolls back the transaction" do
119
120
  expect {
120
- MessageDriver::Broker.with_transaction do
121
+ MessageDriver::Client.with_message_transaction do
121
122
  destination.publish("Test Message")
122
123
  raise "unhandled error"
123
124
  end
@@ -127,14 +128,14 @@ describe "AMQP Integration", :bunny, type: :integration do
127
128
 
128
129
  it "allows the next transaction to continue" do
129
130
  expect {
130
- MessageDriver::Broker.with_transaction do
131
+ MessageDriver::Client.with_message_transaction do
131
132
  destination.publish("Test Message 1")
132
133
  raise "unhandled error"
133
134
  end
134
135
  }.to raise_error "unhandled error"
135
136
  expect(destination.pop_message).to be_nil
136
137
 
137
- MessageDriver::Broker.with_transaction do
138
+ MessageDriver::Client.with_message_transaction do
138
139
  destination.publish("Test Message 2")
139
140
  end
140
141
 
@@ -0,0 +1,339 @@
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::Error, "bunny 0.9.3 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.pre11 0.9.0.rc1 0.9.0 0.9.2).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.3 0.9.4 0.10.0 1.0.0.pre1).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
+ end
56
+
57
+ shared_context "a connected bunny adapter" do
58
+ subject(:adapter) { described_class.new(valid_connection_attrs) }
59
+ let(:connection) { adapter.connection }
60
+
61
+ before do
62
+ MessageDriver::Broker.configure(adapter: adapter)
63
+ end
64
+
65
+ after do
66
+ adapter.stop
67
+ 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
+
82
+ describe "#new_context" do
83
+ include_context "a connected bunny adapter"
84
+
85
+ it "returns a BunnyAdapter::BunnyContext" do
86
+ expect(subject.new_context).to be_a BunnyAdapter::BunnyContext
87
+ end
88
+ end
89
+
90
+ describe BunnyAdapter::BunnyContext do
91
+ include_context "a connected bunny adapter"
92
+ subject(:adapter_context) { adapter.new_context }
93
+
94
+ it_behaves_like "an adapter context"
95
+ it_behaves_like "transactions are supported"
96
+ it_behaves_like "client acks are supported"
97
+ it_behaves_like "subscriptions are supported", BunnyAdapter::Subscription
98
+
99
+ describe "#pop_message" do
100
+ include_context "with a queue"
101
+ it "needs some real tests"
102
+ end
103
+
104
+
105
+ describe "#invalidate" do
106
+ it "closes the channel" do
107
+ subject.with_channel(false) do |ch|
108
+ expect(ch).to be_open
109
+ end
110
+ subject.invalidate
111
+ expect(subject.instance_variable_get(:@channel)).to be_closed
112
+ end
113
+ end
114
+
115
+ describe "#create_destination" do
116
+
117
+ context "with defaults" do
118
+ context "the resulting destination" do
119
+ let(:dest_name) { "my_dest" }
120
+ subject(:result) { adapter_context.create_destination(dest_name, exclusive: true) }
121
+
122
+ it { should be_a BunnyAdapter::QueueDestination }
123
+ end
124
+ end
125
+
126
+ context "the type is queue" do
127
+ context "and there is no destination name given" do
128
+ subject(:destination) { adapter_context.create_destination("", type: :queue, exclusive: true) }
129
+ it { should be_a BunnyAdapter::QueueDestination }
130
+ its(:name) { should be_a String }
131
+ its(:name) { should_not be_empty }
132
+ end
133
+ context "the resulting destination" do
134
+ let(:dest_name) { "my_dest" }
135
+ subject(:destination) { adapter_context.create_destination(dest_name, type: :queue, exclusive: true) }
136
+ before do
137
+ destination
138
+ end
139
+
140
+ it { should be_a BunnyAdapter::QueueDestination }
141
+ its(:name) { should be_a String }
142
+ its(:name) { should eq(dest_name) }
143
+
144
+ include_examples "supports #message_count"
145
+
146
+ it "strips off the type so it isn't set on the destination" do
147
+ expect(subject.dest_options).to_not have_key :type
148
+ end
149
+ it "ensures the queue is declared" do
150
+ expect {
151
+ connection.with_channel do |ch|
152
+ ch.queue(dest_name, passive: true)
153
+ end
154
+ }.to_not raise_error
155
+ end
156
+ context "publishing a message" do
157
+ let(:body) { "Testing the QueueDestination" }
158
+ let(:headers) { {"foo" => "bar"} }
159
+ let(:properties) { {persistent: false} }
160
+ before do
161
+ subject.publish(body, headers, properties)
162
+ end
163
+ it "publishes via the default exchange" do
164
+ msg = subject.pop_message
165
+ expect(msg.body).to eq(body)
166
+ expect(msg.headers).to eq(headers)
167
+ expect(msg.properties[:delivery_mode]).to eq(1)
168
+ expect(msg.delivery_info.exchange).to eq("")
169
+ expect(msg.delivery_info.routing_key).to eq(subject.name)
170
+ end
171
+ end
172
+ it_behaves_like "a destination"
173
+ end
174
+ context "and bindings are provided" do
175
+ let(:dest_name) { "binding_test_queue" }
176
+ let(:exchange) { adapter_context.create_destination("amq.direct", type: :exchange) }
177
+
178
+ it "raises an exception if you don't provide a source" do
179
+ expect {
180
+ adapter_context.create_destination("bad_bind_queue", type: :queue, exclusive: true, bindings: [{args: {routing_key: "test_exchange_bind"}}])
181
+ }.to raise_error MessageDriver::Error, /must provide a source/
182
+ end
183
+
184
+ it "routes message to the queue through the exchange" do
185
+ destination = adapter_context.create_destination(dest_name, type: :queue, exclusive: true, bindings: [{source: "amq.direct", args: {routing_key: "test_queue_bind"}}])
186
+ exchange.publish("test queue bindings", {}, {routing_key: "test_queue_bind"})
187
+ message = destination.pop_message
188
+ expect(message).to_not be_nil
189
+ expect(message.body).to eq("test queue bindings")
190
+ end
191
+ end
192
+
193
+ context "we are not yet connected to the broker and :no_declare is provided" do
194
+ it "doesn't cause a connection to the broker" do
195
+ connection.stop
196
+ adapter_context.create_destination("test_queue", no_declare: true, type: :queue, exclusive: true)
197
+ expect(adapter.connection(false)).to_not be_open
198
+ end
199
+
200
+ context "with a server-named queue" do
201
+ it "raises an error" do
202
+ expect {
203
+ adapter_context.create_destination("", no_declare: true, type: :queue, exclusive: true)
204
+ }.to raise_error MessageDriver::Error, "server-named queues must be declared, but you provided :no_declare => true"
205
+ end
206
+ end
207
+
208
+ context "with bindings" do
209
+ it "raises an error" do
210
+ expect {
211
+ adapter_context.create_destination("tmp_queue", no_declare: true, bindings: [{source: "amq.fanout"}], type: :queue, exclusive: true)
212
+ }.to raise_error MessageDriver::Error, "queues with bindings must be declared, but you provided :no_declare => true"
213
+ end
214
+ end
215
+ end
216
+ end
217
+
218
+ context "the type is exchange" do
219
+ context "the resulting destination" do
220
+ let(:dest_name) { "my_dest" }
221
+ subject(:destination) { adapter_context.create_destination(dest_name, type: :exchange) }
222
+
223
+ it { should be_a BunnyAdapter::ExchangeDestination }
224
+ include_examples "doesn't support #message_count"
225
+
226
+ it "strips off the type so it isn't set on the destination" do
227
+ expect(subject.dest_options).to_not have_key :type
228
+ end
229
+
230
+ it "raises an error when pop_message is called" do
231
+ expect {
232
+ subject.pop_message(dest_name)
233
+ }.to raise_error MessageDriver::Error, "You can't pop a message off an exchange"
234
+ end
235
+
236
+ context "publishing a message" do
237
+ let(:body) { "Testing the ExchangeDestination" }
238
+ let(:headers) { {"foo" => "bar"} }
239
+ let(:properties) { {persistent: false} }
240
+ before { connection.with_channel { |ch| ch.fanout(dest_name, auto_delete: true) } }
241
+ let!(:queue) do
242
+ q = nil
243
+ connection.with_channel do |ch|
244
+ q = ch.queue("", exclusive: true)
245
+ q.bind(dest_name)
246
+ end
247
+ q
248
+ end
249
+ before do
250
+ subject.publish(body, headers, properties)
251
+ end
252
+
253
+ it "publishes to the specified exchange" do
254
+ connection.with_channel do |ch|
255
+ q = ch.queue(queue.name, passive: true)
256
+ msg = q.pop
257
+ expect(msg[2]).to eq(body)
258
+ expect(msg[0].exchange).to eq(dest_name)
259
+ expect(msg[1][:headers]).to eq(headers)
260
+ expect(msg[1][:delivery_mode]).to eq(1)
261
+ end
262
+ end
263
+ end
264
+ end
265
+
266
+ context "declaring an exchange on the broker" do
267
+ let(:dest_name) { "my.cool.exchange" }
268
+
269
+ it "creates the exchange if you include 'declare' in the options" do
270
+ exchange = adapter_context.create_destination(dest_name, type: :exchange, declare: {type: :fanout, auto_delete: true})
271
+ queue = adapter_context.create_destination("", type: :queue, exclusive: true, bindings: [{source: dest_name}])
272
+ exchange.publish("test declaring exchange")
273
+ message = queue.pop_message
274
+ expect(message).to_not be_nil
275
+ expect(message.body).to eq("test declaring exchange")
276
+ end
277
+
278
+ it "raises an error if you don't provide a type" do
279
+ expect {
280
+ adapter_context.create_destination(dest_name, type: :exchange, declare: {auto_delete: true})
281
+ }.to raise_error MessageDriver::Error, /you must provide a valid exchange type/
282
+ end
283
+
284
+ end
285
+
286
+ context "and bindings are provided" do
287
+ let(:dest_name) { "binding_exchange_queue" }
288
+ let(:exchange) { adapter_context.create_destination("amq.direct", type: :exchange) }
289
+
290
+ it "raises an exception if you don't provide a source" do
291
+ expect {
292
+ adapter_context.create_destination("amq.fanout", type: :exchange, bindings: [{args: {routing_key: "test_exchange_bind"}}])
293
+ }.to raise_error MessageDriver::Error, /must provide a source/
294
+ end
295
+
296
+ it "routes message to the queue through the exchange" do
297
+ adapter_context.create_destination("amq.fanout", type: :exchange, bindings: [{source: "amq.direct", args: {routing_key: "test_exchange_bind"}}])
298
+ destination = adapter_context.create_destination(dest_name, type: :queue, exclusive: true, bindings: [{source: "amq.fanout"}])
299
+ exchange.publish("test exchange bindings", {}, {routing_key: "test_exchange_bind"})
300
+ message = destination.pop_message
301
+ expect(message).to_not be_nil
302
+ expect(message.body).to eq("test exchange bindings")
303
+ end
304
+ end
305
+
306
+ context "we are not yet connected to the broker" do
307
+ it "doesn't cause a connection to the broker" do
308
+ connection.stop
309
+ adapter_context.create_destination("amq.fanout", type: :exchange)
310
+ expect(adapter.connection(false)).to_not be_open
311
+ end
312
+ end
313
+ end
314
+
315
+ context "the type is invalid" do
316
+ it "raises in an error" do
317
+ expect {
318
+ adapter_context.create_destination("my_dest", type: :foo_bar)
319
+ }.to raise_error MessageDriver::Error, "invalid destination type #{:foo_bar}"
320
+ end
321
+ end
322
+ end
323
+
324
+ describe "#subscribe" do
325
+ context "the destination is an ExchangeDestination" do
326
+ let(:dest_name) { "my_dest" }
327
+ let(:destination) { adapter_context.create_destination(dest_name, type: :exchange) }
328
+ let(:consumer) { lambda do |m|; end }
329
+
330
+ it "raises an error" do
331
+ expect {
332
+ adapter_context.subscribe(destination, &consumer)
333
+ }.to raise_error MessageDriver::Error, /QueueDestination/
334
+ end
335
+ end
336
+ end
337
+ end
338
+ end
339
+ end
@@ -0,0 +1,126 @@
1
+ require 'spec_helper'
2
+
3
+ require 'message_driver/adapters/in_memory_adapter'
4
+
5
+ module MessageDriver::Adapters
6
+ describe InMemoryAdapter, :in_memory, type: :integration do
7
+ subject(:adapter) { described_class.new }
8
+
9
+ describe "#new_context" do
10
+ it "returns a InMemoryAdapter::InMemoryContext" do
11
+ expect(subject.new_context).to be_a InMemoryAdapter::InMemoryContext
12
+ end
13
+ end
14
+
15
+ it_behaves_like "an adapter"
16
+
17
+ describe InMemoryAdapter::InMemoryContext do
18
+ subject(:adapter_context) { adapter.new_context }
19
+
20
+ it_behaves_like "an adapter context"
21
+ it_behaves_like "transactions are not supported"
22
+ it_behaves_like "client acks are not supported"
23
+ it_behaves_like "subscriptions are supported", InMemoryAdapter::Subscription
24
+ end
25
+
26
+ describe "#create_destination" do
27
+ describe "the resulting destination" do
28
+ subject(:destination) { adapter.create_destination("my_test_dest") }
29
+
30
+ it { should be_a InMemoryAdapter::Destination }
31
+
32
+ it_behaves_like "a destination"
33
+ include_examples "supports #message_count"
34
+ end
35
+
36
+ context "when creating two destinations for the same queue" do
37
+ it "creates seperate destination instances" do
38
+ queue_name = "my_queue"
39
+ dest1 = adapter.create_destination(queue_name)
40
+ dest2 = adapter.create_destination(queue_name)
41
+ expect(dest1).to_not be(dest2)
42
+ end
43
+ end
44
+ end
45
+
46
+ describe "#reset_after_tests" do
47
+ it "empties all the destination queues" do
48
+ destinations = (1..3).map(&adapter.method(:create_destination))
49
+ destinations.each do |destination|
50
+ destination.publish("There's always money in the banana stand!", {}, {})
51
+ end
52
+
53
+ adapter.reset_after_tests
54
+
55
+ destinations.each do |destination|
56
+ expect(destination.message_count).to eq(0)
57
+ end
58
+ end
59
+
60
+ it "removes any existing subscriptions" do
61
+ destinations = (1..3).map(&adapter.method(:create_destination))
62
+ consumer = lambda do |m| end
63
+ destinations.each do |destination|
64
+ destination.subscribe(&consumer)
65
+ end
66
+
67
+ adapter.reset_after_tests
68
+
69
+ destinations.each do |destination|
70
+ expect(destination.subscription).to be_nil
71
+ end
72
+
73
+ end
74
+ end
75
+
76
+ describe "accessing the same queue from two destinations" do
77
+ let(:queue_name) { "my_queue" }
78
+ let(:dest1) { adapter.create_destination(queue_name) }
79
+ let(:dest2) { adapter.create_destination(queue_name) }
80
+
81
+ context "when I have a consumer on one destination" do
82
+ let(:consumer) { lambda do |m| end }
83
+ before do
84
+ dest1.subscribe(&consumer)
85
+ end
86
+ it "is the same consumer on the other destination" do
87
+ expect(dest2.subscription.consumer).to be(consumer)
88
+ end
89
+ end
90
+
91
+ context "when I publish a message to one destination" do
92
+ it "changes the message_count on the other" do
93
+ expect {
94
+ dest1.publish("my test message")
95
+ }.to change{dest2.message_count}.from(0).to(1)
96
+ end
97
+
98
+ it "can be popped off the other" do
99
+ dest1.publish("my test message")
100
+ msg = dest2.pop_message
101
+ expect(msg).to_not be_nil
102
+ expect(msg.body).to eq("my test message")
103
+ end
104
+ end
105
+
106
+ context "when I pop a message off one destination" do
107
+ let(:message_body) { "test popping a message" }
108
+ before do
109
+ dest2.publish(message_body)
110
+ end
111
+
112
+ it "changes the message_count on the other" do
113
+ expect {
114
+ dest1.pop_message
115
+ }.to change{dest2.message_count}.from(1).to(0)
116
+ end
117
+ end
118
+ end
119
+
120
+ describe "subscribing a consumer" do
121
+ let(:destination) { adapter.create_destination(:my_queue) }
122
+
123
+ let(:subscription_type) { MessageDriver::Adapters::InMemoryAdapter::Subscription }
124
+ end
125
+ end
126
+ end