right_amqp 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,100 @@
1
+ class MQ
2
+ # Basic RPC (remote procedure call) facility.
3
+ #
4
+ # Needs more detail and explanation.
5
+ #
6
+ # EM.run do
7
+ # server = MQ.rpc('hash table node', Hash)
8
+ #
9
+ # client = MQ.rpc('hash table node')
10
+ # client[:now] = Time.now
11
+ # client[:one] = 1
12
+ #
13
+ # client.values do |res|
14
+ # p 'client', :values => res
15
+ # end
16
+ #
17
+ # client.keys do |res|
18
+ # p 'client', :keys => res
19
+ # EM.stop_event_loop
20
+ # end
21
+ # end
22
+ #
23
+ class RPC < BlankSlate
24
+ # Takes a channel, queue and optional object.
25
+ #
26
+ # The optional object may be a class name, module name or object
27
+ # instance. When given a class or module name, the object is instantiated
28
+ # during this setup. The passed queue is automatically subscribed to so
29
+ # it passes all messages (and their arguments) to the object.
30
+ #
31
+ # Marshalling and unmarshalling the objects is handled internally. This
32
+ # marshalling is subject to the same restrictions as defined in the
33
+ # Marshal[http://ruby-doc.org/core/classes/Marshal.html] standard
34
+ # library. See that documentation for further reference.
35
+ #
36
+ # When the optional object is not passed, the returned rpc reference is
37
+ # used to send messages and arguments to the queue. See #method_missing
38
+ # which does all of the heavy lifting with the proxy. Some client
39
+ # elsewhere must call this method *with* the optional block so that
40
+ # there is a valid destination. Failure to do so will just enqueue
41
+ # marshalled messages that are never consumed.
42
+ #
43
+ def initialize mq, queue, obj = nil
44
+ @mq = mq
45
+ @mq.rpcs[queue] ||= self
46
+
47
+ if obj
48
+ @obj = case obj
49
+ when ::Class
50
+ obj.new
51
+ when ::Module
52
+ (::Class.new do include(obj) end).new
53
+ else
54
+ obj
55
+ end
56
+
57
+ @mq.queue(queue).subscribe(:ack=>true){ |info, request|
58
+ method, *args = ::Marshal.load(request)
59
+ ret = @obj.__send__(method, *args)
60
+
61
+ info.ack
62
+
63
+ if info.reply_to
64
+ @mq.queue(info.reply_to).publish(::Marshal.dump(ret), :key => info.reply_to, :message_id => info.message_id)
65
+ end
66
+ }
67
+ else
68
+ @callbacks ||= {}
69
+ # XXX implement and use queue(nil)
70
+ @queue = @mq.queue(@name = "random identifier #{::Kernel.rand(999_999_999_999)}", :auto_delete => true).subscribe{|info, msg|
71
+ if blk = @callbacks.delete(info.message_id)
72
+ blk.call ::Marshal.load(msg)
73
+ end
74
+ }
75
+ @remote = @mq.queue(queue)
76
+ end
77
+ end
78
+
79
+ # Calling MQ.rpc(*args) returns a proxy object without any methods beyond
80
+ # those in Object. All calls to the proxy are handled by #method_missing which
81
+ # works to marshal and unmarshal all method calls and their arguments.
82
+ #
83
+ # EM.run do
84
+ # server = MQ.rpc('hash table node', Hash)
85
+ # client = MQ.rpc('hash table node')
86
+ #
87
+ # # calls #method_missing on #[] which marshals the method name and
88
+ # # arguments to publish them to the remote
89
+ # client[:now] = Time.now
90
+ # ....
91
+ # end
92
+ #
93
+ def method_missing meth, *args, &blk
94
+ # XXX use uuids instead
95
+ message_id = "random message id #{::Kernel.rand(999_999_999_999)}"
96
+ @callbacks[message_id] = blk if blk
97
+ @remote.publish(::Marshal.dump([meth, *args]), :reply_to => blk ? @name : nil, :message_id => message_id)
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,57 @@
1
+ # -*-ruby-*-
2
+ # Copyright: Copyright (c) 2012 RightScale, Inc.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # 'Software'), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ require 'rubygems'
24
+
25
+ Gem::Specification.new do |spec|
26
+ spec.name = 'right_amqp'
27
+ spec.version = '0.2.0'
28
+ spec.authors = ['Lee Kirchhoff']
29
+ spec.email = 'lee@rightscale.com'
30
+ spec.homepage = 'https://github.com/rightscale/right_amqp'
31
+ spec.platform = Gem::Platform::RUBY
32
+ spec.summary = 'Client for interfacing to RightScale RabbitMQ broker using AMQP'
33
+ spec.has_rdoc = true
34
+ spec.rdoc_options = ["--main", "README.rdoc", "--title", "RightAMQP"]
35
+ spec.extra_rdoc_files = ["README.rdoc"]
36
+ spec.required_ruby_version = '>= 1.8.7'
37
+ spec.require_path = 'lib'
38
+
39
+ spec.add_dependency('right_support', '~> 1.2')
40
+ spec.add_dependency('eventmachine', '~> 0.12.10')
41
+
42
+ spec.description = <<-EOF
43
+ RightAMQP provides a high availability client for interfacing with the
44
+ RightScale RabbitMQ broker using the AMQP protocol. The AMQP version on which
45
+ this gem is based is 0.6.7 but beyond that it contains a number of bug fixes and
46
+ enhancements including reconnect, message return, heartbeat, and UTF-8 support.
47
+ The high availability is achieved by maintaining multiple broker connections
48
+ such that failed connections automatically reconnect and only connected
49
+ brokers are used when routing a message. Although the HABrokerClient class
50
+ is the intended primary means for accessing RabbitMQ services with this gem,
51
+ alternatively the underlying AMQP services may be used directly.
52
+ EOF
53
+
54
+ candidates = Dir.glob("{lib,spec}/**/*") +
55
+ ["LICENSE", "README.rdoc", "Rakefile", "right_amqp.gemspec"]
56
+ spec.files = candidates.sort
57
+ end
@@ -0,0 +1,105 @@
1
+ #
2
+ # Copyright (c) 2009-2012 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
24
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib', 'right_amqp'))
25
+
26
+ describe AMQP::Client do
27
+
28
+ context 'with an incorrect AMQP password' do
29
+
30
+ include RightAMQP::SpecHelper
31
+
32
+ class SUT
33
+ include AMQP::Client
34
+
35
+ attr_accessor :reconnecting, :settings, :channels
36
+ end
37
+
38
+ before(:each) do
39
+ setup_logger
40
+ @sut = flexmock(SUT.new)
41
+ @sut.reconnecting = false
42
+ @sut.settings = {:host => 'testhost', :port=>'12345'}
43
+ @sut.channels = {}
44
+
45
+ @sut.should_receive(:initialize)
46
+ end
47
+
48
+ context 'and no :reconnect_delay' do
49
+ it 'should reconnect immediately' do
50
+ flexmock(EM).should_receive(:reconnect).once
51
+ flexmock(EM).should_receive(:add_timer).never
52
+
53
+ @sut.reconnect()
54
+ end
55
+ end
56
+
57
+ context 'and a :reconnect_delay of true' do
58
+ it 'should reconnect immediately' do
59
+ @sut.settings[:reconnect_delay] = true
60
+
61
+ flexmock(EM).should_receive(:reconnect).once
62
+ flexmock(EM).should_receive(:add_timer).never
63
+
64
+ @sut.reconnect()
65
+ end
66
+ end
67
+
68
+ context 'and a :reconnect_delay of 15 seconds' do
69
+ it 'should schedule a reconnect attempt in 15s' do
70
+ @sut.settings[:reconnect_delay] = 15
71
+
72
+ flexmock(EM).should_receive(:reconnect).never
73
+ flexmock(EM).should_receive(:add_timer).with(15, Proc).once
74
+
75
+ @sut.reconnect()
76
+ end
77
+ end
78
+
79
+ context 'and a :reconnect_delay containing a Proc that returns 30' do
80
+ it 'should schedule a reconnect attempt in 30s' do
81
+ @sut.settings[:reconnect_delay] = Proc.new {30}
82
+
83
+ flexmock(EM).should_receive(:reconnect).never
84
+ flexmock(EM).should_receive(:add_timer).with(30, Proc).once
85
+
86
+ @sut.reconnect()
87
+ end
88
+ end
89
+
90
+ context 'and a :reconnect_interval of 5 seconds' do
91
+ it 'should schedule reconnect attempts on a 5s interval' do
92
+ @sut.reconnecting = true
93
+ @sut.settings[:reconnect_delay] = 15
94
+ @sut.settings[:reconnect_interval] = 5
95
+
96
+ flexmock(EM).should_receive(:reconnect).never
97
+ flexmock(EM).should_receive(:add_timer).with(5, Proc).once
98
+
99
+ @sut.reconnect()
100
+ end
101
+ end
102
+
103
+ end
104
+
105
+ end
@@ -0,0 +1,936 @@
1
+ #
2
+ # Copyright (c) 2009-2012 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
24
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib', 'right_amqp'))
25
+
26
+ class RequestMock; end
27
+ class ResultMock; end
28
+
29
+ describe RightAMQP::BrokerClient do
30
+
31
+ include FlexMock::ArgumentTypes
32
+ include RightAMQP::SpecHelper
33
+
34
+ before(:each) do
35
+ setup_logger
36
+ @message = "message"
37
+ @packet = flexmock("packet", :class => RequestMock, :to_s => true, :version => [12, 12]).by_default
38
+ @serializer = flexmock("serializer")
39
+ @serializer.should_receive(:dump).and_return(@message).by_default
40
+ @serializer.should_receive(:load).with(@message).and_return(@packet).by_default
41
+ @exceptions = flexmock("exceptions")
42
+ @exceptions.should_receive(:track).never.by_default
43
+ @connection = flexmock("connection")
44
+ @connection.should_receive(:connection_status).by_default
45
+ flexmock(AMQP).should_receive(:connect).and_return(@connection).by_default
46
+ @channel = flexmock("AMQP connection channel")
47
+ @channel.should_receive(:connection).and_return(@connection).by_default
48
+ @identity = "rs-broker-localhost-5672"
49
+ @address = {:host => "localhost", :port => 5672, :index => 0}
50
+ @options = {}
51
+ end
52
+
53
+ context "when initializing connection" do
54
+
55
+ before(:each) do
56
+ @amqp = flexmock(AMQP)
57
+ @amqp.should_receive(:connect).and_return(@connection).by_default
58
+ @channel.should_receive(:prefetch).never.by_default
59
+ flexmock(MQ).should_receive(:new).with(@connection).and_return(@channel).by_default
60
+ end
61
+
62
+ it "should create a broker with AMQP connection for specified address" do
63
+ @amqp.should_receive(:connect).with(hsh(:user => "user", :pass => "pass", :vhost => "vhost", :host => "localhost",
64
+ :port => 5672, :insist => true, :reconnect_interval => 10)).and_return(@connection).once
65
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, {:user => "user",
66
+ :pass => "pass", :vhost => "vhost", :insist => true,
67
+ :reconnect_interval => 10})
68
+ broker.host.should == "localhost"
69
+ broker.port.should == 5672
70
+ broker.index.should == 0
71
+ broker.queues.should == []
72
+ broker.summary.should == {:alias => "b0", :identity => @identity, :status => :connecting,
73
+ :disconnects => 0, :failures => 0, :retries => 0}
74
+ broker.usable?.should be_true
75
+ broker.connected?.should be_false
76
+ broker.failed?.should be_false
77
+ end
78
+
79
+ it "should update state from existing client for given broker" do
80
+ existing = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
81
+ existing.__send__(:update_status, :disconnected)
82
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options, existing)
83
+ broker.summary.should == {:alias => "b0", :identity => @identity, :status => :connecting,
84
+ :disconnects => 1, :failures => 0, :retries => 0}
85
+ end
86
+
87
+ it "should log an info message when it creates an AMQP connection" do
88
+ @logger.should_receive(:info).with(/Connecting to broker/).once
89
+ RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
90
+ end
91
+
92
+ it "should log an error and set status to :failed if it fails to create an AMQP connection" do
93
+ @exceptions.should_receive(:track).once
94
+ @connection.should_receive(:close).once
95
+ @logger.should_receive(:info).once
96
+ @logger.should_receive(:error).with(/Failed connecting/).once
97
+ flexmock(MQ).should_receive(:new).with(@connection).and_raise(Exception)
98
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
99
+ broker.summary.should == {:alias => "b0", :identity => @identity, :status => :failed,
100
+ :disconnects => 0, :failures => 1, :retries => 0}
101
+ end
102
+
103
+ it "should set initialize connection status callback" do
104
+ @connection.should_receive(:connection_status).once
105
+ RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
106
+ end
107
+
108
+ it "should set broker prefetch value if specified" do
109
+ @channel.should_receive(:prefetch).with(1).once
110
+ RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, {:prefetch => 1})
111
+ end
112
+
113
+ end # when initializing connection
114
+
115
+ context "when subscribing" do
116
+
117
+ before(:each) do
118
+ @info = flexmock("info", :ack => true).by_default
119
+ @serializer.should_receive(:load).with(@message).and_return(@packet).by_default
120
+ @direct = flexmock("direct")
121
+ @fanout = flexmock("fanout")
122
+ @bind = flexmock("bind")
123
+ @queue = flexmock("queue")
124
+ @queue.should_receive(:bind).and_return(@bind).by_default
125
+ @channel.should_receive(:queue).and_return(@queue).by_default
126
+ @channel.should_receive(:direct).and_return(@direct).by_default
127
+ @channel.should_receive(:fanout).and_return(@fanout).by_default
128
+ flexmock(MQ).should_receive(:new).with(@connection).and_return(@channel).by_default
129
+ end
130
+
131
+ it "should subscribe queue to exchange" do
132
+ @queue.should_receive(:bind).and_return(@bind).once
133
+ @bind.should_receive(:subscribe).once
134
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
135
+ broker.__send__(:update_status, :ready)
136
+ broker.subscribe({:name => "queue"}, {:type => :direct, :name => "exchange"}) {|_, _|}
137
+ end
138
+
139
+ it "should subscribe queue to second exchange if specified" do
140
+ @queue.should_receive(:bind).and_return(@bind).twice
141
+ @bind.should_receive(:subscribe).once
142
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
143
+ broker.__send__(:update_status, :ready)
144
+ options = {:exchange2 => {:type => :fanout, :name => "exchange2", :options => {:durable => true}}}
145
+ broker.subscribe({:name => "queue"}, {:type => :direct, :name => "exchange"}, options) {|_, _|}
146
+ end
147
+
148
+ it "should subscribe queue to exchange when still connecting" do
149
+ @bind.should_receive(:subscribe).once
150
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
151
+ broker.subscribe({:name => "queue"}, {:type => :direct, :name => "exchange"}) {|_, _|}
152
+ end
153
+
154
+ it "should subscribe queue to empty exchange if no exchange specified" do
155
+ @queue.should_receive(:subscribe).once
156
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
157
+ broker.__send__(:update_status, :ready)
158
+ broker.subscribe({:name => "queue"}) {|b, p| p.should == nil}
159
+ end
160
+
161
+ it "should store queues for future reference" do
162
+ @bind.should_receive(:subscribe).once
163
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
164
+ broker.subscribe({:name => "queue"}, {:type => :direct, :name => "exchange"})
165
+ broker.queues.should == [@queue]
166
+ end
167
+
168
+ it "should return true if subscribed successfully" do
169
+ @bind.should_receive(:subscribe).and_yield(@info, @message).once
170
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
171
+ result = broker.subscribe({:name => "queue"}, {:type => :direct, :name => "exchange"},
172
+ RequestMock => true) {|b, p| p.should == @packet}
173
+ result.should be_true
174
+ end
175
+
176
+ it "should return true if already subscribed and not try to resubscribe" do
177
+ @queue.should_receive(:name).and_return("queue").once
178
+ @bind.should_receive(:subscribe).and_yield(@info, @message).once
179
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
180
+ result = broker.subscribe({:name => "queue"}, {:type => :direct, :name => "exchange"},
181
+ RequestMock => true) {|b, p| p.should == @packet}
182
+ result.should be_true
183
+ result = broker.subscribe({:name => "queue"}, {:type => :direct, :name => "exchange"}) {|_, _|}
184
+ result.should be_true
185
+ end
186
+
187
+ it "should ack received message if requested" do
188
+ @info.should_receive(:ack).once
189
+ @bind.should_receive(:subscribe).and_yield(@info, @message).once
190
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
191
+ broker.__send__(:update_status, :ready)
192
+ result = broker.subscribe({:name => "queue"}, {:type => :direct, :name => "exchange"},
193
+ :ack => true, RequestMock => true) {|b, p| p.should == @packet}
194
+ result.should be_true
195
+ end
196
+
197
+ it "should return false if client not usable" do
198
+ @queue.should_receive(:bind).and_return(@bind).never
199
+ @bind.should_receive(:subscribe).never
200
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
201
+ broker.__send__(:update_status, :disconnected)
202
+ broker.subscribe({:name => "queue"}).should be_false
203
+ end
204
+
205
+ it "should receive message causing it to be unserialized and logged" do
206
+ @logger.should_receive(:info).with(/Connecting/).once
207
+ @logger.should_receive(:info).with(/Subscribing/).once
208
+ @logger.should_receive(:info).with(/RECV/).once
209
+ @serializer.should_receive(:load).with(@message).and_return(@packet).once
210
+ @bind.should_receive(:subscribe).and_yield(@info, @message).once
211
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
212
+ broker.__send__(:update_status, :ready)
213
+ broker.subscribe({:name => "queue"}, {:type => :direct, :name => "exchange"},
214
+ RequestMock => nil) {|b, p| p.class.should == RequestMock}
215
+ end
216
+
217
+ it "should receive message and log exception if subscribe block fails" do
218
+ @logger.should_receive(:info).with(/Connecting/).once
219
+ @logger.should_receive(:info).with(/Subscribing/).once
220
+ @logger.should_receive(:error).with(/Failed executing block/).once
221
+ @exceptions.should_receive(:track).once
222
+ @serializer.should_receive(:load).with(@message).and_return(@packet).once
223
+ @bind.should_receive(:subscribe).and_yield(@info, @message).once
224
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
225
+ broker.__send__(:update_status, :ready)
226
+ result = broker.subscribe({:name => "queue"}, {:type => :direct, :name => "exchange"},
227
+ RequestMock => nil) {|b, p| raise Exception}
228
+ result.should be_false
229
+ end
230
+
231
+ it "should ignore 'nil' message when using ack" do
232
+ @logger.should_receive(:level).and_return(:debug)
233
+ @logger.should_receive(:info).with(/Connecting/).once
234
+ @logger.should_receive(:info).with(/Subscribing/).once
235
+ @logger.should_receive(:debug).with(/nil message ignored/).once
236
+ @bind.should_receive(:subscribe).and_yield(@info, "nil").once
237
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
238
+ broker.__send__(:update_status, :ready)
239
+ called = 0
240
+ broker.subscribe({:name => "queue"}, {:type => :direct, :name => "exchange"}, :ack => true) { |b, m| called += 1 }
241
+ called.should == 0
242
+ end
243
+
244
+ it "should ignore 'nil' message when not using ack" do
245
+ @logger.should_receive(:level).and_return(:debug)
246
+ @logger.should_receive(:info).with(/Connecting/).once
247
+ @logger.should_receive(:info).with(/Subscribing/).once
248
+ @logger.should_receive(:debug).with(/nil message ignored/).once
249
+ @bind.should_receive(:subscribe).and_yield(@info, "nil").once
250
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
251
+ broker.__send__(:update_status, :ready)
252
+ called = 0
253
+ broker.subscribe({:name => "queue"}, {:type => :direct, :name => "exchange"}) { |b, m| called += 1 }
254
+ called.should == 0
255
+ end
256
+
257
+ it "should not unserialize the message if requested" do
258
+ @logger.should_receive(:info).with(/Connecting/).once
259
+ @logger.should_receive(:info).with(/Subscribing/).once
260
+ @logger.should_receive(:info).with(/^RECV/).never
261
+ @bind.should_receive(:subscribe).and_yield(@info, @message).once
262
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
263
+ broker.__send__(:update_status, :ready)
264
+ broker.subscribe({:name => "queue"}, {:type => :direct, :name => "exchange"}, :no_unserialize => true) do |b, m|
265
+ b.should == "rs-broker-localhost-5672"
266
+ m.should == @message
267
+ end
268
+ end
269
+
270
+ it "should pass header with message if callback requires it" do
271
+ @logger.should_receive(:info).with(/Connecting/).once
272
+ @logger.should_receive(:info).with(/Subscribing/).once
273
+ @logger.should_receive(:info).with(/^RECV/).never
274
+ @bind.should_receive(:subscribe).and_yield(@info, @message).once
275
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
276
+ broker.__send__(:update_status, :ready)
277
+ broker.subscribe({:name => "queue"}, {:type => :direct, :name => "exchange"}, :no_unserialize => true) do |b, m, h|
278
+ b.should == "rs-broker-localhost-5672"
279
+ m.should == @message
280
+ h.should == @info
281
+ end
282
+ end
283
+
284
+ it "should log an error if a subscribe fails" do
285
+ @logger.should_receive(:info).with(/Connecting/).once
286
+ @logger.should_receive(:info).with(/RECV/).never
287
+ @logger.should_receive(:error).with(/Failed subscribing/).once
288
+ @exceptions.should_receive(:track).once
289
+ @bind.should_receive(:subscribe).and_raise(Exception)
290
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
291
+ broker.__send__(:update_status, :ready)
292
+ result = broker.subscribe({:name => "queue"}, {:type => :direct, :name => "exchange"}) {|b, p|}
293
+ result.should be_false
294
+ end
295
+
296
+ end # when subscribing
297
+
298
+ context "when receiving" do
299
+
300
+ it "should unserialize the message, log it, and return it" do
301
+ @logger.should_receive(:info).with(/Connecting/).once
302
+ @logger.should_receive(:info).with(/^RECV/).once
303
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
304
+ broker.__send__(:receive, "queue", @message, RequestMock => nil).should == @packet
305
+ end
306
+
307
+ it "should log a warning if the message is not of the right type and return nil" do
308
+ @logger.should_receive(:warning).with(/Received invalid.*packet type/).once
309
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
310
+ broker.__send__(:receive, "queue", @message).should be_nil
311
+ end
312
+
313
+ it "should show the category in the warning message if specified" do
314
+ @logger.should_receive(:warning).with(/Received invalid xxxx packet type/).once
315
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
316
+ broker.__send__(:receive, "queue", @message, ResultMock => nil, :category => "xxxx")
317
+ end
318
+
319
+ it "should display broker alias in the log" do
320
+ @logger.should_receive(:info).with(/Connecting/).once
321
+ @logger.should_receive(:info).with(/^RECV b0 /).once
322
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
323
+ broker.__send__(:receive, "queue", @message, RequestMock => nil)
324
+ end
325
+
326
+ it "should filter the packet display for :info level" do
327
+ @logger.should_receive(:info).with(/Connecting/).once
328
+ @logger.should_receive(:info).with(/^RECV.*TO YOU/).once
329
+ @logger.should_receive(:debug).with(/^RECV.*TO YOU/).never
330
+ @packet.should_receive(:to_s).with([:to], :recv_version).and_return("TO YOU").once
331
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
332
+ broker.__send__(:receive, "queue", @message, RequestMock => [:to])
333
+ end
334
+
335
+ it "should not filter the packet display for :debug level" do
336
+ @logger.should_receive(:level).and_return(:debug)
337
+ @logger.should_receive(:info).with(/Connecting/).once
338
+ @logger.should_receive(:info).with(/^RECV.*ALL/).never
339
+ @logger.should_receive(:info).with(/^RECV.*ALL/).once
340
+ @packet.should_receive(:to_s).with(nil, :recv_version).and_return("ALL").once
341
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
342
+ broker.__send__(:receive, "queue", @message, RequestMock => [:to])
343
+ end
344
+
345
+ it "should display additional data in log" do
346
+ @logger.should_receive(:info).with(/Connecting/).once
347
+ @logger.should_receive(:info).with(/^RECV.*More data/).once
348
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
349
+ broker.__send__(:receive, "queue", @message, RequestMock => nil, :log_data => "More data")
350
+ end
351
+
352
+ it "should not log a message if requested not to" do
353
+ @logger.should_receive(:info).with(/Connecting/).once
354
+ @logger.should_receive(:info).with(/^RECV/).never
355
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
356
+ broker.__send__(:receive, "queue", @message, RequestMock => nil, :no_log => true)
357
+ end
358
+
359
+ it "should not log a message if requested not to unless debug level" do
360
+ @logger.should_receive(:level).and_return(:debug)
361
+ @logger.should_receive(:info).with(/Connecting/).once
362
+ @logger.should_receive(:info).with(/^RECV/).once
363
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
364
+ broker.__send__(:receive, "queue", @message, RequestMock => nil, :no_log => true)
365
+ end
366
+
367
+ it "should log an error if exception prevents normal logging and should then return nil" do
368
+ @logger.should_receive(:error).with(/Failed receiving from queue/).once
369
+ @serializer.should_receive(:load).with(@message).and_raise(Exception).once
370
+ @exceptions.should_receive(:track).once
371
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
372
+ broker.__send__(:receive, "queue", @message).should be_nil
373
+ end
374
+
375
+ it "should make callback when there is a receive failure" do
376
+ @logger.should_receive(:error).with(/Failed receiving from queue/).once
377
+ @serializer.should_receive(:load).with(@message).and_raise(Exception).once
378
+ @exceptions.should_receive(:track).once
379
+ called = 0
380
+ callback = lambda { |msg, e| called += 1 }
381
+ options = {:exception_on_receive_callback => callback}
382
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, options)
383
+ broker.__send__(:receive, "queue", @message).should be_nil
384
+ called.should == 1
385
+ end
386
+
387
+ it "should display RE-RECV if the message being received is a retry" do
388
+ @logger.should_receive(:info).with(/Connecting/).once
389
+ @logger.should_receive(:info).with(/^RE-RECV/).once
390
+ @packet.should_receive(:tries).and_return(["try1"]).once
391
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
392
+ broker.__send__(:receive, "queue", @message, RequestMock => nil).should == @packet
393
+ end
394
+
395
+ end # when receiving
396
+
397
+ context "when unsubscribing" do
398
+
399
+ before(:each) do
400
+ @direct = flexmock("direct")
401
+ @bind = flexmock("bind", :subscribe => true)
402
+ @queue = flexmock("queue", :bind => @bind, :name => "queue1")
403
+ @channel.should_receive(:queue).and_return(@queue).by_default
404
+ @channel.should_receive(:direct).and_return(@direct).by_default
405
+ flexmock(MQ).should_receive(:new).with(@connection).and_return(@channel).by_default
406
+ end
407
+
408
+ it "should unsubscribe a queue by name" do
409
+ @queue.should_receive(:unsubscribe).once
410
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
411
+ broker.subscribe({:name => "queue1"}, {:type => :direct, :name => "exchange"})
412
+ broker.unsubscribe(["queue1"])
413
+ end
414
+
415
+ it "should ignore unsubscribe if queue unknown" do
416
+ @queue.should_receive(:unsubscribe).never
417
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
418
+ broker.subscribe({:name => "queue1"}, {:type => :direct, :name => "exchange"})
419
+ broker.unsubscribe(["queue2"])
420
+ end
421
+
422
+ it "should activate block after unsubscribing if provided" do
423
+ @queue.should_receive(:unsubscribe).and_yield.once
424
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
425
+ broker.subscribe({:name => "queue1"}, {:type => :direct, :name => "exchange"})
426
+ called = 0
427
+ broker.unsubscribe(["queue1"]) { called += 1 }
428
+ called.should == 1
429
+ end
430
+
431
+ it "should ignore the request if client not usable" do
432
+ @queue.should_receive(:unsubscribe).and_yield.never
433
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
434
+ broker.subscribe({:name => "queue1"}, {:type => :direct, :name => "exchange"})
435
+ broker.__send__(:update_status, :disconnected)
436
+ broker.unsubscribe(["queue1"])
437
+ end
438
+
439
+ it "should log an error if unsubscribe raises an exception and activate block if provided" do
440
+ @logger.should_receive(:error).with(/Failed unsubscribing/).once
441
+ @queue.should_receive(:unsubscribe).and_raise(Exception).once
442
+ @exceptions.should_receive(:track).once
443
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
444
+ broker.subscribe({:name => "queue1"}, {:type => :direct, :name => "exchange"})
445
+ called = 0
446
+ broker.unsubscribe(["queue1"]) { called += 1 }
447
+ called.should == 1
448
+ end
449
+
450
+ end # when unsubscribing
451
+
452
+ context "when declaring" do
453
+
454
+ before(:each) do
455
+ flexmock(MQ).should_receive(:new).with(@connection).and_return(@channel).by_default
456
+ @channel.should_receive(:queues).and_return({}).by_default
457
+ @channel.should_receive(:exchanges).and_return({}).by_default
458
+ end
459
+
460
+ it "should declare exchange and return true" do
461
+ @channel.should_receive(:exchange).once
462
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
463
+ broker.declare(:exchange, "x", :durable => true).should be_true
464
+ end
465
+
466
+ it "should delete the exchange or queue from the AMQP cache before declaring" do
467
+ @channel.should_receive(:queue).once
468
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
469
+ flexmock(broker).should_receive(:delete_amqp_resources).with(:queue, "queue").once
470
+ broker.declare(:queue, "queue", :durable => true).should be_true
471
+ end
472
+
473
+ it "should log declaration" do
474
+ @logger.should_receive(:info).with(/Connecting/).once
475
+ @logger.should_receive(:info).with(/Declaring/).once
476
+ @channel.should_receive(:queue).once
477
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
478
+ broker.declare(:queue, "q").should be_true
479
+ end
480
+
481
+ it "should return false if client not usable" do
482
+ @channel.should_receive(:exchange).never
483
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
484
+ broker.__send__(:update_status, :disconnected)
485
+ broker.declare(:exchange, "x", :durable => true).should be_false
486
+
487
+ end
488
+
489
+ it "should log an error if the declare fails and return false" do
490
+ @logger.should_receive(:info).with(/Connecting/).once
491
+ @logger.should_receive(:info).with(/Declaring/).once
492
+ @logger.should_receive(:error).with(/Failed declaring/).once
493
+ @exceptions.should_receive(:track).once
494
+ @channel.should_receive(:queue).and_raise(Exception).once
495
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
496
+ broker.declare(:queue, "q").should be_false
497
+ end
498
+
499
+ end # when declaring
500
+
501
+ context "when publishing" do
502
+
503
+ before(:each) do
504
+ @direct = flexmock("direct")
505
+ flexmock(MQ).should_receive(:new).with(@connection).and_return(@channel).by_default
506
+ end
507
+
508
+ it "should serialize message, publish it, and return true" do
509
+ @channel.should_receive(:direct).with("exchange", :durable => true).and_return(@direct).once
510
+ @direct.should_receive(:publish).with(@message, :persistent => true).once
511
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
512
+ broker.__send__(:update_status, :ready)
513
+ broker.publish({:type => :direct, :name => "exchange", :options => {:durable => true}},
514
+ @packet, @message, :persistent => true).should be_true
515
+ end
516
+
517
+ it "should delete the exchange or queue from the AMQP cache if :declare specified" do
518
+ @channel.should_receive(:direct).with("exchange", {:declare => true}).and_return(@direct)
519
+ @direct.should_receive(:publish).with(@message, {})
520
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
521
+ broker.__send__(:update_status, :ready)
522
+ exchange = {:type => :direct, :name => "exchange", :options => {:declare => true}}
523
+ flexmock(broker).should_receive(:delete_amqp_resources).with(:direct, "exchange").once
524
+ broker.publish(exchange, @packet, @message).should be_true
525
+ end
526
+
527
+ it "should return false if client not connected" do
528
+ @channel.should_receive(:direct).never
529
+ @direct.should_receive(:publish).with(@message, :persistent => true).never
530
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
531
+ broker.publish({:type => :direct, :name => "exchange", :options => {:durable => true}},
532
+ @packet, @message, :persistent => true).should be_false
533
+ end
534
+
535
+ it "should log an error if the publish fails" do
536
+ @logger.should_receive(:error).with(/Failed publishing/).once
537
+ @exceptions.should_receive(:track).once
538
+ @channel.should_receive(:direct).and_raise(Exception)
539
+ @direct.should_receive(:publish).with(@message, {}).never
540
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
541
+ broker.__send__(:update_status, :ready)
542
+ broker.publish({:type => :direct, :name => "exchange"}, @packet, @message).should be_false
543
+ end
544
+
545
+ it "should log that message is being sent with info about which broker used" do
546
+ @logger.should_receive(:info).with(/Connecting/).once
547
+ @logger.should_receive(:info).with(/^SEND b0/).once
548
+ @channel.should_receive(:direct).with("exchange", {}).and_return(@direct).once
549
+ @direct.should_receive(:publish).with(@message, {}).once
550
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
551
+ broker.__send__(:update_status, :ready)
552
+ broker.publish({:type => :direct, :name => "exchange"}, @packet, @message).should be_true
553
+ end
554
+
555
+ it "should log broker choices for :debug level" do
556
+ @logger.should_receive(:level).and_return(:debug)
557
+ @logger.should_receive(:info).with(/Connecting/).once
558
+ @logger.should_receive(:info).with(/^SEND b0.*publish options/).once
559
+ @channel.should_receive(:direct).with("exchange", {}).and_return(@direct).once
560
+ @direct.should_receive(:publish).with(@message, {}).once
561
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
562
+ broker.__send__(:update_status, :ready)
563
+ broker.publish({:type => :direct, :name => "exchange"}, @packet, @message).should be_true
564
+ end
565
+
566
+ it "should not log a message if requested not to" do
567
+ @logger.should_receive(:info).with(/Connecting/).once
568
+ @logger.should_receive(:info).with(/^SEND/).never
569
+ @channel.should_receive(:direct).with("exchange", {}).and_return(@direct).once
570
+ @direct.should_receive(:publish).with(@message, :no_log => true).once
571
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
572
+ broker.__send__(:update_status, :ready)
573
+ broker.publish({:type => :direct, :name => "exchange"}, @packet, @message, :no_log => true).should be_true
574
+ end
575
+
576
+ it "should not log a message if requested not to unless debug level" do
577
+ @logger.should_receive(:level).and_return(:debug)
578
+ @logger.should_receive(:info).with(/Connecting/).once
579
+ @logger.should_receive(:info).with(/^SEND/).once
580
+ @channel.should_receive(:direct).with("exchange", {}).and_return(@direct).once
581
+ @direct.should_receive(:publish).with(@message, :no_log => true).once
582
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
583
+ broker.__send__(:update_status, :ready)
584
+ broker.publish({:type => :direct, :name => "exchange"}, @packet, @message, :no_log => true).should be_true
585
+ end
586
+
587
+ it "should display broker alias in the log" do
588
+ @logger.should_receive(:info).with(/Connecting/).once
589
+ @logger.should_receive(:info).with(/^SEND b0 /).once
590
+ @channel.should_receive(:direct).with("exchange", {}).and_return(@direct).once
591
+ @direct.should_receive(:publish).with(@message, {}).once
592
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
593
+ broker.__send__(:update_status, :ready)
594
+ broker.publish({:type => :direct, :name => "exchange"}, @packet, @message).should be_true
595
+ end
596
+
597
+ it "should filter the packet display for :info level" do
598
+ @logger.should_receive(:info).with(/Connecting/).once
599
+ @logger.should_receive(:info).with(/^SEND.*TO YOU/).once
600
+ @logger.should_receive(:info).with(/^SEND.*TO YOU/).never
601
+ @packet.should_receive(:to_s).with([:to], :send_version).and_return("TO YOU").once
602
+ @channel.should_receive(:direct).with("exchange", {}).and_return(@direct).once
603
+ @direct.should_receive(:publish).with(@message, :log_filter => [:to]).once
604
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
605
+ broker.__send__(:update_status, :ready)
606
+ broker.publish({:type => :direct, :name => "exchange"}, @packet, @message, :log_filter => [:to]).should be_true
607
+ end
608
+
609
+ it "should not filter the packet display for :debug level" do
610
+ @logger.should_receive(:level).and_return(:debug)
611
+ @logger.should_receive(:info).with(/Connecting/).once
612
+ @logger.should_receive(:info).with(/^SEND.*ALL/).never
613
+ @logger.should_receive(:info).with(/^SEND.*ALL/).once
614
+ @packet.should_receive(:to_s).with(nil, :send_version).and_return("ALL").once
615
+ @channel.should_receive(:direct).with("exchange", {}).and_return(@direct).once
616
+ @direct.should_receive(:publish).with(@message, :log_filter => [:to]).once
617
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
618
+ broker.__send__(:update_status, :ready)
619
+ broker.publish({:type => :direct, :name => "exchange"}, @packet, @message, :log_filter => [:to]).should be_true
620
+ end
621
+
622
+ it "should display additional data in log" do
623
+ @logger.should_receive(:info).with(/Connecting/).once
624
+ @logger.should_receive(:info).with(/^SEND.*More data/).once
625
+ @channel.should_receive(:direct).with("exchange", {}).and_return(@direct).once
626
+ @direct.should_receive(:publish).with(@message, :log_data => "More data").once
627
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
628
+ broker.__send__(:update_status, :ready)
629
+ broker.publish({:type => :direct, :name => "exchange"}, @packet, @message, :log_data => "More data").should be_true
630
+ end
631
+
632
+ it "should display RE-SEND if the message being sent is a retry" do
633
+ @logger.should_receive(:info).with(/Connecting/).once
634
+ @logger.should_receive(:info).with(/^RE-SEND/).once
635
+ @packet.should_receive(:tries).and_return(["try1"]).once
636
+ @channel.should_receive(:direct).with("exchange", {}).and_return(@direct).once
637
+ @direct.should_receive(:publish).with(@message, {}).once
638
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
639
+ broker.__send__(:update_status, :ready)
640
+ broker.publish({:type => :direct, :name => "exchange"}, @packet, @message).should be_true
641
+ end
642
+
643
+ end # when publishing
644
+
645
+ context "when returning" do
646
+
647
+ class MQ
648
+ attr_accessor :connection, :on_return_message
649
+
650
+ def initialize(connection)
651
+ @connection = connection
652
+ end
653
+
654
+ def return_message(&blk)
655
+ @on_return_message = blk
656
+ end
657
+ end
658
+
659
+ before(:each) do
660
+ @info = flexmock("info", :reply_text => "NO_CONSUMERS", :exchange => "exchange", :routing_key => "routing_key").by_default
661
+ end
662
+
663
+ it "should invoke block and log the return" do
664
+ @logger.should_receive(:info).with(/Connecting to broker/).once
665
+ @logger.should_receive(:debug).with(/RETURN b0/).once
666
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
667
+ called = 0
668
+ broker.return_message do |to, reason, message|
669
+ called += 1
670
+ to.should == "exchange"
671
+ reason.should == "NO_CONSUMERS"
672
+ message.should == @message
673
+ end
674
+ broker.instance_variable_get(:@channel).on_return_message.call(@info, @message)
675
+ called.should == 1
676
+ end
677
+
678
+ it "should invoke block with routing key if exchange is empty" do
679
+ @logger.should_receive(:debug)
680
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
681
+ called = 0
682
+ broker.return_message do |to, reason, message|
683
+ called += 1
684
+ to.should == "routing_key"
685
+ reason.should == "NO_CONSUMERS"
686
+ message.should == @message
687
+ end
688
+ @info.should_receive(:exchange).and_return("")
689
+ broker.instance_variable_get(:@channel).on_return_message.call(@info, @message)
690
+ called.should == 1
691
+ end
692
+
693
+ it "should log an error if there is a failure while processing the return" do
694
+ @logger.should_receive(:error).with(/Failed return/).once
695
+ @logger.should_receive(:debug)
696
+ @exceptions.should_receive(:track).once
697
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
698
+ called = 0
699
+ broker.return_message do |to, reason, message|
700
+ called += 1
701
+ raise Exception
702
+ end
703
+ broker.instance_variable_get(:@channel).on_return_message.call(@info, @message)
704
+ called.should == 1
705
+ end
706
+
707
+ end # when returning
708
+
709
+ context "when deleting" do
710
+
711
+ before(:each) do
712
+ @direct = flexmock("direct")
713
+ @bind = flexmock("bind", :subscribe => true)
714
+ @queue = flexmock("queue", :bind => @bind, :name => "queue1")
715
+ @channel.should_receive(:queue).and_return(@queue).by_default
716
+ @channel.should_receive(:direct).and_return(@direct).by_default
717
+ flexmock(MQ).should_receive(:new).with(@connection).and_return(@channel).by_default
718
+ end
719
+
720
+ it "should delete the named queue and return true" do
721
+ @queue.should_receive(:delete).once
722
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
723
+ broker.subscribe({:name => "queue1"}, {:type => :direct, :name => "exchange"})
724
+ broker.queues.should == [@queue]
725
+ broker.delete("queue1").should be_true
726
+ broker.queues.should == []
727
+ end
728
+
729
+ it "should return false if the client is not usable" do
730
+ @queue.should_receive(:delete).never
731
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
732
+ broker.subscribe({:name => "queue1"}, {:type => :direct, :name => "exchange"})
733
+ broker.queues.should == [@queue]
734
+ broker.__send__(:update_status, :disconnected)
735
+ broker.delete("queue1").should be_false
736
+ broker.queues.should == [@queue]
737
+ end
738
+
739
+ it "should log an error and return false if the delete fails" do
740
+ @logger.should_receive(:error).with(/Failed deleting queue/).once
741
+ @exceptions.should_receive(:track).once
742
+ @queue.should_receive(:delete).and_raise(Exception)
743
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
744
+ broker.subscribe({:name => "queue1"}, {:type => :direct, :name => "exchange"})
745
+ broker.queues.should == [@queue]
746
+ broker.delete("queue1").should be_false
747
+ end
748
+
749
+ end # when deleteing
750
+
751
+ context "when monitoring" do
752
+
753
+ before(:each) do
754
+ flexmock(MQ).should_receive(:new).with(@connection).and_return(@channel).by_default
755
+ end
756
+
757
+ it "should distinguish whether the client is usable based on whether connecting or connected" do
758
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
759
+ broker.usable?.should be_true
760
+ broker.__send__(:update_status, :ready)
761
+ broker.usable?.should be_true
762
+ broker.__send__(:update_status, :disconnected)
763
+ broker.usable?.should be_false
764
+ @logger.should_receive(:error).with(/Failed to connect to broker b0/).once
765
+ broker.__send__(:update_status, :failed)
766
+ broker.usable?.should be_false
767
+ end
768
+
769
+ it "should distinguish whether the client is connected" do
770
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
771
+ broker.connected?.should be_false
772
+ broker.__send__(:update_status, :ready)
773
+ broker.connected?.should be_true
774
+ broker.__send__(:update_status, :disconnected)
775
+ broker.connected?.should be_false
776
+ @logger.should_receive(:error).with(/Failed to connect to broker b0/).once
777
+ broker.__send__(:update_status, :failed)
778
+ broker.connected?.should be_false
779
+ end
780
+
781
+ it "should distinguish whether the client has failed" do
782
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
783
+ broker.failed?.should be_false
784
+ broker.__send__(:update_status, :ready)
785
+ broker.failed?.should be_false
786
+ broker.__send__(:update_status, :disconnected)
787
+ broker.failed?.should be_false
788
+ @logger.should_receive(:error).with(/Failed to connect to broker b0/).once
789
+ broker.__send__(:update_status, :failed)
790
+ broker.failed?.should be_true
791
+ end
792
+
793
+ it "should give broker summary" do
794
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
795
+ broker.summary.should == {:alias => "b0", :identity => @identity, :status => :connecting,
796
+ :disconnects => 0, :failures => 0, :retries => 0}
797
+ broker.__send__(:update_status, :ready)
798
+ broker.summary.should == {:alias => "b0", :identity => @identity, :status => :connected,
799
+ :disconnects => 0, :failures => 0, :retries => 0}
800
+ @logger.should_receive(:error).with(/Failed to connect to broker/).once
801
+ broker.__send__(:update_status, :failed)
802
+ broker.summary.should == {:alias => "b0", :identity => @identity, :status => :failed,
803
+ :disconnects => 0, :failures => 1, :retries => 0}
804
+ end
805
+
806
+ it "should give broker statistics" do
807
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
808
+ broker.stats.should == {"alias" => "b0", "identity" => "rs-broker-localhost-5672",
809
+ "status" => "connecting", "disconnects" => nil, "disconnect last" => nil,
810
+ "failures" => nil, "failure last" => nil, "retries" => nil}
811
+ broker.__send__(:update_status, :ready)
812
+ broker.stats.should == {"alias" => "b0", "identity" => "rs-broker-localhost-5672",
813
+ "status" => "connected", "disconnects" => nil, "disconnect last" => nil,
814
+ "failures" => nil, "failure last" => nil, "retries" => nil}
815
+ @logger.should_receive(:error).with(/Failed to connect to broker/).once
816
+ broker.__send__(:update_status, :failed)
817
+ broker.stats.should == {"alias" => "b0", "identity" => "rs-broker-localhost-5672",
818
+ "status" => "failed", "disconnects" => nil, "disconnect last" => nil,
819
+ "failures" => 1, "failure last" => {"elapsed" => 0}, "retries" => nil}
820
+ end
821
+
822
+ it "should make update status callback when status changes" do
823
+ broker = nil
824
+ called = 0
825
+ connected_before = false
826
+ callback = lambda { |b, c| called += 1; b.should == broker; c.should == connected_before }
827
+ options = {:update_status_callback => callback}
828
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, options)
829
+ broker.__send__(:update_status, :ready)
830
+ broker.last_failed.should be_false
831
+ called.should == 1
832
+ connected_before = true
833
+ broker.__send__(:update_status, :disconnected)
834
+ broker.last_failed.should be_false
835
+ broker.disconnects.total.should == 1
836
+ called.should == 2
837
+ broker.__send__(:update_status, :disconnected)
838
+ broker.disconnects.total.should == 1
839
+ called.should == 2
840
+ @logger.should_receive(:error).with(/Failed to connect to broker b0/).once
841
+ connected_before = false
842
+ broker.__send__(:update_status, :failed)
843
+ broker.last_failed.should be_true
844
+ called.should == 3
845
+ end
846
+
847
+ end # when monitoring
848
+
849
+ context "when closing" do
850
+
851
+ before(:each) do
852
+ flexmock(MQ).should_receive(:new).with(@connection).and_return(@channel).by_default
853
+ end
854
+
855
+ it "should close broker connection and send status update" do
856
+ @connection.should_receive(:close).and_yield.once
857
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
858
+ flexmock(broker).should_receive(:update_status).once
859
+ broker.close
860
+ broker.status.should == :closed
861
+ end
862
+
863
+ it "should not propagate status update if requested not to" do
864
+ @connection.should_receive(:close).and_yield.once
865
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
866
+ flexmock(broker).should_receive(:update_status).never
867
+ broker.close(propagate = false)
868
+ end
869
+
870
+ it "should set status to :failed if not a normal close" do
871
+ @connection.should_receive(:close).and_yield.once
872
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
873
+ broker.close(propagate = false, normal = false)
874
+ broker.status.should == :failed
875
+ end
876
+
877
+ it "should log that closing connection" do
878
+ @logger.should_receive(:info).with(/Connecting/).once
879
+ @logger.should_receive(:info).with(/Closed connection to broker b0/).once
880
+ @connection.should_receive(:close).and_yield.once
881
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
882
+ broker.close
883
+ end
884
+
885
+ it "should not log if requested not to" do
886
+ @logger.should_receive(:info).with(/Connecting/).once
887
+ @logger.should_receive(:info).with(/Closed connection to broker b0/).never
888
+ @connection.should_receive(:close).and_yield.once
889
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
890
+ broker.close(propagate = true, normal = true, log = false)
891
+ end
892
+
893
+ it "should close broker connection and execute block if supplied" do
894
+ @connection.should_receive(:close).and_yield.once
895
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
896
+ called = 0
897
+ broker.close { called += 1; broker.status.should == :closed }
898
+ called.should == 1
899
+ end
900
+
901
+ it "should close broker connection when no block supplied" do
902
+ @connection.should_receive(:close).and_yield.once
903
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
904
+ broker.close
905
+ end
906
+
907
+ it "should not propagate status update if already closed" do
908
+ @connection.should_receive(:close).never
909
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
910
+ broker.__send__(:update_status, :closed)
911
+ flexmock(broker).should_receive(:update_status).never
912
+ broker.close
913
+ end
914
+
915
+ it "should change failed status to closed" do
916
+ @logger.should_receive(:error).with(/Failed to connect to broker/).once
917
+ @connection.should_receive(:close).never
918
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
919
+ broker.__send__(:update_status, :failed)
920
+ flexmock(broker).should_receive(:update_status).never
921
+ broker.close
922
+ broker.status.should == :closed
923
+ end
924
+
925
+ it "should log an error if closing connection fails but still set status to :closed" do
926
+ @logger.should_receive(:error).with(/Failed to close broker b0/).once
927
+ @exceptions.should_receive(:track).once
928
+ @connection.should_receive(:close).and_raise(Exception)
929
+ broker = RightAMQP::BrokerClient.new(@identity, @address, @serializer, @exceptions, @options)
930
+ broker.close
931
+ broker.status.should == :closed
932
+ end
933
+
934
+ end # when closing
935
+
936
+ end # RightAMQP::BrokerClient