PageRenderTime 45ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/test/beetle/publisher_test.rb

https://github.com/cpocommerce/beetle
Ruby | 407 lines | 401 code | 6 blank | 0 comment | 1 complexity | 04f31cc8e8c6ba13c01b98c28d99b606 MD5 | raw file
  1. require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
  2. module Beetle
  3. class PublisherTest < Test::Unit::TestCase
  4. def setup
  5. client = Client.new
  6. @pub = Publisher.new(client)
  7. end
  8. test "acccessing a bunny for a server which doesn't have one should create it and associate it with the server" do
  9. @pub.expects(:new_bunny).returns(42)
  10. assert_equal 42, @pub.send(:bunny)
  11. bunnies = @pub.instance_variable_get("@bunnies")
  12. assert_equal(42, bunnies[@pub.server])
  13. end
  14. test "new bunnies should be created using current host and port and they should be started" do
  15. m = mock("dummy")
  16. expected_bunny_options = {
  17. :host => @pub.send(:current_host), :port => @pub.send(:current_port),
  18. :logging => false, :user => "guest", :pass => "guest", :vhost => "/"
  19. }
  20. Bunny.expects(:new).with(expected_bunny_options).returns(m)
  21. m.expects(:start)
  22. assert_equal m, @pub.send(:new_bunny)
  23. end
  24. test "initially there should be no bunnies" do
  25. assert_equal({}, @pub.instance_variable_get("@bunnies"))
  26. end
  27. test "initially there should be no dead servers" do
  28. assert_equal({}, @pub.instance_variable_get("@dead_servers"))
  29. end
  30. test "stop! should shut down bunny and clean internal data structures" do
  31. b = mock("bunny")
  32. b.expects(:stop).raises(Exception.new)
  33. @pub.expects(:bunny).returns(b)
  34. @pub.send(:stop!)
  35. assert_equal({}, @pub.send(:exchanges))
  36. assert_equal({}, @pub.send(:queues))
  37. assert_nil @pub.instance_variable_get(:@bunnies)[@pub.server]
  38. end
  39. end
  40. class PublisherPublishingTest < Test::Unit::TestCase
  41. def setup
  42. @client = Client.new
  43. @pub = Publisher.new(@client)
  44. @pub.stubs(:bind_queues_for_exchange)
  45. @client.register_queue("mama", :exchange => "mama-exchange")
  46. @client.register_message("mama", :ttl => 1.hour, :exchange => "mama-exchange")
  47. @opts = { :ttl => 1.hour , :key => "mama", :persistent => true}
  48. @data = 'XXX'
  49. end
  50. test "failover publishing should try to recycle dead servers before trying to publish the message" do
  51. @pub.servers << "localhost:3333"
  52. @pub.send(:mark_server_dead)
  53. publishing = sequence('publishing')
  54. @pub.expects(:recycle_dead_servers).in_sequence(publishing)
  55. @pub.expects(:publish_with_failover).with("mama-exchange", "mama", @data, @opts).in_sequence(publishing)
  56. @pub.publish("mama", @data)
  57. end
  58. test "redundant publishing should try to recycle dead servers before trying to publish the message" do
  59. @pub.servers << "localhost:3333"
  60. @pub.send(:mark_server_dead)
  61. publishing = sequence('publishing')
  62. @pub.expects(:recycle_dead_servers).in_sequence(publishing)
  63. @pub.expects(:publish_with_redundancy).with("mama-exchange", "mama", @data, @opts.merge(:redundant => true)).in_sequence(publishing)
  64. @pub.publish("mama", @data, :redundant => true)
  65. end
  66. test "publishing should fail over to the next server" do
  67. failover = sequence('failover')
  68. @pub.expects(:select_next_server).in_sequence(failover)
  69. e = mock("exchange")
  70. @pub.expects(:exchange).with("mama-exchange").returns(e).in_sequence(failover)
  71. e.expects(:publish).raises(Bunny::ConnectionError).in_sequence(failover)
  72. @pub.expects(:stop!).in_sequence(failover)
  73. @pub.expects(:mark_server_dead).in_sequence(failover)
  74. @pub.publish_with_failover("mama-exchange", "mama", @data, @opts)
  75. end
  76. test "redundant publishing should send the message to two servers" do
  77. redundant = sequence("redundant")
  78. @pub.servers = ["someserver", "someotherserver"]
  79. @pub.server = "someserver"
  80. e = mock("exchange")
  81. @pub.expects(:exchange).with("mama-exchange").returns(e).in_sequence(redundant)
  82. e.expects(:publish).in_sequence(redundant)
  83. @pub.expects(:exchange).with("mama-exchange").returns(e).in_sequence(redundant)
  84. e.expects(:publish).in_sequence(redundant)
  85. assert_equal 2, @pub.publish_with_redundancy("mama-exchange", "mama", @data, @opts)
  86. end
  87. test "redundant publishing should return 1 if the message was published to one server only" do
  88. redundant = sequence("redundant")
  89. @pub.servers = ["someserver", "someotherserver"]
  90. @pub.server = "someserver"
  91. e = mock("exchange")
  92. @pub.expects(:exchange).with("mama-exchange").returns(e).in_sequence(redundant)
  93. e.expects(:publish).raises(Bunny::ConnectionError).in_sequence(redundant)
  94. @pub.expects(:exchange).with("mama-exchange").returns(e).in_sequence(redundant)
  95. e.expects(:publish).in_sequence(redundant)
  96. assert_equal 1, @pub.publish_with_redundancy("mama-exchange", "mama", @data, @opts)
  97. end
  98. test "redundant publishing should return 0 if the message was published to no server" do
  99. redundant = sequence("redundant")
  100. @pub.servers = ["someserver", "someotherserver"]
  101. @pub.server = "someserver"
  102. e = mock("exchange")
  103. @pub.expects(:exchange).with("mama-exchange").returns(e).in_sequence(redundant)
  104. e.expects(:publish).raises(Bunny::ConnectionError).in_sequence(redundant)
  105. @pub.expects(:exchange).with("mama-exchange").returns(e).in_sequence(redundant)
  106. e.expects(:publish).raises(Bunny::ConnectionError).in_sequence(redundant)
  107. assert_equal 0, @pub.publish_with_redundancy("mama-exchange", "mama", @data, @opts)
  108. end
  109. test "redundant publishing should fallback to failover publishing if less than one server is available" do
  110. @pub.server = ["a server"]
  111. @pub.expects(:publish_with_failover).with("mama-exchange", "mama", @data, @opts).returns(1)
  112. assert_equal 1, @pub.publish_with_redundancy("mama-exchange", "mama", @data, @opts)
  113. end
  114. test "redundant publishing should publish to two of three servers if one server is dead" do
  115. @pub.servers = %w(server1 server2 server3)
  116. @pub.server = "server1"
  117. redundant = sequence("redundant")
  118. e = mock("exchange")
  119. @pub.expects(:exchange).returns(e).in_sequence(redundant)
  120. e.expects(:publish).in_sequence(redundant)
  121. @pub.expects(:exchange).returns(e).in_sequence(redundant)
  122. e.expects(:publish).raises(Bunny::ConnectionError).in_sequence(redundant)
  123. @pub.expects(:stop!).in_sequence(redundant)
  124. @pub.expects(:exchange).returns(e).in_sequence(redundant)
  125. e.expects(:publish).in_sequence(redundant)
  126. assert_equal 2, @pub.publish_with_redundancy("mama-exchange", "mama", @data, @opts)
  127. end
  128. test "publishing should use the message ttl passed in the options hash to encode the message body" do
  129. opts = {:ttl => 1.day}
  130. Message.expects(:publishing_options).with(:ttl => 1.day).returns({})
  131. @pub.expects(:select_next_server)
  132. e = mock("exchange")
  133. @pub.expects(:exchange).returns(e)
  134. e.expects(:publish)
  135. assert_equal 1, @pub.publish_with_failover("mama-exchange", "mama", @data, opts)
  136. end
  137. test "publishing with redundancy should use the message ttl passed in the options hash to encode the message body" do
  138. opts = {:ttl => 1.day}
  139. Message.expects(:publishing_options).with(:ttl => 1.day).returns({})
  140. @pub.expects(:select_next_server)
  141. e = mock("exchange")
  142. @pub.expects(:exchange).returns(e)
  143. e.expects(:publish)
  144. assert_equal 1, @pub.publish_with_redundancy("mama-exchange", "mama", @data, opts)
  145. end
  146. test "publishing should use the message ttl from the message configuration if no ttl is passed in via the options hash" do
  147. @pub.expects(:publish_with_failover).with("mama-exchange", "mama", @data, @opts).returns(1)
  148. assert_equal 1, @pub.publish("mama", @data)
  149. end
  150. end
  151. class PublisherQueueManagementTest < Test::Unit::TestCase
  152. def setup
  153. @client = Client.new
  154. @pub = Publisher.new(@client)
  155. end
  156. test "initially there should be no queues for the current server" do
  157. assert_equal({}, @pub.send(:queues))
  158. assert !@pub.send(:queues)["some_queue"]
  159. end
  160. test "binding a queue should create it using the config and bind it to the exchange with the name specified" do
  161. @client.register_queue("some_queue", :exchange => "some_exchange", :key => "haha.#")
  162. @pub.expects(:exchange).with("some_exchange").returns(:the_exchange)
  163. q = mock("queue")
  164. q.expects(:bind).with(:the_exchange, {:key => "haha.#"})
  165. m = mock("Bunny")
  166. m.expects(:queue).with("some_queue", :durable => true, :passive => false, :auto_delete => false, :exclusive => false).returns(q)
  167. @pub.expects(:bunny).returns(m)
  168. @pub.send(:queue, "some_queue")
  169. assert_equal q, @pub.send(:queues)["some_queue"]
  170. end
  171. test "should bind the defined queues for the used exchanges when publishing" do
  172. @client.register_queue('test_queue_1', :exchange => 'test_exchange')
  173. @client.register_queue('test_queue_2', :exchange => 'test_exchange')
  174. @client.register_queue('test_queue_3', :exchange => 'test_exchange_2')
  175. @pub.expects(:bind_queue!).returns(1).times(3)
  176. @pub.send(:bind_queues_for_exchange, 'test_exchange')
  177. @pub.send(:bind_queues_for_exchange, 'test_exchange_2')
  178. end
  179. test "should not rebind the defined queues for the used exchanges if they already have been bound" do
  180. @client.register_queue('test_queue_1', :exchange => 'test_exchange')
  181. @client.register_queue('test_queue_2', :exchange => 'test_exchange')
  182. @pub.expects(:bind_queue!).twice
  183. @pub.send(:bind_queues_for_exchange, 'test_exchange')
  184. @pub.send(:bind_queues_for_exchange, 'test_exchange')
  185. end
  186. test "call the queue binding method when publishing" do
  187. data = "XXX"
  188. opts = {}
  189. @client.register_queue("mama", :exchange => "mama-exchange")
  190. @client.register_message("mama", :ttl => 1.hour, :exchange => "mama-exchange")
  191. e = stub('exchange', 'publish')
  192. @pub.expects(:exchange).with('mama-exchange').returns(e)
  193. @pub.expects(:bind_queues_for_exchange).with('mama-exchange').returns(true)
  194. @pub.publish('mama', data)
  195. end
  196. test "purging a queue should purge the queues on all servers" do
  197. @pub.servers = %w(a b)
  198. queue = mock("queue")
  199. s = sequence("purging")
  200. @pub.expects(:set_current_server).with("a").in_sequence(s)
  201. @pub.expects(:queue).with("queue").returns(queue).in_sequence(s)
  202. queue.expects(:purge).in_sequence(s)
  203. @pub.expects(:set_current_server).with("b").in_sequence(s)
  204. @pub.expects(:queue).with("queue").returns(queue).in_sequence(s)
  205. queue.expects(:purge).in_sequence(s)
  206. @pub.purge("queue")
  207. end
  208. end
  209. class PublisherExchangeManagementTest < Test::Unit::TestCase
  210. def setup
  211. @client = Client.new
  212. @pub = Publisher.new(@client)
  213. end
  214. test "initially there should be no exchanges for the current server" do
  215. assert_equal({}, @pub.send(:exchanges))
  216. end
  217. test "accessing a given exchange should create it using the config. further access should return the created exchange" do
  218. m = mock("Bunny")
  219. m.expects(:exchange).with("some_exchange", :type => :topic, :durable => true).returns(42)
  220. @client.register_exchange("some_exchange", :type => :topic, :durable => true)
  221. @pub.expects(:bunny).returns(m)
  222. ex = @pub.send(:exchange, "some_exchange")
  223. assert @pub.send(:exchanges).include?("some_exchange")
  224. ex2 = @pub.send(:exchange, "some_exchange")
  225. assert_equal ex2, ex
  226. end
  227. end
  228. class PublisherServerManagementTest < Test::Unit::TestCase
  229. def setup
  230. @client = Client.new
  231. @pub = Publisher.new(@client)
  232. end
  233. test "marking the current server as dead should add it to the dead servers hash and remove it from the active servers list" do
  234. @pub.servers = ["localhost:1111", "localhost:2222"]
  235. @pub.send(:set_current_server, "localhost:2222")
  236. @pub.send(:mark_server_dead)
  237. assert_equal ["localhost:1111"], @pub.servers
  238. dead = @pub.instance_variable_get "@dead_servers"
  239. assert_equal ["localhost:2222"], dead.keys
  240. assert_kind_of Time, dead["localhost:2222"]
  241. end
  242. 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
  243. @pub.servers = ["a:1", "b:2"]
  244. @pub.send(:set_current_server, "a:1")
  245. @pub.send(:mark_server_dead)
  246. assert_equal ["b:2"], @pub.servers
  247. dead = @pub.instance_variable_get("@dead_servers")
  248. dead["a:1"] = 9.seconds.ago
  249. @pub.send(:recycle_dead_servers)
  250. assert_equal ["a:1"], dead.keys
  251. dead["a:1"] = 11.seconds.ago
  252. @pub.send(:recycle_dead_servers)
  253. assert_equal ["b:2", "a:1"], @pub.servers
  254. assert_equal({}, dead)
  255. end
  256. test "select_next_server should cycle through the list of all servers" do
  257. @pub.servers = ["a:1", "b:2"]
  258. @pub.send(:set_current_server, "a:1")
  259. @pub.send(:select_next_server)
  260. assert_equal "b:2", @pub.server
  261. @pub.send(:select_next_server)
  262. assert_equal "a:1", @pub.server
  263. end
  264. test "select_next_server should return 0 if there are no servers to publish to" do
  265. @pub.servers = []
  266. logger = mock('logger')
  267. logger.expects(:error).returns(true)
  268. @pub.expects(:logger).returns(logger)
  269. assert_equal 0, @pub.send(:select_next_server)
  270. end
  271. test "stop should shut down all bunnies" do
  272. @pub.servers = ["localhost:1111", "localhost:2222"]
  273. s = sequence("shutdown")
  274. bunny = mock("bunny")
  275. @pub.expects(:set_current_server).with("localhost:1111").in_sequence(s)
  276. @pub.expects(:bunny).returns(bunny).in_sequence(s)
  277. bunny.expects(:stop).in_sequence(s)
  278. @pub.expects(:set_current_server).with("localhost:2222").in_sequence(s)
  279. @pub.expects(:bunny).returns(bunny).in_sequence(s)
  280. bunny.expects(:stop).in_sequence(s)
  281. @pub.stop
  282. end
  283. end
  284. class RPCTest < Test::Unit::TestCase
  285. def setup
  286. @client = Client.new
  287. @pub = Publisher.new(@client)
  288. @client.register_message(:test, :exchange => :some_exchange)
  289. end
  290. test "rpc should return a timeout status if bunny throws an exception" do
  291. bunny = mock("bunny")
  292. @pub.expects(:bunny).returns(bunny)
  293. bunny.expects(:queue).raises(Bunny::ConnectionError.new)
  294. s = sequence("rpc")
  295. @pub.expects(:select_next_server).in_sequence(s)
  296. @pub.expects(:bind_queues_for_exchange).with("some_exchange").in_sequence(s)
  297. @pub.expects(:stop!)
  298. assert_equal "TIMEOUT", @pub.rpc("test", "hello").first
  299. end
  300. test "rpc should return a timeout status if the answer doesn't arrive in time" do
  301. bunny = mock("bunny")
  302. reply_queue = mock("reply_queue")
  303. exchange = mock("exchange")
  304. @pub.expects(:bunny).returns(bunny)
  305. bunny.expects(:queue).returns(reply_queue)
  306. reply_queue.stubs(:name).returns("reply_queue")
  307. s = sequence("rpc")
  308. @pub.expects(:select_next_server).in_sequence(s)
  309. @pub.expects(:bind_queues_for_exchange).with("some_exchange").in_sequence(s)
  310. @pub.expects(:exchange).with("some_exchange").returns(exchange).in_sequence(s)
  311. exchange.expects(:publish).in_sequence(s)
  312. reply_queue.expects(:subscribe).with(:message_max => 1, :timeout => 10).in_sequence(s)
  313. assert_equal "TIMEOUT", @pub.rpc("test", "hello").first
  314. end
  315. test "rpc should recover dead servers before selecting the next server" do
  316. @pub.servers << "localhost:3333"
  317. @pub.send(:mark_server_dead)
  318. bunny = mock("bunny")
  319. reply_queue = mock("reply_queue")
  320. exchange = mock("exchange")
  321. @pub.expects(:bunny).returns(bunny)
  322. bunny.expects(:queue).returns(reply_queue)
  323. reply_queue.stubs(:name).returns("reply_queue")
  324. s = sequence("rpc")
  325. @pub.expects(:recycle_dead_servers).in_sequence(s)
  326. @pub.expects(:select_next_server).in_sequence(s)
  327. @pub.expects(:bind_queues_for_exchange).with("some_exchange").in_sequence(s)
  328. @pub.expects(:exchange).with("some_exchange").returns(exchange).in_sequence(s)
  329. exchange.expects(:publish).in_sequence(s)
  330. reply_queue.expects(:subscribe).with(:message_max => 1, :timeout => 10).in_sequence(s)
  331. assert_equal "TIMEOUT", @pub.rpc("test", "hello").first
  332. end
  333. test "rpc should fetch the result and the status code from the reply message" do
  334. bunny = mock("bunny")
  335. reply_queue = mock("reply_queue")
  336. exchange = mock("exchange")
  337. @pub.expects(:bunny).returns(bunny)
  338. bunny.expects(:queue).returns(reply_queue)
  339. reply_queue.stubs(:name).returns("reply_queue")
  340. s = sequence("rpc")
  341. @pub.expects(:select_next_server).in_sequence(s)
  342. @pub.expects(:bind_queues_for_exchange).with("some_exchange").in_sequence(s)
  343. @pub.expects(:exchange).with("some_exchange").returns(exchange).in_sequence(s)
  344. exchange.expects(:publish).in_sequence(s)
  345. header = mock("header")
  346. header.expects(:properties).returns({:headers => {:status => "OK"}})
  347. msg = {:payload => 1, :header => header}
  348. reply_queue.expects(:subscribe).with(:message_max => 1, :timeout => 10).in_sequence(s).yields(msg)
  349. assert_equal ["OK",1], @pub.rpc("test", "hello")
  350. end
  351. end
  352. end