right_amqp 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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