message-driver 0.1.0 → 0.2.0.rc1

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 (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