cwyckoff-rosetta_queue 0.3.0 → 0.3.3
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.
- data/History.txt +26 -3
- data/README.rdoc +4 -149
- data/Rakefile +3 -0
- data/VERSION.yml +2 -2
- data/cucumber.yml +1 -1
- data/examples/sample_amqp_consumer.rb +45 -0
- data/examples/sample_amqp_fanout_consumer.rb +52 -0
- data/examples/sample_amqp_fanout_producer.rb +18 -0
- data/examples/sample_amqp_producer.rb +16 -0
- data/features/filtering.feature +31 -0
- data/features/messaging.feature +47 -0
- data/features/step_definitions/common_messaging_steps.rb +82 -0
- data/features/step_definitions/filtering_steps.rb +17 -0
- data/features/step_definitions/point_to_point_steps.rb +22 -0
- data/features/step_definitions/publish_subscribe_steps.rb +25 -0
- data/features/support/env.rb +25 -0
- data/features/support/sample_consumers.rb +29 -0
- data/lib/rosetta_queue.rb +3 -2
- data/lib/rosetta_queue/adapter.rb +1 -1
- data/lib/rosetta_queue/adapters/amqp.rb +48 -0
- data/lib/rosetta_queue/adapters/amqp_evented.rb +132 -0
- data/lib/rosetta_queue/adapters/amqp_synch.rb +48 -69
- data/lib/rosetta_queue/adapters/beanstalk.rb +56 -0
- data/lib/rosetta_queue/adapters/stomp.rb +16 -1
- data/lib/rosetta_queue/consumer_managers/base.rb +3 -1
- data/lib/rosetta_queue/consumer_managers/threaded.rb +23 -4
- data/lib/rosetta_queue/core_ext/string.rb +22 -0
- data/lib/rosetta_queue/core_ext/time.rb +20 -0
- data/lib/rosetta_queue/filters.rb +1 -1
- data/lib/rosetta_queue/logger.rb +1 -1
- data/lib/rosetta_queue/message_handler.rb +6 -0
- data/spec/rosetta_queue/adapter_spec.rb +101 -0
- data/spec/rosetta_queue/adapters/amqp_synchronous_spec.rb +278 -0
- data/spec/rosetta_queue/adapters/beanstalk_spec.rb +47 -0
- data/spec/rosetta_queue/adapters/fake_spec.rb +72 -0
- data/spec/rosetta_queue/adapters/null_spec.rb +31 -0
- data/spec/rosetta_queue/adapters/shared_adapter_behavior.rb +38 -0
- data/spec/rosetta_queue/adapters/shared_fanout_behavior.rb +20 -0
- data/spec/rosetta_queue/adapters/stomp_spec.rb +126 -0
- data/spec/rosetta_queue/consumer_managers/evented_spec.rb +56 -0
- data/spec/rosetta_queue/consumer_managers/shared_manager_behavior.rb +26 -0
- data/spec/rosetta_queue/consumer_managers/threaded_spec.rb +51 -0
- data/spec/rosetta_queue/consumer_spec.rb +99 -0
- data/spec/rosetta_queue/core_ext/string_spec.rb +15 -0
- data/spec/rosetta_queue/destinations_spec.rb +34 -0
- data/spec/rosetta_queue/filters_spec.rb +44 -0
- data/spec/rosetta_queue/producer_spec.rb +66 -0
- data/spec/rosetta_queue/shared_messaging_behavior.rb +21 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +47 -0
- metadata +68 -19
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
require 'beanstalk-client'
|
|
2
|
+
|
|
3
|
+
module RosettaQueue
|
|
4
|
+
module Gateway
|
|
5
|
+
|
|
6
|
+
class BeanstalkAdapter < BaseAdapter
|
|
7
|
+
|
|
8
|
+
def ack(msg)
|
|
9
|
+
@conn.ack(msg.headers["message-id"])
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def initialize(adapter_settings = {})
|
|
13
|
+
@host, @port = adapter_settings[:host], adapter_settings[:port]
|
|
14
|
+
@conn = Beanstalk::Pool.new(["#{@host}:#{@port}"])
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def disconnect; end
|
|
18
|
+
|
|
19
|
+
# TODO: support options[:timeout] ?
|
|
20
|
+
def receive(options=nil)
|
|
21
|
+
msg = @conn.reserve
|
|
22
|
+
msg.delete
|
|
23
|
+
msg
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def receive_once(destination=nil, opts={})
|
|
27
|
+
receive.body
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def receive_with(message_handler)
|
|
31
|
+
# Note that, while we call destination_for (to comply with
|
|
32
|
+
# Rosetta's generic specs), beanstalk doesn't actually support
|
|
33
|
+
# destinations. This is just for compatibility.
|
|
34
|
+
destination = destination_for(message_handler)
|
|
35
|
+
|
|
36
|
+
running do
|
|
37
|
+
msg = receive.body
|
|
38
|
+
RosettaQueue.logger.info("Receiving from #{destination} :: #{msg}")
|
|
39
|
+
message_handler.on_message(filter_receiving(msg))
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def send_message(destination, message, options)
|
|
44
|
+
RosettaQueue.logger.info("Publishing to #{destination} :: #{message}")
|
|
45
|
+
@conn.put(message)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def running(&block)
|
|
51
|
+
loop(&block)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -6,11 +6,12 @@ module RosettaQueue
|
|
|
6
6
|
class StompAdapter < BaseAdapter
|
|
7
7
|
|
|
8
8
|
def ack(msg)
|
|
9
|
+
raise AdapterException, "Unable to ack client because message-id is blank. Are your message handler options correct? (i.e., :ack => 'client')" if msg.headers["message-id"].nil?
|
|
9
10
|
@conn.ack(msg.headers["message-id"])
|
|
10
11
|
end
|
|
11
12
|
|
|
12
13
|
def initialize(adapter_settings = {})
|
|
13
|
-
raise "Missing adapter settings" if adapter_settings.empty?
|
|
14
|
+
raise AdapterException, "Missing adapter settings" if adapter_settings.empty?
|
|
14
15
|
@conn = Stomp::Connection.open(adapter_settings[:user],
|
|
15
16
|
adapter_settings[:password],
|
|
16
17
|
adapter_settings[:host],
|
|
@@ -44,8 +45,10 @@ module RosettaQueue
|
|
|
44
45
|
|
|
45
46
|
running do
|
|
46
47
|
msg = receive(options).body
|
|
48
|
+
Thread.current[:processing] = true
|
|
47
49
|
RosettaQueue.logger.info("Receiving from #{destination} :: #{msg}")
|
|
48
50
|
message_handler.on_message(filter_receiving(msg))
|
|
51
|
+
Thread.current[:processing] = false
|
|
49
52
|
end
|
|
50
53
|
end
|
|
51
54
|
|
|
@@ -69,5 +72,17 @@ module RosettaQueue
|
|
|
69
72
|
end
|
|
70
73
|
|
|
71
74
|
end
|
|
75
|
+
|
|
76
|
+
class StompAdapterProxy
|
|
77
|
+
|
|
78
|
+
def initialize(adapter, msg)
|
|
79
|
+
@adapter, @msg = adapter, msg
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def ack
|
|
83
|
+
@adapter.ack(@msg)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
72
87
|
end
|
|
73
88
|
end
|
|
@@ -6,6 +6,7 @@ module RosettaQueue
|
|
|
6
6
|
def initialize
|
|
7
7
|
@threads = {}
|
|
8
8
|
@running = true
|
|
9
|
+
@processing = true
|
|
9
10
|
super
|
|
10
11
|
end
|
|
11
12
|
|
|
@@ -24,12 +25,24 @@ module RosettaQueue
|
|
|
24
25
|
def join_threads
|
|
25
26
|
@threads.each { |thread| thread.join }
|
|
26
27
|
end
|
|
28
|
+
|
|
29
|
+
def shutdown_requested
|
|
30
|
+
RosettaQueue.logger.error "Shutdown requested, starting to prune threads..."
|
|
31
|
+
|
|
32
|
+
while @threads.any? { |n, t| t.alive? }
|
|
33
|
+
RosettaQueue.logger.info "Calling stop_threads"
|
|
34
|
+
stop_threads
|
|
35
|
+
sleep 5
|
|
36
|
+
end
|
|
37
|
+
end
|
|
27
38
|
|
|
28
39
|
def monitor_threads
|
|
29
40
|
while @running
|
|
30
|
-
trap("TERM"
|
|
41
|
+
trap("TERM") { shutdown_requested }
|
|
42
|
+
trap("INT") { shutdown_requested }
|
|
31
43
|
living = false
|
|
32
44
|
@threads.each { |name, thread| living ||= thread.alive? }
|
|
45
|
+
@processing = @threads.any? { |name, thread| thread[:processing] }
|
|
33
46
|
@running = living
|
|
34
47
|
sleep 1
|
|
35
48
|
end
|
|
@@ -64,11 +77,17 @@ module RosettaQueue
|
|
|
64
77
|
end
|
|
65
78
|
|
|
66
79
|
def stop_threads
|
|
80
|
+
RosettaQueue.logger.debug("Attempting to stop all threads...")
|
|
67
81
|
@running = false
|
|
68
|
-
@threads.each do |key, thread|
|
|
69
|
-
|
|
70
|
-
|
|
82
|
+
@threads.select { |key, thread| thread.alive? }.each do |key, thread|
|
|
83
|
+
if thread[:processing]
|
|
84
|
+
RosettaQueue.logger.debug("#{key} Skipping thread #{thread} because the consumer is processing")
|
|
85
|
+
@running = true
|
|
86
|
+
next
|
|
87
|
+
end
|
|
88
|
+
RosettaQueue.logger.debug("#{key} Stopping thread #{thread} and disconnecting the consumer")
|
|
71
89
|
thread.kill
|
|
90
|
+
@consumers[key].disconnect
|
|
72
91
|
end
|
|
73
92
|
end
|
|
74
93
|
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Taken from ActiveSupport
|
|
2
|
+
class String
|
|
3
|
+
def camelize(first_letter_in_uppercase = true)
|
|
4
|
+
if first_letter_in_uppercase
|
|
5
|
+
self.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
|
6
|
+
else
|
|
7
|
+
self.first.downcase + camelize(self)[1..-1]
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def classify
|
|
12
|
+
camelize(self.sub(/.*\./, ''))
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def underscore
|
|
16
|
+
self.gsub(/::/, '/').
|
|
17
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
|
18
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
|
19
|
+
tr("-", "_").
|
|
20
|
+
downcase
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require 'time'
|
|
2
|
+
|
|
3
|
+
class Time
|
|
4
|
+
|
|
5
|
+
DATE_FORMATS = {
|
|
6
|
+
:db => "%Y-%m-%d %H:%M:%S",
|
|
7
|
+
:number => "%Y%m%d%H%M%S",
|
|
8
|
+
:time => "%H:%M",
|
|
9
|
+
:short => "%d %b %H:%M",
|
|
10
|
+
:long => "%B %d, %Y %H:%M",
|
|
11
|
+
:long_ordinal => lambda { |time| time.strftime("%B #{time.day.ordinalize}, %Y %H:%M") },
|
|
12
|
+
:rfc822 => lambda { |time| time.strftime("%a, %d %b %Y %H:%M:%S #{time.formatted_offset(false)}") }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
def to_formatted_s(format = :default)
|
|
16
|
+
return to_default_s unless formatter = DATE_FORMATS[format]
|
|
17
|
+
formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
end
|
data/lib/rosetta_queue/logger.rb
CHANGED
|
@@ -20,6 +20,7 @@ module RosettaQueue
|
|
|
20
20
|
|
|
21
21
|
def self.included(receiver)
|
|
22
22
|
receiver.extend(ClassMethods)
|
|
23
|
+
attr_accessor :adapter_proxy
|
|
23
24
|
|
|
24
25
|
def destination
|
|
25
26
|
self.class.destination
|
|
@@ -28,6 +29,11 @@ module RosettaQueue
|
|
|
28
29
|
def options_hash
|
|
29
30
|
self.class.options_hash
|
|
30
31
|
end
|
|
32
|
+
|
|
33
|
+
def ack
|
|
34
|
+
adapter_proxy.ack unless adapter_proxy.nil?
|
|
35
|
+
end
|
|
36
|
+
|
|
31
37
|
end
|
|
32
38
|
end
|
|
33
39
|
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
|
2
|
+
|
|
3
|
+
module RosettaQueue
|
|
4
|
+
|
|
5
|
+
describe Adapter do
|
|
6
|
+
|
|
7
|
+
before(:each) do
|
|
8
|
+
@stomp_adapter = mock("Gateway::StompAdapter")
|
|
9
|
+
Adapter.reset
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
describe ".reset" do
|
|
13
|
+
it "should clear all definitions" do
|
|
14
|
+
Adapter.define { |a| a.type = "null" }
|
|
15
|
+
Adapter.instance.should be_instance_of(RosettaQueue::Gateway::NullAdapter)
|
|
16
|
+
Adapter.reset
|
|
17
|
+
running { Adapter.instance }.should raise_error(AdapterException)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
describe ".type=" do
|
|
22
|
+
|
|
23
|
+
it "should raise error when adapter does not exist" do
|
|
24
|
+
running {
|
|
25
|
+
Adapter.define do |a|
|
|
26
|
+
a.type = "foo"
|
|
27
|
+
end
|
|
28
|
+
}.should raise_error(AdapterException)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe "adapter not type set" do
|
|
34
|
+
it "should raise an error when .instance is called" do
|
|
35
|
+
# given
|
|
36
|
+
Adapter.define { |a| }
|
|
37
|
+
# then & when
|
|
38
|
+
running { Adapter.instance }.should raise_error(AdapterException)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe "adapter type set" do
|
|
43
|
+
|
|
44
|
+
before(:each) do
|
|
45
|
+
Adapter.define { |a| a.type = "null" }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "should return adapter instance" do
|
|
49
|
+
Adapter.instance.class.should == RosettaQueue::Gateway::NullAdapter
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
describe "adapter instantiation" do
|
|
55
|
+
|
|
56
|
+
before(:each) do
|
|
57
|
+
Adapter.define do |a|
|
|
58
|
+
a.user = "foo"
|
|
59
|
+
a.password = "bar"
|
|
60
|
+
a.host = "localhost"
|
|
61
|
+
a.port = "9000"
|
|
62
|
+
a.type = "fake"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def do_process
|
|
67
|
+
Adapter.instance
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "should set opts as an empty has unless variable is set" do
|
|
71
|
+
during_process {
|
|
72
|
+
RosettaQueue::Gateway::FakeAdapter.should_receive(:new).with({:user => "foo", :password => "bar", :host => "localhost", :port => "9000", :opts => {}})
|
|
73
|
+
}
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
describe "when setting options" do
|
|
77
|
+
before(:each) do
|
|
78
|
+
Adapter.define { |a| a.options = {:vhost => "baz"} }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it "should map adapter_settings to a hash" do
|
|
82
|
+
during_process {
|
|
83
|
+
RosettaQueue::Gateway::FakeAdapter.should_receive(:new).with({:user => "foo", :password => "bar", :host => "localhost", :port => "9000", :opts => {:vhost => "baz"}})
|
|
84
|
+
}
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
describe "setting options incorrectly (options should always be set as a Hash)" do
|
|
89
|
+
|
|
90
|
+
before(:each) do
|
|
91
|
+
Adapter.define { |a| a.options = "baz" }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it "should raise an adapter exception" do
|
|
95
|
+
running { Adapter.instance }.should raise_error("Adapter options should be a hash")
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/../../spec_helper'
|
|
2
|
+
require File.dirname(__FILE__) + '/shared_adapter_behavior'
|
|
3
|
+
require File.dirname(__FILE__) + '/shared_fanout_behavior'
|
|
4
|
+
|
|
5
|
+
begin
|
|
6
|
+
require 'rosetta_queue/adapters/amqp_synch'
|
|
7
|
+
rescue LoadError => ex
|
|
8
|
+
if ex.message =~ /bunny/i
|
|
9
|
+
warn "--WARNING-- Skipping all of tha AmqpSynchAdapter code examples since bunny is not present. Install bunny if you need to be testing this adapter!"
|
|
10
|
+
else
|
|
11
|
+
raise ex
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
if defined? Bunny
|
|
16
|
+
|
|
17
|
+
module RosettaQueue::Gateway
|
|
18
|
+
|
|
19
|
+
describe "an exchange", :shared => true do
|
|
20
|
+
|
|
21
|
+
describe "#do_exchange" do
|
|
22
|
+
|
|
23
|
+
it "should filter the message and forward it to the handler" do
|
|
24
|
+
when_receiving_exchange {
|
|
25
|
+
::RosettaQueue::Filters.should_receive(:process_receiving).with(@msg).and_return("Filtered Message")
|
|
26
|
+
@handler.should_receive(:on_message).with("Filtered Message")
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe "AmqpSynch adapter and components" do
|
|
33
|
+
|
|
34
|
+
before(:each) do
|
|
35
|
+
RosettaQueue.logger.stub!(:info)
|
|
36
|
+
@msg = "Hello World!"
|
|
37
|
+
@adapter = AmqpSynchAdapter.new({:user => "foo", :password => "bar", :host => "localhost"})
|
|
38
|
+
@handler = mock("handler", :on_message => true, :destination => :foo, :options_hash => {:durable => true})
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
describe AmqpSynchAdapter do
|
|
42
|
+
|
|
43
|
+
before(:each) do
|
|
44
|
+
@exchange_strategy = mock('DirectExchange', :receive_once => @msg, :receive => @msg, :send_message => true)
|
|
45
|
+
SynchExchange::DirectExchange.stub!(:new).and_return(@exchange_strategy)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it_should_behave_like "an adapter"
|
|
49
|
+
|
|
50
|
+
describe "#receive_once" do
|
|
51
|
+
|
|
52
|
+
def do_receiving_once
|
|
53
|
+
@adapter.receive_once("queue.foo", {:durable => false})
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "should pass destination and options to exchange strategy" do
|
|
57
|
+
when_receiving_once {
|
|
58
|
+
@exchange_strategy.should_receive(:receive_once).with("queue.foo")
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
describe "#receive_with" do
|
|
65
|
+
|
|
66
|
+
def do_receiving_with_handler
|
|
67
|
+
@adapter.receive_with(@handler)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
before(:each) do
|
|
71
|
+
@handler = mock("handler", :on_message => true, :destination => :foo, :options_hash => {:durable => true })
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it "should pass message handler to exchange strategy" do
|
|
75
|
+
when_receiving_with_handler {
|
|
76
|
+
@exchange_strategy.should_receive(:receive).with("foo", @handler)
|
|
77
|
+
}
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
describe "#send_message" do
|
|
83
|
+
|
|
84
|
+
it "should pass message handler to exchange strategy" do
|
|
85
|
+
when_publishing {
|
|
86
|
+
@exchange_strategy.should_receive(:publish).with('queue', 'message')
|
|
87
|
+
}
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
describe SynchExchange::DirectExchange do
|
|
95
|
+
|
|
96
|
+
before(:each) do
|
|
97
|
+
@queue = mock("Bunny::Queue", :pop => @msg, :publish => true, :unsubscribe => true)
|
|
98
|
+
Bunny.stub!(:new).and_return(@conn = mock("Bunny::Client", :queue => @queue, :exchange => @exchange, :status => :connected, :stop => nil))
|
|
99
|
+
@queue.stub!(:subscribe).and_yield(@msg)
|
|
100
|
+
@handler = mock("handler", :on_message => true, :destination => :foo)
|
|
101
|
+
@exchange = SynchExchange::DirectExchange.new({:user => 'user', :password => 'pass', :host => 'host', :opts => {:vhost => "foo"}})
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def do_receiving_exchange
|
|
106
|
+
@exchange.receive("queue.foo", @handler)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it_should_behave_like "an exchange"
|
|
110
|
+
|
|
111
|
+
describe "#receive_once" do
|
|
112
|
+
|
|
113
|
+
def do_receiving_single_exchange
|
|
114
|
+
@exchange.receive_once("queue.foo") { |msg| }
|
|
115
|
+
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it "should return the message from the connection" do
|
|
119
|
+
@exchange.receive_once("queue.foo") do |msg|
|
|
120
|
+
msg.should == @msg
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it "should subscribe to queue" do
|
|
125
|
+
when_receiving_single_exchange {
|
|
126
|
+
@queue.should_receive(:pop)
|
|
127
|
+
}
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
describe "#receive" do
|
|
133
|
+
|
|
134
|
+
it "should subscribe to queue" do
|
|
135
|
+
when_receiving_exchange {
|
|
136
|
+
@queue.should_receive(:subscribe).and_yield(@msg)
|
|
137
|
+
}
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
describe "#publish" do
|
|
144
|
+
|
|
145
|
+
def do_publishing
|
|
146
|
+
@exchange.publish('queue.foo', 'message')
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it "should instantiate queue" do
|
|
150
|
+
when_publishing {
|
|
151
|
+
@conn.should_receive(:queue).and_return(@queue)
|
|
152
|
+
}
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
it "should publish message to queue" do
|
|
156
|
+
when_publishing {
|
|
157
|
+
@conn.queue.should_receive(:publish).with("message", {})
|
|
158
|
+
}
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
describe SynchExchange::FanoutExchange do
|
|
167
|
+
|
|
168
|
+
before(:each) do
|
|
169
|
+
@exchange = SynchExchange::FanoutExchange.new({:user => 'user', :password => 'pass', :host => 'host', :opts => {:vhost => 'foo'}})
|
|
170
|
+
@queue = mock("Bunny::Queue", :pop => @msg, :bind => @bound_queue = mock("Bunny::Queue", :pop => @msg), :publish => true, :unbind => true)
|
|
171
|
+
Bunny.stub!(:new).and_return(@conn = mock("Bunny::Client", :queue => @queue, :exchange => @exchange, :status => :connected))
|
|
172
|
+
@queue.stub!(:subscribe).and_yield(@msg)
|
|
173
|
+
@handler = mock("handler", :on_message => true, :destination => :foo, :options => {:durable => false})
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def do_receiving_exchange
|
|
177
|
+
@exchange.receive("topic.foo", @handler)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
it_should_behave_like "an exchange"
|
|
181
|
+
|
|
182
|
+
describe "#receive_once" do
|
|
183
|
+
|
|
184
|
+
def do_receiving_exchange
|
|
185
|
+
@exchange.receive_once("topic.foo") { |msg| }
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
it_should_behave_like "a fanout exchange adapter"
|
|
189
|
+
|
|
190
|
+
it "should return the message from the connection" do
|
|
191
|
+
@exchange.receive_once("topic.foo") do |msg|
|
|
192
|
+
msg.should == @msg
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
it "should subscribe to queue" do
|
|
197
|
+
when_receiving_exchange {
|
|
198
|
+
@queue.should_receive(:pop)
|
|
199
|
+
}
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
it "should unbind queue from exchange" do
|
|
203
|
+
pending
|
|
204
|
+
when_receiving_single_exchange {
|
|
205
|
+
@queue.should_receive(:unbind)
|
|
206
|
+
}
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
describe "#receive" do
|
|
212
|
+
|
|
213
|
+
it_should_behave_like "a fanout exchange adapter"
|
|
214
|
+
|
|
215
|
+
it "should forward the message body onto the handler" do
|
|
216
|
+
when_receiving_exchange {
|
|
217
|
+
@handler.should_receive(:on_message).with("Hello World!")
|
|
218
|
+
}
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
it "should subscribe to queue" do
|
|
222
|
+
when_receiving_exchange {
|
|
223
|
+
@queue.should_receive(:subscribe).and_yield(@msg)
|
|
224
|
+
}
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# describe "#publish_to_exchange" do
|
|
230
|
+
#
|
|
231
|
+
# def do_publishing
|
|
232
|
+
# @exchange.publish_to_exchange('/queue/foo', 'message', {:durable => false})
|
|
233
|
+
# end
|
|
234
|
+
#
|
|
235
|
+
# it "should instantiate queue" do
|
|
236
|
+
# when_publishing {
|
|
237
|
+
# @channel.should_receive(:queue).and_return(@queue)
|
|
238
|
+
# }
|
|
239
|
+
# end
|
|
240
|
+
#
|
|
241
|
+
# it "should publish message to queue" do
|
|
242
|
+
# when_publishing {
|
|
243
|
+
# @channel.queue.should_receive(:publish).with('message')
|
|
244
|
+
# }
|
|
245
|
+
# end
|
|
246
|
+
#
|
|
247
|
+
# it "should stop event loop" do
|
|
248
|
+
# when_publishing {
|
|
249
|
+
# EM.should_receive(:stop_event_loop)
|
|
250
|
+
# }
|
|
251
|
+
# end
|
|
252
|
+
# end
|
|
253
|
+
|
|
254
|
+
# describe SynchExchange::AmqpAdapterProxy do
|
|
255
|
+
|
|
256
|
+
# before(:each) do
|
|
257
|
+
# @queue = mock("Queue", :ack => nil)
|
|
258
|
+
# @proxy = SynchExchange::AmqpAdapterProxy.new(@queue)
|
|
259
|
+
# end
|
|
260
|
+
|
|
261
|
+
# context "#ack" do
|
|
262
|
+
|
|
263
|
+
# it "should delegate to AMQP queue object" do
|
|
264
|
+
# # expect
|
|
265
|
+
# @queue.should_receive(:ack)
|
|
266
|
+
|
|
267
|
+
# # when
|
|
268
|
+
# @proxy.ack
|
|
269
|
+
# end
|
|
270
|
+
|
|
271
|
+
# end
|
|
272
|
+
# end
|
|
273
|
+
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
end # for Guard up top to prevent this spec from running if Bunny not loaded from amqp_synch
|