beetle 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.gitignore +5 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +82 -0
  4. data/Rakefile +114 -0
  5. data/TODO +7 -0
  6. data/beetle.gemspec +127 -0
  7. data/etc/redis-master.conf +189 -0
  8. data/etc/redis-slave.conf +189 -0
  9. data/examples/README.rdoc +14 -0
  10. data/examples/attempts.rb +66 -0
  11. data/examples/handler_class.rb +64 -0
  12. data/examples/handling_exceptions.rb +73 -0
  13. data/examples/multiple_exchanges.rb +48 -0
  14. data/examples/multiple_queues.rb +43 -0
  15. data/examples/redis_failover.rb +65 -0
  16. data/examples/redundant.rb +65 -0
  17. data/examples/rpc.rb +45 -0
  18. data/examples/simple.rb +39 -0
  19. data/lib/beetle.rb +57 -0
  20. data/lib/beetle/base.rb +78 -0
  21. data/lib/beetle/client.rb +252 -0
  22. data/lib/beetle/configuration.rb +31 -0
  23. data/lib/beetle/deduplication_store.rb +152 -0
  24. data/lib/beetle/handler.rb +95 -0
  25. data/lib/beetle/message.rb +336 -0
  26. data/lib/beetle/publisher.rb +187 -0
  27. data/lib/beetle/r_c.rb +40 -0
  28. data/lib/beetle/subscriber.rb +144 -0
  29. data/script/start_rabbit +29 -0
  30. data/snafu.rb +55 -0
  31. data/test/beetle.yml +81 -0
  32. data/test/beetle/base_test.rb +52 -0
  33. data/test/beetle/bla.rb +0 -0
  34. data/test/beetle/client_test.rb +305 -0
  35. data/test/beetle/configuration_test.rb +5 -0
  36. data/test/beetle/deduplication_store_test.rb +90 -0
  37. data/test/beetle/handler_test.rb +105 -0
  38. data/test/beetle/message_test.rb +744 -0
  39. data/test/beetle/publisher_test.rb +407 -0
  40. data/test/beetle/r_c_test.rb +9 -0
  41. data/test/beetle/subscriber_test.rb +263 -0
  42. data/test/beetle_test.rb +5 -0
  43. data/test/test_helper.rb +20 -0
  44. data/tmp/master/.gitignore +2 -0
  45. data/tmp/slave/.gitignore +3 -0
  46. metadata +192 -0
