mbus 1.0.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.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/README.mediawiki +169 -0
  3. data/Rakefile +24 -0
  4. data/bin/console +11 -0
  5. data/bin/messagebus_swarm +77 -0
  6. data/lib/messagebus.rb +62 -0
  7. data/lib/messagebus/client.rb +166 -0
  8. data/lib/messagebus/cluster_map.rb +161 -0
  9. data/lib/messagebus/connection.rb +118 -0
  10. data/lib/messagebus/consumer.rb +447 -0
  11. data/lib/messagebus/custom_errors.rb +37 -0
  12. data/lib/messagebus/dottable_hash.rb +113 -0
  13. data/lib/messagebus/error_status.rb +42 -0
  14. data/lib/messagebus/logger.rb +45 -0
  15. data/lib/messagebus/message.rb +168 -0
  16. data/lib/messagebus/messagebus_types.rb +107 -0
  17. data/lib/messagebus/producer.rb +187 -0
  18. data/lib/messagebus/swarm.rb +49 -0
  19. data/lib/messagebus/swarm/controller.rb +296 -0
  20. data/lib/messagebus/swarm/drone.rb +195 -0
  21. data/lib/messagebus/swarm/drone/logging_worker.rb +53 -0
  22. data/lib/messagebus/validations.rb +68 -0
  23. data/lib/messagebus/version.rb +36 -0
  24. data/messagebus.gemspec +29 -0
  25. data/spec/messagebus/client_spec.rb +157 -0
  26. data/spec/messagebus/cluster_map_spec.rb +178 -0
  27. data/spec/messagebus/consumer_spec.rb +338 -0
  28. data/spec/messagebus/dottable_hash_spec.rb +137 -0
  29. data/spec/messagebus/message_spec.rb +93 -0
  30. data/spec/messagebus/producer_spec.rb +147 -0
  31. data/spec/messagebus/swarm/controller_spec.rb +73 -0
  32. data/spec/messagebus/validations_spec.rb +71 -0
  33. data/spec/spec_helper.rb +10 -0
  34. data/vendor/gems/stomp.rb +23 -0
  35. data/vendor/gems/stomp/client.rb +360 -0
  36. data/vendor/gems/stomp/connection.rb +583 -0
  37. data/vendor/gems/stomp/errors.rb +39 -0
  38. data/vendor/gems/stomp/ext/hash.rb +24 -0
  39. data/vendor/gems/stomp/message.rb +68 -0
  40. metadata +138 -0
