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.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.travis.yml +18 -7
- data/CHANGELOG.md +12 -2
- data/Gemfile +17 -0
- data/Guardfile +8 -4
- data/README.md +14 -5
- data/Rakefile +44 -11
- data/examples/basic_producer_and_consumer/Gemfile +5 -0
- data/examples/basic_producer_and_consumer/common.rb +17 -0
- data/examples/basic_producer_and_consumer/consumer.rb +24 -0
- data/examples/basic_producer_and_consumer/producer.rb +33 -0
- data/features/.nav +8 -0
- data/features/CHANGELOG.md +12 -2
- data/features/amqp_specific_features/binding_amqp_destinations.feature +7 -7
- data/features/amqp_specific_features/declaring_amqp_exchanges.feature +3 -3
- data/features/amqp_specific_features/nack_redelivered_messages.feature +92 -0
- data/features/amqp_specific_features/requeueing_on_nack.feature +44 -0
- data/features/amqp_specific_features/server_named_destinations.feature +5 -5
- data/features/client_acks.feature +92 -0
- data/features/destination_metadata.feature +9 -11
- data/features/dynamic_destinations.feature +7 -7
- data/features/error_handling.feature +11 -9
- data/features/logging.feature +14 -0
- data/features/message_consumers/auto_ack_consumers.feature +79 -0
- data/features/message_consumers/manual_ack_consumers.feature +95 -0
- data/features/message_consumers/transactional_ack_consumers.feature +77 -0
- data/features/message_consumers.feature +54 -0
- data/features/publishing_a_message.feature +6 -10
- data/features/publishing_with_transactions.feature +10 -14
- data/features/rabbitmq_specific_features/dead_letter_queueing.feature +116 -0
- data/features/step_definitions/dynamic_destinations_steps.rb +3 -3
- data/features/step_definitions/error_handling_steps.rb +4 -2
- data/features/step_definitions/logging_steps.rb +28 -0
- data/features/step_definitions/message_consumers_steps.rb +29 -0
- data/features/step_definitions/steps.rb +60 -9
- data/features/support/broker_config_helper.rb +19 -0
- data/features/support/env.rb +1 -0
- data/features/support/firewall_helper.rb +8 -11
- data/features/support/message_table_matcher.rb +21 -5
- data/features/support/test_runner.rb +39 -16
- data/lib/message_driver/adapters/base.rb +51 -4
- data/lib/message_driver/adapters/bunny_adapter.rb +251 -127
- data/lib/message_driver/adapters/in_memory_adapter.rb +97 -18
- data/lib/message_driver/adapters/stomp_adapter.rb +127 -0
- data/lib/message_driver/broker.rb +23 -24
- data/lib/message_driver/client.rb +157 -0
- data/lib/message_driver/destination.rb +7 -4
- data/lib/message_driver/errors.rb +27 -0
- data/lib/message_driver/logging.rb +11 -0
- data/lib/message_driver/message.rb +8 -0
- data/lib/message_driver/subscription.rb +18 -0
- data/lib/message_driver/vendor/.document +0 -0
- data/lib/message_driver/vendor/nesty/nested_error.rb +26 -0
- data/lib/message_driver/vendor/nesty.rb +1 -0
- data/lib/message_driver/version.rb +1 -1
- data/lib/message_driver.rb +4 -2
- data/message-driver.gemspec +4 -4
- data/spec/integration/{amqp_integration_spec.rb → bunny/amqp_integration_spec.rb} +29 -28
- data/spec/integration/bunny/bunny_adapter_spec.rb +339 -0
- data/spec/integration/in_memory/in_memory_adapter_spec.rb +126 -0
- data/spec/integration/stomp/stomp_adapter_spec.rb +142 -0
- data/spec/spec_helper.rb +5 -2
- data/spec/support/shared/adapter_examples.rb +17 -0
- data/spec/support/shared/client_ack_examples.rb +18 -0
- data/spec/support/shared/context_examples.rb +14 -0
- data/spec/support/shared/destination_examples.rb +4 -5
- data/spec/support/shared/subscription_examples.rb +146 -0
- data/spec/support/shared/transaction_examples.rb +43 -0
- data/spec/support/utils.rb +14 -0
- data/spec/units/message_driver/adapters/base_spec.rb +38 -19
- data/spec/units/message_driver/broker_spec.rb +71 -18
- data/spec/units/message_driver/client_spec.rb +375 -0
- data/spec/units/message_driver/destination_spec.rb +9 -0
- data/spec/units/message_driver/logging_spec.rb +18 -0
- data/spec/units/message_driver/message_spec.rb +36 -0
- data/spec/units/message_driver/subscription_spec.rb +24 -0
- data/test_lib/broker_config.rb +50 -20
- metadata +83 -45
- data/.rbenv-version +0 -1
- data/lib/message_driver/exceptions.rb +0 -18
- data/lib/message_driver/message_publisher.rb +0 -15
- data/spec/integration/message_driver/adapters/bunny_adapter_spec.rb +0 -301
- data/spec/units/message_driver/adapters/in_memory_adapter_spec.rb +0 -43
- 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
|
16
|
+
}.to raise_error(MessageDriver::QueueNotFound) do |err|
|
14
17
|
expect(err.queue_name).to eq(queue_name)
|
15
|
-
expect(err.
|
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::
|
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::
|
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::
|
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::
|
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::
|
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
|
-
|
63
|
-
|
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::
|
68
|
-
expect(err.
|
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::
|
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::
|
90
|
+
it "raises a MessageDriver::ConnectionError" do
|
90
91
|
expect {
|
91
|
-
MessageDriver::
|
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::
|
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::
|
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::
|
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::
|
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::
|
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::
|
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
|