@@ -0,0 +1,407 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+
4
+ module Beetle
5
+ class PublisherTest < Test::Unit::TestCase
6
+ def setup
7
+ client = Client.new
8
+ @pub = Publisher.new(client)
9
+ end
10
+
11
+ test "acccessing a bunny for a server which doesn't have one should create it and associate it with the server" do
12
+ @pub.expects(:new_bunny).returns(42)
13
+ assert_equal 42, @pub.send(:bunny)
14
+ bunnies = @pub.instance_variable_get("@bunnies")
15
+ assert_equal(42, bunnies[@pub.server])
16
+ end
17
+
18
+ test "new bunnies should be created using current host and port and they should be started" do
19
+ m = mock("dummy")
20
+ expected_bunny_options = {
21
+ :host => @pub.send(:current_host), :port => @pub.send(:current_port),
22
+ :logging => false, :user => "guest", :pass => "guest", :vhost => "/"
23
+ }
24
+ Bunny.expects(:new).with(expected_bunny_options).returns(m)
25
+ m.expects(:start)
26
+ assert_equal m, @pub.send(:new_bunny)
27
+ end
28
+
29
+ test "initially there should be no bunnies" do
30
+ assert_equal({}, @pub.instance_variable_get("@bunnies"))
31
+ end
32
+
33
+ test "initially there should be no dead servers" do
34
+ assert_equal({}, @pub.instance_variable_get("@dead_servers"))
35
+ end
36
+
37
+ test "stop! should shut down bunny and clean internal data structures" do
38
+ b = mock("bunny")
39
+ b.expects(:stop).raises(Exception.new)
40
+ @pub.expects(:bunny).returns(b)
41
+ @pub.send(:stop!)
42
+ assert_equal({}, @pub.send(:exchanges))
43
+ assert_equal({}, @pub.send(:queues))
44
+ assert_nil @pub.instance_variable_get(:@bunnies)[@pub.server]
45
+ end
46
+
47
+ end
48
+
49
+ class PublisherPublishingTest < Test::Unit::TestCase
50
+ def setup
51
+ @client = Client.new
52
+ @pub = Publisher.new(@client)
53
+ @pub.stubs(:bind_queues_for_exchange)
54
+ @client.register_queue("mama", :exchange => "mama-exchange")
55
+ @client.register_message("mama", :ttl => 1.hour, :exchange => "mama-exchange")
56
+ @opts = { :ttl => 1.hour , :key => "mama", :persistent => true}
57
+ @data = 'XXX'
58
+ end
59
+
60
+ test "failover publishing should try to recycle dead servers before trying to publish the message" do
61
+ @pub.servers << "localhost:3333"
62
+ @pub.send(:mark_server_dead)
63
+ publishing = sequence('publishing')
64
+ @pub.expects(:recycle_dead_servers).in_sequence(publishing)
65
+ @pub.expects(:publish_with_failover).with("mama-exchange", "mama", @data, @opts).in_sequence(publishing)
66
+ @pub.publish("mama", @data)
67
+ end
68
+
69
+ test "redundant publishing should try to recycle dead servers before trying to publish the message" do
70
+ @pub.servers << "localhost:3333"
71
+ @pub.send(:mark_server_dead)
72
+ publishing = sequence('publishing')
73
+ @pub.expects(:recycle_dead_servers).in_sequence(publishing)
74
+ @pub.expects(:publish_with_redundancy).with("mama-exchange", "mama", @data, @opts.merge(:redundant => true)).in_sequence(publishing)
75
+ @pub.publish("mama", @data, :redundant => true)
76
+ end
77
+
78
+ test "publishing should fail over to the next server" do
79
+ failover = sequence('failover')
80
+ @pub.expects(:select_next_server).in_sequence(failover)
81
+ e = mock("exchange")
82
+ @pub.expects(:exchange).with("mama-exchange").returns(e).in_sequence(failover)
83
+ e.expects(:publish).raises(Bunny::ConnectionError).in_sequence(failover)
84
+ @pub.expects(:stop!).in_sequence(failover)
85
+ @pub.expects(:mark_server_dead).in_sequence(failover)
86
+ @pub.publish_with_failover("mama-exchange", "mama", @data, @opts)
87
+ end
88
+
89
+ test "redundant publishing should send the message to two servers" do
90
+ redundant = sequence("redundant")
91
+ @pub.servers = ["someserver", "someotherserver"]
92
+ @pub.server = "someserver"
93
+
94
+ e = mock("exchange")
95
+ @pub.expects(:exchange).with("mama-exchange").returns(e).in_sequence(redundant)
96
+ e.expects(:publish).in_sequence(redundant)
97
+ @pub.expects(:exchange).with("mama-exchange").returns(e).in_sequence(redundant)
98
+ e.expects(:publish).in_sequence(redundant)
99
+
100
+ assert_equal 2, @pub.publish_with_redundancy("mama-exchange", "mama", @data, @opts)
101
+ end
102
+
103
+ test "redundant publishing should return 1 if the message was published to one server only" do
104
+ redundant = sequence("redundant")
105
+ @pub.servers = ["someserver", "someotherserver"]
106
+ @pub.server = "someserver"
107
+
108
+ e = mock("exchange")
109
+ @pub.expects(:exchange).with("mama-exchange").returns(e).in_sequence(redundant)
110
+ e.expects(:publish).raises(Bunny::ConnectionError).in_sequence(redundant)
111
+ @pub.expects(:exchange).with("mama-exchange").returns(e).in_sequence(redundant)
112
+ e.expects(:publish).in_sequence(redundant)
113
+
114
+ assert_equal 1, @pub.publish_with_redundancy("mama-exchange", "mama", @data, @opts)
115
+ end
116
+
117
+ test "redundant publishing should return 0 if the message was published to no server" do
118
+ redundant = sequence("redundant")
119
+ @pub.servers = ["someserver", "someotherserver"]
120
+ @pub.server = "someserver"
121
+
122
+ e = mock("exchange")
123
+ @pub.expects(:exchange).with("mama-exchange").returns(e).in_sequence(redundant)
124
+ e.expects(:publish).raises(Bunny::ConnectionError).in_sequence(redundant)
125
+ @pub.expects(:exchange).with("mama-exchange").returns(e).in_sequence(redundant)
126
+ e.expects(:publish).raises(Bunny::ConnectionError).in_sequence(redundant)
127
+
128
+ assert_equal 0, @pub.publish_with_redundancy("mama-exchange", "mama", @data, @opts)
129
+ end
130
+
131
+ test "redundant publishing should fallback to failover publishing if less than one server is available" do
132
+ @pub.server = ["a server"]
133
+ @pub.expects(:publish_with_failover).with("mama-exchange", "mama", @data, @opts).returns(1)
134
+ assert_equal 1, @pub.publish_with_redundancy("mama-exchange", "mama", @data, @opts)
135
+ end
136
+
137
+ test "redundant publishing should publish to two of three servers if one server is dead" do
138
+ @pub.servers = %w(server1 server2 server3)
139
+ @pub.server = "server1"
140
+ redundant = sequence("redundant")
141
+
142
+ e = mock("exchange")
143
+
144
+ @pub.expects(:exchange).returns(e).in_sequence(redundant)
145
+ e.expects(:publish).in_sequence(redundant)
146
+
147
+ @pub.expects(:exchange).returns(e).in_sequence(redundant)
148
+ e.expects(:publish).raises(Bunny::ConnectionError).in_sequence(redundant)
149
+ @pub.expects(:stop!).in_sequence(redundant)
150
+
151
+ @pub.expects(:exchange).returns(e).in_sequence(redundant)
152
+ e.expects(:publish).in_sequence(redundant)
153
+
154
+ assert_equal 2, @pub.publish_with_redundancy("mama-exchange", "mama", @data, @opts)
155
+ end
156
+
157
+ test "publishing should use the message ttl passed in the options hash to encode the message body" do
158
+ opts = {:ttl => 1.day}
159
+ Message.expects(:publishing_options).with(:ttl => 1.day).returns({})
160
+ @pub.expects(:select_next_server)
161
+ e = mock("exchange")
162
+ @pub.expects(:exchange).returns(e)
163
+ e.expects(:publish)
164
+ assert_equal 1, @pub.publish_with_failover("mama-exchange", "mama", @data, opts)
165
+ end
166
+
167
+ test "publishing with redundancy should use the message ttl passed in the options hash to encode the message body" do
168
+ opts = {:ttl => 1.day}
169
+ Message.expects(:publishing_options).with(:ttl => 1.day).returns({})
170
+ @pub.expects(:select_next_server)
171
+ e = mock("exchange")
172
+ @pub.expects(:exchange).returns(e)
173
+ e.expects(:publish)
174
+ assert_equal 1, @pub.publish_with_redundancy("mama-exchange", "mama", @data, opts)
175
+ end
176
+
177
+ test "publishing should use the message ttl from the message configuration if no ttl is passed in via the options hash" do
178
+ @pub.expects(:publish_with_failover).with("mama-exchange", "mama", @data, @opts).returns(1)
179
+ assert_equal 1, @pub.publish("mama", @data)
180
+ end
181
+ end
182
+
183
+ class PublisherQueueManagementTest < Test::Unit::TestCase
184
+ def setup
185
+ @client = Client.new
186
+ @pub = Publisher.new(@client)
187
+ end
188
+
189
+ test "initially there should be no queues for the current server" do
190
+ assert_equal({}, @pub.send(:queues))
191
+ assert !@pub.send(:queues)["some_queue"]
192
+ end
193
+
194
+ test "binding a queue should create it using the config and bind it to the exchange with the name specified" do
195
+ @client.register_queue("some_queue", :exchange => "some_exchange", :key => "haha.#")
196
+ @pub.expects(:exchange).with("some_exchange").returns(:the_exchange)
197
+ q = mock("queue")
198
+ q.expects(:bind).with(:the_exchange, {:key => "haha.#"})
199
+ m = mock("Bunny")
200
+ m.expects(:queue).with("some_queue", :durable => true, :passive => false, :auto_delete => false, :exclusive => false).returns(q)
201
+ @pub.expects(:bunny).returns(m)
202
+
203
+ @pub.send(:queue, "some_queue")
204
+ assert_equal q, @pub.send(:queues)["some_queue"]
205
+ end
206
+
207
+ test "should bind the defined queues for the used exchanges when publishing" do
208
+ @client.register_queue('test_queue_1', :exchange => 'test_exchange')
209
+ @client.register_queue('test_queue_2', :exchange => 'test_exchange')
210
+ @client.register_queue('test_queue_3', :exchange => 'test_exchange_2')
211
+ @pub.expects(:bind_queue!).returns(1).times(3)
212
+ @pub.send(:bind_queues_for_exchange, 'test_exchange')
213
+ @pub.send(:bind_queues_for_exchange, 'test_exchange_2')
214
+ end
215
+
216
+ test "should not rebind the defined queues for the used exchanges if they already have been bound" do
217
+ @client.register_queue('test_queue_1', :exchange => 'test_exchange')
218
+ @client.register_queue('test_queue_2', :exchange => 'test_exchange')
219
+ @pub.expects(:bind_queue!).twice
220
+ @pub.send(:bind_queues_for_exchange, 'test_exchange')
221
+ @pub.send(:bind_queues_for_exchange, 'test_exchange')
222
+ end
223
+
224
+ test "call the queue binding method when publishing" do
225
+ data = "XXX"
226
+ opts = {}
227
+ @client.register_queue("mama", :exchange => "mama-exchange")
228
+ @client.register_message("mama", :ttl => 1.hour, :exchange => "mama-exchange")
229
+ e = stub('exchange', 'publish')
230
+ @pub.expects(:exchange).with('mama-exchange').returns(e)
231
+ @pub.expects(:bind_queues_for_exchange).with('mama-exchange').returns(true)
232
+ @pub.publish('mama', data)
233
+ end
234
+
235
+ test "purging a queue should purge the queues on all servers" do
236
+ @pub.servers = %w(a b)
237
+ queue = mock("queue")
238
+ s = sequence("purging")
239
+ @pub.expects(:set_current_server).with("a").in_sequence(s)
240
+ @pub.expects(:queue).with("queue").returns(queue).in_sequence(s)
241
+ queue.expects(:purge).in_sequence(s)
242
+ @pub.expects(:set_current_server).with("b").in_sequence(s)
243
+ @pub.expects(:queue).with("queue").returns(queue).in_sequence(s)
244
+ queue.expects(:purge).in_sequence(s)
245
+ @pub.purge("queue")
246
+ end
247
+ end
248
+
249
+ class PublisherExchangeManagementTest < Test::Unit::TestCase
250
+ def setup
251
+ @client = Client.new
252
+ @pub = Publisher.new(@client)
253
+ end
254
+
255
+ test "initially there should be no exchanges for the current server" do
256
+ assert_equal({}, @pub.send(:exchanges))
257
+ end
258
+
259
+ test "accessing a given exchange should create it using the config. further access should return the created exchange" do
260
+ m = mock("Bunny")
261
+ m.expects(:exchange).with("some_exchange", :type => :topic, :durable => true).returns(42)
262
+ @client.register_exchange("some_exchange", :type => :topic, :durable => true)
263
+ @pub.expects(:bunny).returns(m)
264
+ ex = @pub.send(:exchange, "some_exchange")
265
+ assert @pub.send(:exchanges).include?("some_exchange")
266
+ ex2 = @pub.send(:exchange, "some_exchange")
267
+ assert_equal ex2, ex
268
+ end
269
+ end
270
+
271
+ class PublisherServerManagementTest < Test::Unit::TestCase
272
+ def setup
273
+ @client = Client.new
274
+ @pub = Publisher.new(@client)
275
+ end
276
+
277
+ test "marking the current server as dead should add it to the dead servers hash and remove it from the active servers list" do
278
+ @pub.servers = ["localhost:1111", "localhost:2222"]
279
+ @pub.send(:set_current_server, "localhost:2222")
280
+ @pub.send(:mark_server_dead)
281
+ assert_equal ["localhost:1111"], @pub.servers
282
+ dead = @pub.instance_variable_get "@dead_servers"
283
+ assert_equal ["localhost:2222"], dead.keys
284
+ assert_kind_of Time, dead["localhost:2222"]
285
+ end
286
+
287
+ test "recycle_dead_servers should move servers from the dead server hash to the servers list only if the have been markd dead for longer than 10 seconds" do
288
+ @pub.servers = ["a:1", "b:2"]
289
+ @pub.send(:set_current_server, "a:1")
290
+ @pub.send(:mark_server_dead)
291
+ assert_equal ["b:2"], @pub.servers
292
+ dead = @pub.instance_variable_get("@dead_servers")
293
+ dead["a:1"] = 9.seconds.ago
294
+ @pub.send(:recycle_dead_servers)
295
+ assert_equal ["a:1"], dead.keys
296
+ dead["a:1"] = 11.seconds.ago
297
+ @pub.send(:recycle_dead_servers)
298
+ assert_equal ["b:2", "a:1"], @pub.servers
299
+ assert_equal({}, dead)
300
+ end
301
+
302
+ test "select_next_server should cycle through the list of all servers" do
303
+ @pub.servers = ["a:1", "b:2"]
304
+ @pub.send(:set_current_server, "a:1")
305
+ @pub.send(:select_next_server)
306
+ assert_equal "b:2", @pub.server
307
+ @pub.send(:select_next_server)
308
+ assert_equal "a:1", @pub.server
309
+ end
310
+
311
+ test "select_next_server should return 0 if there are no servers to publish to" do
312
+ @pub.servers = []
313
+ logger = mock('logger')
314
+ logger.expects(:error).returns(true)
315
+ @pub.expects(:logger).returns(logger)
316
+ assert_equal 0, @pub.send(:select_next_server)
317
+ end
318
+
319
+ test "stop should shut down all bunnies" do
320
+ @pub.servers = ["localhost:1111", "localhost:2222"]
321
+ s = sequence("shutdown")
322
+ bunny = mock("bunny")
323
+ @pub.expects(:set_current_server).with("localhost:1111").in_sequence(s)
324
+ @pub.expects(:bunny).returns(bunny).in_sequence(s)
325
+ bunny.expects(:stop).in_sequence(s)
326
+ @pub.expects(:set_current_server).with("localhost:2222").in_sequence(s)
327
+ @pub.expects(:bunny).returns(bunny).in_sequence(s)
328
+ bunny.expects(:stop).in_sequence(s)
329
+ @pub.stop
330
+ end
331
+ end
332
+
333
+
334
+ class RPCTest < Test::Unit::TestCase
335
+ def setup
336
+ @client = Client.new
337
+ @pub = Publisher.new(@client)
338
+ @client.register_message(:test, :exchange => :some_exchange)
339
+ end
340
+
341
+ test "rpc should return a timeout status if bunny throws an exception" do
342
+ bunny = mock("bunny")
343
+ @pub.expects(:bunny).returns(bunny)
344
+ bunny.expects(:queue).raises(Bunny::ConnectionError.new)
345
+ s = sequence("rpc")
346
+ @pub.expects(:select_next_server).in_sequence(s)
347
+ @pub.expects(:bind_queues_for_exchange).with("some_exchange").in_sequence(s)
348
+ @pub.expects(:stop!)
349
+ assert_equal "TIMEOUT", @pub.rpc("test", "hello").first
350
+ end
351
+
352
+ test "rpc should return a timeout status if the answer doesn't arrive in time" do
353
+ bunny = mock("bunny")
354
+ reply_queue = mock("reply_queue")
355
+ exchange = mock("exchange")
356
+ @pub.expects(:bunny).returns(bunny)
357
+ bunny.expects(:queue).returns(reply_queue)
358
+ reply_queue.stubs(:name).returns("reply_queue")
359
+ s = sequence("rpc")
360
+ @pub.expects(:select_next_server).in_sequence(s)
361
+ @pub.expects(:bind_queues_for_exchange).with("some_exchange").in_sequence(s)
362
+ @pub.expects(:exchange).with("some_exchange").returns(exchange).in_sequence(s)
363
+ exchange.expects(:publish).in_sequence(s)
364
+ reply_queue.expects(:subscribe).with(:message_max => 1, :timeout => 10).in_sequence(s)
365
+ assert_equal "TIMEOUT", @pub.rpc("test", "hello").first
366
+ end
367
+
368
+ test "rpc should recover dead servers before selecting the next server" do
369
+ @pub.servers << "localhost:3333"
370
+ @pub.send(:mark_server_dead)
371
+ bunny = mock("bunny")
372
+ reply_queue = mock("reply_queue")
373
+ exchange = mock("exchange")
374
+ @pub.expects(:bunny).returns(bunny)
375
+ bunny.expects(:queue).returns(reply_queue)
376
+ reply_queue.stubs(:name).returns("reply_queue")
377
+ s = sequence("rpc")
378
+ @pub.expects(:recycle_dead_servers).in_sequence(s)
379
+ @pub.expects(:select_next_server).in_sequence(s)
380
+ @pub.expects(:bind_queues_for_exchange).with("some_exchange").in_sequence(s)
381
+ @pub.expects(:exchange).with("some_exchange").returns(exchange).in_sequence(s)
382
+ exchange.expects(:publish).in_sequence(s)
383
+ reply_queue.expects(:subscribe).with(:message_max => 1, :timeout => 10).in_sequence(s)
384
+ assert_equal "TIMEOUT", @pub.rpc("test", "hello").first
385
+ end
386
+
387
+ test "rpc should fetch the result and the status code from the reply message" do
388
+ bunny = mock("bunny")
389
+ reply_queue = mock("reply_queue")
390
+ exchange = mock("exchange")
391
+ @pub.expects(:bunny).returns(bunny)
392
+ bunny.expects(:queue).returns(reply_queue)
393
+ reply_queue.stubs(:name).returns("reply_queue")
394
+ s = sequence("rpc")
395
+ @pub.expects(:select_next_server).in_sequence(s)
396
+ @pub.expects(:bind_queues_for_exchange).with("some_exchange").in_sequence(s)
397
+ @pub.expects(:exchange).with("some_exchange").returns(exchange).in_sequence(s)
398
+ exchange.expects(:publish).in_sequence(s)
399
+ header = mock("header")
400
+ header.expects(:properties).returns({:headers => {:status => "OK"}})
401
+ msg = {:payload => 1, :header => header}
402
+ reply_queue.expects(:subscribe).with(:message_max => 1, :timeout => 10).in_sequence(s).yields(msg)
403
+ assert_equal ["OK",1], @pub.rpc("test", "hello")
404
+ end
405
+
406
+ end
407
+ end
@@ -0,0 +1,9 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ module Beetle
4
+ class ReturnCodesTest < Test::Unit::TestCase
5
+ test "inspecting a return code should display the name of the returncode" do
6
+ assert_equal "Beetle::RC::OK", Beetle::RC::OK.inspect
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,263 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ module Beetle
4
+ class SubscriberTest < Test::Unit::TestCase
5
+ def setup
6
+ client = Client.new
7
+ @sub = client.send(:subscriber)
8
+ end
9
+
10
+ test "initially there should be no amqp connections" do
11
+ assert_equal({}, @sub.instance_variable_get("@amqp_connections"))
12
+ end
13
+
14
+ test "initially there should be no instances of MQ" do
15
+ assert_equal({}, @sub.instance_variable_get("@mqs"))
16
+ end
17
+
18
+ test "acccessing an amq_connection for a server which doesn't have one should create it and associate it with the server" do
19
+ @sub.expects(:new_amqp_connection).returns(42)
20
+ # TODO: smarter way to test? what triggers the amqp_connection private method call?
21
+ assert_equal 42, @sub.send(:amqp_connection)
22
+ connections = @sub.instance_variable_get("@amqp_connections")
23
+ assert_equal 42, connections[@sub.server]
24
+ end
25
+
26
+ test "new amqp connections should be created using current host and port" do
27
+ m = mock("dummy")
28
+ expected_amqp_options = {
29
+ :host => @sub.send(:current_host), :port => @sub.send(:current_port),
30
+ :user => "guest", :pass => "guest", :vhost => "/"
31
+ }
32
+ AMQP.expects(:connect).with(expected_amqp_options).returns(m)
33
+ # TODO: smarter way to test? what triggers the amqp_connection private method call?
34
+ assert_equal m, @sub.send(:new_amqp_connection)
35
+ end
36
+
37
+ test "mq instances should be created for the current server if accessed" do
38
+ @sub.expects(:amqp_connection).returns(11)
39
+ mq_mock = mock('mq')
40
+ mq_mock.expects(:prefetch).with(1).returns(42)
41
+ MQ.expects(:new).with(11).returns(mq_mock)
42
+ assert_equal 42, @sub.send(:mq)
43
+ mqs = @sub.instance_variable_get("@mqs")
44
+ assert_equal 42, mqs[@sub.server]
45
+ end
46
+
47
+ test "stop! should stop the event loop" do
48
+ EM.expects(:stop_event_loop)
49
+ @sub.send(:stop!)
50
+ end
51
+
52
+ end
53
+
54
+ class SubscriberQueueManagementTest < Test::Unit::TestCase
55
+ def setup
56
+ @client = Client.new
57
+ @sub = @client.send(:subscriber)
58
+ end
59
+
60
+ test "initially there should be no queues for the current server" do
61
+ assert_equal({}, @sub.send(:queues))
62
+ assert !@sub.send(:queues)["some_queue"]
63
+ end
64
+
65
+ test "binding a queue should create it using the config and bind it to the exchange with the name specified" do
66
+ @client.register_queue("some_queue", "durable" => true, "exchange" => "some_exchange", "key" => "haha.#")
67
+ @sub.expects(:exchange).with("some_exchange").returns(:the_exchange)
68
+ q = mock("queue")
69
+ q.expects(:bind).with(:the_exchange, {:key => "haha.#"})
70
+ m = mock("MQ")
71
+ m.expects(:queue).with("some_queue", :durable => true, :passive => false, :auto_delete => false, :exclusive => false).returns(q)
72
+ @sub.expects(:mq).returns(m)
73
+
74
+ @sub.send(:queue, "some_queue")
75
+ assert_equal q, @sub.send(:queues)["some_queue"]
76
+ end
77
+
78
+ test "binding queues should iterate over all servers" do
79
+ s = sequence("binding")
80
+ @client.register_queue(:x)
81
+ @client.register_queue(:y)
82
+ @client.register_handler(%w(x y)){}
83
+ @sub.servers = %w(a b)
84
+ @sub.expects(:set_current_server).with("a").in_sequence(s)
85
+ @sub.expects(:queue).with("x").in_sequence(s)
86
+ @sub.expects(:queue).with("y").in_sequence(s)
87
+ @sub.expects(:set_current_server).with("b").in_sequence(s)
88
+ @sub.expects(:queue).with("x").in_sequence(s)
89
+ @sub.expects(:queue).with("y").in_sequence(s)
90
+ @sub.send(:bind_queues, %W(x y))
91
+ end
92
+
93
+ end
94
+
95
+ class SubscriberExchangeManagementTest < Test::Unit::TestCase
96
+ def setup
97
+ @client = Client.new
98
+ @sub = @client.send(:subscriber)
99
+ end
100
+
101
+ test "initially there should be no exchanges for the current server" do
102
+ assert_equal({}, @sub.send(:exchanges))
103
+ end
104
+
105
+ test "accessing a given exchange should create it using the config. further access should return the created exchange" do
106
+ @client.register_exchange("some_exchange", "type" => "topic", "durable" => true)
107
+ m = mock("AMQP")
108
+ m.expects(:topic).with("some_exchange", :durable => true).returns(42)
109
+ @sub.expects(:mq).returns(m)
110
+ ex = @sub.send(:exchange, "some_exchange")
111
+ assert @sub.send(:exchanges).include?("some_exchange")
112
+ ex2 = @sub.send(:exchange, "some_exchange")
113
+ assert_equal ex2, ex
114
+ end
115
+
116
+ test "should create exchanges for all exchanges passed to create_exchanges, for all servers" do
117
+ @sub.servers = %w(x y)
118
+ @client.register_queue(:donald, :exchange => 'duck')
119
+ @client.register_queue(:mickey)
120
+ @client.register_queue(:mouse, :exchange => 'mickey')
121
+
122
+ exchange_creation = sequence("exchange creation")
123
+ @sub.expects(:set_current_server).with('x').in_sequence(exchange_creation)
124
+ @sub.expects(:create_exchange!).with("duck", anything).in_sequence(exchange_creation)
125
+ @sub.expects(:create_exchange!).with("mickey", anything).in_sequence(exchange_creation)
126
+ @sub.expects(:set_current_server).with('y', anything).in_sequence(exchange_creation)
127
+ @sub.expects(:create_exchange!).with("duck", anything).in_sequence(exchange_creation)
128
+ @sub.expects(:create_exchange!).with("mickey", anything).in_sequence(exchange_creation)
129
+ @sub.send(:create_exchanges, %w(duck mickey))
130
+ end
131
+ end
132
+
133
+ class CallBackExecutionTest < Test::Unit::TestCase
134
+ def setup
135
+ client = Client.new
136
+ @queue = "somequeue"
137
+ client.register_queue(@queue)
138
+ @sub = client.send(:subscriber)
139
+ @exception = Exception.new "murks"
140
+ @handler = Handler.create(lambda{|*args| raise @exception})
141
+ @callback = @sub.send(:create_subscription_callback, "my myessage", @queue, @handler, :exceptions => 1)
142
+ end
143
+
144
+ test "exceptions raised from message processing should be ignored" do
145
+ header = header_with_params({})
146
+ Message.any_instance.expects(:process).raises(Exception.new)
147
+ assert_nothing_raised { @callback.call(header, 'foo') }
148
+ end
149
+
150
+ test "should call recover on the server when processing the handler returns true on recover?" do
151
+ header = header_with_params({})
152
+ result = mock("result")
153
+ result.expects(:recover?).returns(true)
154
+ Message.any_instance.expects(:process).returns(result)
155
+ @sub.expects(:sleep).with(1)
156
+ mq = mock("MQ")
157
+ mq.expects(:recover)
158
+ @sub.expects(:mq).with(@sub.server).returns(mq)
159
+ @callback.call(header, 'foo')
160
+ end
161
+
162
+ test "should sent a reply with status OK if the message reply_to header is set and processing the handler succeeds" do
163
+ header = header_with_params(:reply_to => "tmp-queue")
164
+ result = RC::OK
165
+ Message.any_instance.expects(:process).returns(result)
166
+ Message.any_instance.expects(:handler_result).returns("response-data")
167
+ mq = mock("MQ")
168
+ @sub.expects(:mq).with(@sub.server).returns(mq)
169
+ exchange = mock("exchange")
170
+ exchange.expects(:publish).with("response-data", :headers => {:status => "OK"})
171
+ MQ::Exchange.expects(:new).with(mq, :direct, "", :key => "tmp-queue").returns(exchange)
172
+ @callback.call(header, 'foo')
173
+ end
174
+
175
+ test "should sent a reply with status FAILED if the message reply_to header is set and processing the handler fails" do
176
+ header = header_with_params(:reply_to => "tmp-queue")
177
+ result = RC::AttemptsLimitReached
178
+ Message.any_instance.expects(:process).returns(result)
179
+ Message.any_instance.expects(:handler_result).returns(nil)
180
+ mq = mock("MQ")
181
+ @sub.expects(:mq).with(@sub.server).returns(mq)
182
+ exchange = mock("exchange")
183
+ exchange.expects(:publish).with("", :headers => {:status => "FAILED"})
184
+ MQ::Exchange.expects(:new).with(mq, :direct, "", :key => "tmp-queue").returns(exchange)
185
+ @callback.call(header, 'foo')
186
+ end
187
+
188
+ end
189
+
190
+ class SubscriptionTest < Test::Unit::TestCase
191
+ def setup
192
+ @client = Client.new
193
+ @sub = @client.send(:subscriber)
194
+ end
195
+
196
+ test "subscribe should create subscriptions on all queues for all servers" do
197
+ @sub.servers << "localhost:7777"
198
+ @client.register_message(:a)
199
+ @client.register_message(:b)
200
+ @client.register_queue(:a)
201
+ @client.register_queue(:b)
202
+ @client.register_handler(%W(a b)){}
203
+ @sub.expects(:subscribe).with("a").times(2)
204
+ @sub.expects(:subscribe).with("b").times(2)
205
+ @sub.send(:subscribe_queues, %W(a b))
206
+ end
207
+
208
+ test "subscribe should subscribe with a subscription callback created from the registered block" do
209
+ @client.register_queue(:some_queue, :exchange => "some_exchange", :key => "some_key")
210
+ server = @sub.server
211
+ header = header_with_params({})
212
+ header.expects(:ack)
213
+ block_called = false
214
+ proc = lambda do |m|
215
+ block_called = true
216
+ assert_equal header, m.header
217
+ assert_equal "data", m.data
218
+ assert_equal server, m.server
219
+ end
220
+ @sub.register_handler("some_queue", &proc)
221
+ q = mock("QUEUE")
222
+ q.expects(:subscribe).with({:ack => true, :key => "#"}).yields(header, 'foo')
223
+ @sub.expects(:queues).returns({"some_queue" => q})
224
+ @sub.send(:subscribe, "some_queue")
225
+ assert block_called
226
+ end
227
+
228
+ test "subscribe should fail if no handler exists for given message" do
229
+ assert_raises(Error){ @sub.send(:subscribe, "some_queue") }
230
+ end
231
+
232
+ test "listening should use eventmachine. create exchanges. bind queues. install subscribers. and yield." do
233
+ @client.register_queue(:a)
234
+ @client.register_message(:a)
235
+ EM.expects(:run).yields
236
+ @sub.expects(:create_exchanges).with(["a"])
237
+ @sub.expects(:bind_queues).with(["a"])
238
+ @sub.expects(:subscribe_queues).with(["a"])
239
+ @sub.listen(["a"]) {}
240
+ end
241
+ end
242
+
243
+ class HandlersTest < Test::Unit::TestCase
244
+ def setup
245
+ @client = Client.new
246
+ @sub = @client.send(:subscriber)
247
+ end
248
+
249
+ test "initially we should have no handlers" do
250
+ assert_equal({}, @sub.instance_variable_get("@handlers"))
251
+ end
252
+
253
+ test "registering a handler for a queue should store it in the configuration with symbolized option keys" do
254
+ opts = {"ack" => true}
255
+ @sub.register_handler("some_queue", opts){ |*args| 42 }
256
+ opts, block = @sub.instance_variable_get("@handlers")["some_queue"]
257
+ assert_equal({:ack => true}, opts)
258
+ assert_equal 42, block.call(1)
259
+ end
260
+
261
+ end
262
+
263
+ end