@@ -0,0 +1,195 @@
1
+ # Copyright (c) 2012, Groupon, Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions
6
+ # are met:
7
+ #
8
+ # Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ #
11
+ # Redistributions in binary form must reproduce the above copyright
12
+ # notice, this list of conditions and the following disclaimer in the
13
+ # documentation and/or other materials provided with the distribution.
14
+ #
15
+ # Neither the name of GROUPON nor the names of its contributors may be
16
+ # used to endorse or promote products derived from this software without
17
+ # specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20
+ # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21
+ # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22
+ # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23
+ # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
25
+ # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26
+ # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28
+ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+
31
+ require 'json'
32
+
33
+ module Messagebus
34
+ module Swarm
35
+ ##
36
+ # This is a composition of a consumer and a separate message processor.
37
+ # It allows you to use Plain-Old-Ruby-Objects to do message processing.
38
+ # See #initialize for how the messages are delegated.
39
+ class Drone
40
+ # Raise this error when you want to abort processing a message and not
41
+ # acknowledge it.
42
+ class AbortProcessing < StandardError; end
43
+
44
+ INITIALIZING = "initializing"
45
+ RECEIVING = "receiving"
46
+ PROCESSING = "processing"
47
+ COMPLETED = "completed"
48
+
49
+ STOP_PROCESSING_MESSAGE = "stop processing message"
50
+
51
+ attr_reader :state, :id
52
+
53
+ ##
54
+ # Expected options:
55
+ # * :ack_on_error (default false): whether to ack the message when an error was raised. Aliased to auto_acknowledge for backwards compatibility
56
+ # * :consumer (required): a consumer object for that topic
57
+ # * :destination_name (required): the message bus queue/topic name
58
+ # * :id: an id for this drone (just used for debugging)
59
+ # * :logger (required): the logger to publish messages to
60
+ # * :worker_class (required): the actual worker that will be used to do the processing
61
+ #
62
+ # As messages come down, they will be passed to the worker's perform or
63
+ # perform_on_destination method. The methods will be called in the
64
+ # following priority (if they exist):
65
+ # * perform_on_destination(message_payload, destination_message_came_from)
66
+ # * perform(message_payload)
67
+ #
68
+ # A message is processed by:
69
+ # message = receive_message
70
+ # begin
71
+ # worker.perform(message)
72
+ # ack(message)
73
+ # rescue AbortProcessing
74
+ # # raise this error if you want to say "Don't ack me"
75
+ # rescue StandardError
76
+ # ack(message) if ack_on_error
77
+ # end
78
+ def initialize(options)
79
+ @auto_acknowledge = options.fetch(:ack_on_error, options[:auto_acknowledge])
80
+ @consumer, @destination_name, @worker_class, @id, @logger = options.values_at(:consumer, :destination_name, :worker_class, :id, :logger)
81
+ @state = INITIALIZING
82
+ @logger.debug { "initializing a drone drone_id=#{@id}" }
83
+ end
84
+
85
+ ##
86
+ # This is the main loop for the drone's work. This will not return until
87
+ # the drone is stopped via #stop.
88
+ #
89
+ # If the consumer hasn't been started yet, this method will start it. It
90
+ # also will auto close the consumer in that case.
91
+ def processing_loop
92
+ @processing = true
93
+
94
+ auto_started_consumer = false
95
+ begin
96
+ if !@consumer.started?
97
+ @logger.debug "auto starting the consumer drone_id=#{@id}"
98
+ @consumer.start
99
+ auto_started_consumer = true
100
+ end
101
+
102
+ while @processing
103
+ message = nil
104
+ begin
105
+ @logger.debug "waiting for a message"
106
+
107
+ @state = RECEIVING
108
+ message = @consumer.receive
109
+ # intentional === here, this is used as a signal, so we can use object equality
110
+ # to check if we received the signal
111
+ if message === STOP_PROCESSING_MESSAGE
112
+ @logger.info "received a stop message, exiting drone_id=#{@id}, message=#{message.inspect}"
113
+ @state = COMPLETED
114
+ next
115
+ end
116
+
117
+ @logger.info "received message drone_id=#{@id}, message_id=#{message.message_id}"
118
+
119
+ @state = PROCESSING
120
+ @logger.info "processing message drone_id=#{@id}, message_id=#{message.message_id}, worker=#{@worker_class.name}"
121
+
122
+ raw_message = extract_underlying_message_body(message)
123
+ @logger.debug { "drone_id=#{@id} message_id=#{message.message_id}, message=#{raw_message.inspect}" }
124
+
125
+ worker_perform(@logger, @destination_name, @worker_class, @consumer, @auto_acknowledge, message, raw_message)
126
+
127
+ @state = COMPLETED
128
+ rescue => except
129
+ @logger.warn "exception processing message drone_id=#{@id}, message_id=#{message && message.message_id}, exception=\"#{except.message}\", exception_backtrace=#{except.backtrace.join("|")}"
130
+ end
131
+ end
132
+ ensure
133
+ if auto_started_consumer
134
+ @logger.debug "auto stopping the consumer drone_id=#{@id}"
135
+ @consumer.stop
136
+ end
137
+ end
138
+
139
+ @logger.info("gracefully exited drone_id=#{@id}")
140
+ end
141
+
142
+ ##
143
+ # Stops this drone from processing any additional jobs.
144
+ # This will not abort any in progress jobs.
145
+ def stop
146
+ @logger.info("received stop message, current_state=#{@state}, processing=#{@processing}, drone_id=#{@id}")
147
+ return if !@processing
148
+
149
+ @processing = false
150
+ @consumer.insert_sentinel_value(STOP_PROCESSING_MESSAGE)
151
+ end
152
+
153
+ private
154
+ def blocking_on_receive?(state)
155
+ state == RECEIVING
156
+ end
157
+
158
+ def worker_perform(logger, destination_name, worker_class, consumer, auto_acknowledge, message, raw_message)
159
+ begin
160
+ if worker_class.respond_to?("perform_on_destination")
161
+ worker_class.perform_on_destination(raw_message, destination_name)
162
+ else
163
+ worker_class.perform(raw_message)
164
+ end
165
+
166
+ # acknowledge unless exception is thrown (auto perform == true)
167
+ logger.debug "processing complete, acknowledging message, drone_id=#{@id}, message_id=#{message.message_id}"
168
+ consumer.ack
169
+
170
+ # This is just a clean error people can throw to trigger an abort
171
+ rescue AbortProcessing => e
172
+ logger.info "processing aborted drone_id=#{@id}, message_id=#{message.message_id}"
173
+ rescue => worker_exception
174
+ logger.warn "processing failed drone_id=#{@id}, message_id=#{message.message_id} exception=\"#{worker_exception.message}\", exception_backtrace=#{worker_exception.backtrace.join("|")}"
175
+
176
+ if auto_acknowledge
177
+ logger.info "despite failure, auto acknowledging message, drone_id=#{@id}, message_id=#{message.message_id}"
178
+ consumer.ack
179
+ end
180
+ end
181
+ end
182
+
183
+ def extract_underlying_message_body(message)
184
+ payload = message.raw_message.payload
185
+ if payload.json?
186
+ JSON.parse(payload.stringPayload)
187
+ elsif payload.binary?
188
+ payload.binaryPayload
189
+ elsif payload.string?
190
+ payload.stringPayload
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,53 @@
1
+ # Copyright (c) 2012, Groupon, Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions
6
+ # are met:
7
+ #
8
+ # Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ #
11
+ # Redistributions in binary form must reproduce the above copyright
12
+ # notice, this list of conditions and the following disclaimer in the
13
+ # documentation and/or other materials provided with the distribution.
14
+ #
15
+ # Neither the name of GROUPON nor the names of its contributors may be
16
+ # used to endorse or promote products derived from this software without
17
+ # specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20
+ # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21
+ # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22
+ # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23
+ # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
25
+ # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26
+ # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28
+ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+
31
+ require 'json'
32
+
33
+ module Messagebus
34
+ module Swarm
35
+ class Drone
36
+ ##
37
+ # Use this for easy testing that a messagebus message is received.
38
+ # Example config:
39
+ # -
40
+ # :destination: jms.topic.some_destination_you_want_to_debug
41
+ # :subscription_id: <some_subscription_id>
42
+ # :worker: Messagebus::Swarm::Drone::LoggingWorker
43
+ # :drones: 1
44
+ class LoggingWorker
45
+ def self.perform_on_destination(message, destination)
46
+ log_message = %|received a message. destination=#{destination}, message=#{message.inspect}|
47
+ Rails.logger.info(log_message) if defined?(Rails.logger)
48
+ puts log_message
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,68 @@
1
+ # Copyright (c) 2012, Groupon, Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions
6
+ # are met:
7
+ #
8
+ # Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ #
11
+ # Redistributions in binary form must reproduce the above copyright
12
+ # notice, this list of conditions and the following disclaimer in the
13
+ # documentation and/or other materials provided with the distribution.
14
+ #
15
+ # Neither the name of GROUPON nor the names of its contributors may be
16
+ # used to endorse or promote products derived from this software without
17
+ # specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20
+ # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21
+ # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22
+ # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23
+ # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
25
+ # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26
+ # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28
+ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+
31
+ module Messagebus
32
+ # :nodoc:all
33
+ module Validations
34
+ def valid_host?(string)
35
+ Messagebus::SERVER_REGEX.match(string)
36
+ end
37
+
38
+ def validate_destination_config(name, is_consumer = false, options = {})
39
+ raise InvalidDestination.new("destination name is nil") unless name
40
+
41
+ if is_consumer && name.match(/^jms.topic/) && options[:subscription_id].nil?
42
+ raise InvalidDestination.new("destination type TOPIC requires a subscription_id")
43
+ end
44
+ end
45
+
46
+ def validate_connection_config(host_params, options = {})
47
+ if options[:ack_type] &&
48
+ options[:ack_type] != Messagebus::ACK_TYPE_AUTO_CLIENT &&
49
+ options[:ack_type] != Messagebus::ACK_TYPE_CLIENT
50
+ raise InvalidAcknowledgementType.new(options[:ack_type])
51
+ end
52
+
53
+ if host_params.nil?
54
+ raise InvalidHost.new(host_params)
55
+ end
56
+
57
+ if host_params.is_a?(Array)
58
+ host_params.each do |string|
59
+ unless valid_host?(string)
60
+ raise InvalidHost.new("host should be defined as <host>:<port>, received: #{host_params}")
61
+ end
62
+ end
63
+ else
64
+ raise InvalidHost.new("host should be defined as <host>:<port>, received #{host_params}")
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,36 @@
1
+ # Copyright (c) 2012, Groupon, Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions
6
+ # are met:
7
+ #
8
+ # Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ #
11
+ # Redistributions in binary form must reproduce the above copyright
12
+ # notice, this list of conditions and the following disclaimer in the
13
+ # documentation and/or other materials provided with the distribution.
14
+ #
15
+ # Neither the name of GROUPON nor the names of its contributors may be
16
+ # used to endorse or promote products derived from this software without
17
+ # specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20
+ # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21
+ # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22
+ # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23
+ # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
25
+ # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26
+ # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28
+ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+
31
+ module Messagebus
32
+ MAJOR = 1
33
+ MINOR = 0
34
+ PATCH = 0
35
+ VERSION = "#{MAJOR}.#{MINOR}.#{PATCH}"
36
+ end
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require "#{File.dirname(__FILE__)}/lib/messagebus/version"
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Pradeep Jawahar", "Lin Zhao", "Erik Weathers"]
6
+ gem.email = ["pjawahar@groupon.com", "lin@groupon.com", "eweathers@groupon.com"]
7
+ gem.description = %q{Messagebus integration gem}
8
+ gem.summary = %q{Messagebus Client}
9
+ gem.homepage = "https://github.com/groupon/Message-Bus"
10
+
11
+ gem.executables = %w(messagebus_swarm)
12
+ gem.files = Dir['bin/*'] + Dir["lib/**/*.rb"] + Dir["vendor/**/*"] + %w(README.mediawiki Rakefile messagebus.gemspec)
13
+ gem.test_files = Dir["spec*/**/*.rb"]
14
+ gem.name = "mbus"
15
+ gem.require_paths = ["vendor/gems", "lib"]
16
+ gem.version = Messagebus::VERSION
17
+ gem.license = 'BSD'
18
+
19
+ gem.required_rubygems_version = ">= 1.3.6"
20
+
21
+ if RUBY_VERSION < "1.9"
22
+ # json is built into ruby 1.9
23
+ gem.add_dependency "json"
24
+ end
25
+ gem.add_dependency "thrift", "0.9.0"
26
+
27
+ gem.add_development_dependency "rspec"
28
+ gem.add_development_dependency "simplecov"
29
+ end
@@ -0,0 +1,157 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
+
3
+ describe Messagebus::Client do
4
+ before do
5
+ @config = {
6
+ "enable_auto_init_connections" => true,
7
+ "clusters" => [
8
+ {
9
+ "type" => "producer-cluster",
10
+ "address" => "localhost:61613",
11
+ "destinations" => [
12
+ "jms.queue.testQueue1",
13
+ "jms.topic.testTopic1"
14
+ ]
15
+ }
16
+ ]
17
+ }
18
+
19
+ @logger = mock(Logger, :info => true, :error => true, :debug => true, :warn => true)
20
+ Logger.stub(:new).and_return(@logger)
21
+
22
+ @cluster_map = mock(Messagebus::ClusterMap, :start => true, :stop => true)
23
+ Messagebus::ClusterMap.stub!(:new).and_return(@cluster_map)
24
+
25
+ end
26
+
27
+ describe "constructing a client" do
28
+ describe "when creating a singleton logger" do
29
+ it "creates a logger using tmp directory if configuration is omitted" do
30
+ Messagebus::Client.new(@config)
31
+ Messagebus::Client.logger.should == @logger
32
+ end
33
+
34
+ it "creates a logger with the supplied log_file" do
35
+ Logger.should_receive(:new).with("log/messagebus-client.log").and_return(@logger)
36
+ Messagebus::Client.new(@config.merge("log_file" => "log/messagebus-client.log"))
37
+ end
38
+ end
39
+
40
+ it "automatically starts the cluster if configured" do
41
+ @cluster_map.should_receive(:start)
42
+ Messagebus::Client.new(@config.merge("enable_auto_init_connections" => true)).start
43
+ end
44
+
45
+ it "automatically defaults to cluster not started" do
46
+ @cluster_map.should_not_receive(:start)
47
+ Messagebus::Client.new(@config)
48
+ end
49
+ end
50
+
51
+ describe ".start (class method)" do
52
+ it "auto closes the connection when a block is given" do
53
+ @cluster_map.should_receive(:start)
54
+ @cluster_map.should_receive(:stop)
55
+ Messagebus::Client.start(@config.merge("enable_auto_init_connections" => true)) {}
56
+ end
57
+
58
+ it "makes sure it stops if the block errors out" do
59
+ @cluster_map.should_receive(:start)
60
+ @cluster_map.should_receive(:stop)
61
+ proc do
62
+ Messagebus::Client.start(@config.merge("enable_auto_init_connections" => true)) do
63
+ raise "error123"
64
+ end
65
+ end.should raise_error("error123")
66
+ end
67
+
68
+ it "doesn't auto close if no block passed" do
69
+ @cluster_map.should_receive(:start)
70
+ @cluster_map.should_not_receive(:stop)
71
+ Messagebus::Client.start(@config.merge("enable_auto_init_connections" => true))
72
+
73
+ end
74
+ end
75
+
76
+ describe "#start" do
77
+ it "delegates to the cluster map start" do
78
+ @cluster_map.should_receive(:start)
79
+ @cluster_map.should_not_receive(:stop)
80
+ Messagebus::Client.new(@config.merge("enable_auto_init_connections" => true)).start
81
+ end
82
+ end
83
+
84
+ describe "#stop" do
85
+ it "delegates to the cluster map stop" do
86
+ @cluster_map.should_receive(:stop)
87
+ Messagebus::Client.new(@config).stop
88
+ end
89
+ end
90
+
91
+ describe "#logger" do
92
+ it "shares the logger across instances" do
93
+ Messagebus::Client.new(@config).logger.should be(Messagebus::Client.new(@config).logger)
94
+ end
95
+ end
96
+
97
+ describe "#publish" do
98
+ before do
99
+ @producer = mock(Messagebus::Producer, :publish => true)
100
+ @cluster_map.stub!(:find).and_return(@producer)
101
+ @producer.stub!(:started?).and_return(true)
102
+
103
+ @message = mock(Messagebus::Message, :create => true, :message_id => "ee4ae017f409dc3f4aad38dddeb7bf88")
104
+ Messagebus::Message.stub!(:create).and_return(@message)
105
+ end
106
+
107
+ it "creates a message from the provided object" do
108
+ Messagebus::Message.should_receive(:create).with({:benjamin => :franklins})
109
+ Messagebus::Client.new(@config).publish("jms.queue.Testqueue1", {:benjamin => :franklins})
110
+ end
111
+
112
+ it "requires an existing destination" do
113
+ @producer.should_not_receive(:publish)
114
+ lambda {
115
+ @cluster_map.stub!(:find).and_return(nil)
116
+ Messagebus::Client.new(@config).publish("jms.queue.testQueue1", {:benjamin => :franklins})
117
+ }.should raise_error(Messagebus::Client::InvalidDestinationError)
118
+ end
119
+
120
+ it "sends a safe message by default" do
121
+ @producer.should_receive(:publish)
122
+ Messagebus::Client.new(@config).publish("jms.queue.testQueue1", {:benjamin => :franklins})
123
+ end
124
+
125
+ it "sends an unsafe message by when the safe parameter is false" do
126
+ @producer.should_receive(:publish)
127
+ Messagebus::Client.new(@config).publish("jms.queue.testQueue1", {:benjamin => :franklins}, 0, false)
128
+ end
129
+
130
+ it "sends a message with headers" do
131
+ headers = {"priority" => 6}
132
+ @producer.should_receive(:publish).with("jms.queue.testQueue1", @message, headers, false)
133
+ Messagebus::Client.new(@config).publish("jms.queue.testQueue1", {:benjamin => :franklins}, 0, false, false, headers)
134
+ end
135
+ end
136
+
137
+ describe "#reload_config_on_interval" do
138
+ it "reloads config when the interval is reached" do
139
+ @cluster_map.should_receive(:update_config).with(@config)
140
+ Messagebus::Client.new(@config).reload_config_on_interval(@config,0)
141
+ end
142
+ end
143
+
144
+ describe "#headers" do
145
+ it "creates a header from current time and the delay milliseconds provided to publish" do
146
+ delay = 3
147
+ time = Time.now
148
+ Time.stub!(:now).and_return(time)
149
+
150
+ client = Messagebus::Client.new(@config)
151
+ client.headers(delay).should == {
152
+ Messagebus::Producer::SCHEDULED_DELIVERY_TIME_MS_HEADER => ((time.to_i * 1000) + delay).to_s
153
+ }
154
+ end
155
+ end
156
+ end
157
+