honest_pubsub 0.2.2
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.
- checksums.yaml +7 -0
- data/#.ruby-gemset# +0 -0
- data/.gitignore +22 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +92 -0
- data/Rakefile +1 -0
- data/bin/start_subscribers +14 -0
- data/config/pubsub.yml +17 -0
- data/honest_pubsub.gemspec +38 -0
- data/lib/honest_pubsub/cli.rb +94 -0
- data/lib/honest_pubsub/configuration.rb +38 -0
- data/lib/honest_pubsub/context.rb +70 -0
- data/lib/honest_pubsub/db_logger.rb +52 -0
- data/lib/honest_pubsub/exceptions/payload_error.rb +2 -0
- data/lib/honest_pubsub/logger.rb +49 -0
- data/lib/honest_pubsub/logging.rb +42 -0
- data/lib/honest_pubsub/message.rb +50 -0
- data/lib/honest_pubsub/middleware.rb +14 -0
- data/lib/honest_pubsub/publisher.rb +93 -0
- data/lib/honest_pubsub/railtie.rb +27 -0
- data/lib/honest_pubsub/server/client_queue_listener.rb +54 -0
- data/lib/honest_pubsub/server/client_worker.rb +86 -0
- data/lib/honest_pubsub/server/subscriber_server.rb +84 -0
- data/lib/honest_pubsub/server.rb +8 -0
- data/lib/honest_pubsub/subscriber.rb +69 -0
- data/lib/honest_pubsub/version.rb +3 -0
- data/lib/honest_pubsub.rb +46 -0
- data/spec/config.yml +20 -0
- data/spec/honest_pubsub/#subscriber_spec.rb# +9 -0
- data/spec/honest_pubsub/cli_spec.rb +145 -0
- data/spec/honest_pubsub/server/client_queue_listener_spec.rb +76 -0
- data/spec/honest_pubsub/server/client_worker_spec.rb +161 -0
- data/spec/honest_pubsub/subscriber_spec.rb +5 -0
- data/spec/logger_spec.rb +110 -0
- data/spec/message_spec.rb +65 -0
- data/spec/spec_helper.rb +49 -0
- metadata +259 -0
@@ -0,0 +1,93 @@
|
|
1
|
+
module HonestPubsub
|
2
|
+
class Publisher
|
3
|
+
attr_reader :publisher
|
4
|
+
attr_reader :channel
|
5
|
+
|
6
|
+
@@publisher = nil
|
7
|
+
|
8
|
+
def self.instance
|
9
|
+
if @@publisher.nil?
|
10
|
+
@@publisher = ::HonestPubsub::Publisher.new
|
11
|
+
end
|
12
|
+
@@publisher
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(exchange="honest")
|
16
|
+
@exchange = exchange
|
17
|
+
@disabled = false
|
18
|
+
@logger = ::HonestPubsub::Logger.new()
|
19
|
+
|
20
|
+
@config = Configuration.configuration
|
21
|
+
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def enable(value)
|
26
|
+
@disabled = !value
|
27
|
+
@disabled
|
28
|
+
end
|
29
|
+
|
30
|
+
def start
|
31
|
+
if !@config[:enabled].nil? && @config[:enabled] == false
|
32
|
+
@disabled = true
|
33
|
+
return
|
34
|
+
end
|
35
|
+
|
36
|
+
# grab server configuration from initialization file somewhere
|
37
|
+
begin
|
38
|
+
@connection = Bunny.new(Configuration.configuration[:connection])
|
39
|
+
@connection.start
|
40
|
+
|
41
|
+
@channel = @connection.create_channel
|
42
|
+
@publisher = @channel.topic(@exchange, :durable=>true, :auto_delete=>false)
|
43
|
+
|
44
|
+
rescue => e
|
45
|
+
Airbrake.notify(e, parameters: {message: e.message}, environment_name: ENV['RAILS_ENV'] )
|
46
|
+
return
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
@publisher.on_return do |return_info, properties, content|
|
51
|
+
# contents are already transformed into message that we want to send
|
52
|
+
@logger.failed_publish(return_info[:routing_key], properties, content)
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
def publish(context, key, payload, enabled = true)
|
58
|
+
routing_key = "#{@exchange}.#{key}"
|
59
|
+
envelope = ::HonestPubsub::Message.new.serialize(context, key, payload)
|
60
|
+
|
61
|
+
if @publisher.nil?
|
62
|
+
start
|
63
|
+
end
|
64
|
+
|
65
|
+
if @disabled || @publisher.nil? || !enabled
|
66
|
+
@logger.failed_publish(routing_key, {}, envelope)
|
67
|
+
else
|
68
|
+
tries = 2
|
69
|
+
begin
|
70
|
+
@publisher.publish(envelope.to_json, :persistent=>true, :mandatory=>true, :timestamp=>envelope[:ts], :content_type=>"application/json", :routing_key =>routing_key )
|
71
|
+
@logger.log_publish(routing_key, envelope)
|
72
|
+
rescue => e
|
73
|
+
tries -= 1
|
74
|
+
teardown
|
75
|
+
start
|
76
|
+
if tries > 0 && @publisher
|
77
|
+
retry
|
78
|
+
else
|
79
|
+
@logger.failed_publish(routing_key, {}, envelope)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
def teardown
|
88
|
+
@connection.close
|
89
|
+
@publisher = nil
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# This railtie provides Rails engine hooks
|
2
|
+
|
3
|
+
module HonestPubsub
|
4
|
+
class Railtie < ::Rails::Railtie
|
5
|
+
initializer "honest_pubsub.insert_middleware" do |app|
|
6
|
+
app.config.middleware.use HonestPubsub::Middleware
|
7
|
+
end
|
8
|
+
|
9
|
+
console do
|
10
|
+
# Set up basic context to identify messages getting generated from console
|
11
|
+
HonestPubsub::Context.setup_context(
|
12
|
+
application: "#{HonestPubsub::Configuration.application_name}/console",
|
13
|
+
hostname: `hostname`.strip,
|
14
|
+
unique_id: Digest::SHA1.new.to_s
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
rake_tasks do
|
19
|
+
# Set up basic context to identify messages getting generated from rake tasks
|
20
|
+
HonestPubsub::Context.setup_context(
|
21
|
+
application: "#{HonestPubsub::Configuration.application_name}/rake",
|
22
|
+
hostname: `hostname`.strip,
|
23
|
+
unique_id: Digest::SHA1.new.to_s
|
24
|
+
)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'celluloid/autostart'
|
3
|
+
require 'celluloid/io'
|
4
|
+
require 'honest_pubsub'
|
5
|
+
|
6
|
+
module HonestPubsub
|
7
|
+
module Server
|
8
|
+
class ClientQueueListener
|
9
|
+
include Celluloid
|
10
|
+
|
11
|
+
attr_reader :worker_class
|
12
|
+
|
13
|
+
def initialize(worker_class, request_key, queue, durable = true, topic = "honest")
|
14
|
+
@topic = topic
|
15
|
+
@request_key = request_key
|
16
|
+
@worker_class = worker_class
|
17
|
+
@queue_name = queue
|
18
|
+
@durable = durable
|
19
|
+
@subscriber = ::HonestPubsub::Subscriber.new(@request_key, @durable, @topic)
|
20
|
+
end
|
21
|
+
|
22
|
+
def start
|
23
|
+
@subscriber.start(@queue_name, false) do |delivery_info, properties, envelope|
|
24
|
+
message_received(delivery_info, properties, envelope)
|
25
|
+
true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def shutdown
|
30
|
+
@subscriber.teardown if @subscriber.present?
|
31
|
+
# TODO -thl
|
32
|
+
# This the kosher thing to do in ruby?
|
33
|
+
teardown
|
34
|
+
end
|
35
|
+
|
36
|
+
def message_received(delivery_info, properties, envelope)
|
37
|
+
begin
|
38
|
+
HonestPubsub.logger.debug( "Message recieved by listener with payload: #{envelope[:payload]}" )
|
39
|
+
worker = "#{@worker_class}".constantize.new(delivery_info, properties)
|
40
|
+
raise PayloadError.new("Invalid Payload") unless worker.valid_payload?(envelope[:payload])
|
41
|
+
HonestPubsub.logger.debug( "Calling worker perform on message" )
|
42
|
+
worker.perform(envelope[:context], envelope[:payload])
|
43
|
+
rescue => e
|
44
|
+
#puts "Error in worker #{e.message}!!!"
|
45
|
+
Airbrake.notify("Failed perform for #{self.class.name}", params: {info: delivery_info, properties: properties, envelope: envelope.to_hash } )
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
def teardown
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'active_support/core_ext'
|
3
|
+
|
4
|
+
module HonestPubsub
|
5
|
+
module Server
|
6
|
+
class ClientWorker
|
7
|
+
@@registered_subscribers = []
|
8
|
+
class_attribute :payload_validators, :error_handlers, :subscribed_key, :subscribed_queue
|
9
|
+
attr_accessor :delivery_routing_data, :delivery_properties
|
10
|
+
|
11
|
+
def self.inherited(klass)
|
12
|
+
@@registered_subscribers << klass
|
13
|
+
end
|
14
|
+
|
15
|
+
# Specify the routing key that the subscriber class should listen to.
|
16
|
+
# @param [String] routing_key_name The routing key to subscribe to. Must be characters only separated by periods (.)
|
17
|
+
# @param [Hash] options Allowed option is :on to optionally specify a queue. If not provided, queue name is generated from the routing key
|
18
|
+
def self.subscribe_to(routing_key_name, options = {})
|
19
|
+
options.assert_valid_keys(:on)
|
20
|
+
unless validate_routing_key_name(routing_key_name)
|
21
|
+
raise ArgumentError.new("#{routing_key_name} is not supported. Only lower case characters separated by periods are allowed.")
|
22
|
+
end
|
23
|
+
self.subscribed_key = routing_key_name
|
24
|
+
self.subscribed_queue = generated_queue_name(routing_key_name, options[:on])
|
25
|
+
end
|
26
|
+
|
27
|
+
# Sets the validator for payload
|
28
|
+
#
|
29
|
+
# @param validator The validator to use for validating the payload.
|
30
|
+
# Returns false if the payload is not valid.
|
31
|
+
# Proc must accept a payload as an argument.
|
32
|
+
def self.validates_payload_with(*validators)
|
33
|
+
self.payload_validators ||= []
|
34
|
+
self.payload_validators += validators
|
35
|
+
#
|
36
|
+
# executable = if validator.class <= Proc then
|
37
|
+
# validator
|
38
|
+
# elsif validator.class == Symbol
|
39
|
+
# self.method(validator)
|
40
|
+
# end
|
41
|
+
# self.payload_validators.push executable
|
42
|
+
end
|
43
|
+
|
44
|
+
# Sets an error handler for the class
|
45
|
+
def self.handle_errors_with(handler)
|
46
|
+
error_handler = handler
|
47
|
+
end
|
48
|
+
|
49
|
+
def initialize(delivery_routing_data, delivery_properties)
|
50
|
+
@delivery_routing_data = delivery_routing_data
|
51
|
+
@delivery_properties = delivery_properties
|
52
|
+
end
|
53
|
+
|
54
|
+
# Need to have a real perform method for the message to be processed.
|
55
|
+
def perform(payload)
|
56
|
+
raise "Need implementation for your worker."
|
57
|
+
end
|
58
|
+
|
59
|
+
def routing_key
|
60
|
+
delivery_routing_data[:routing_key]
|
61
|
+
end
|
62
|
+
|
63
|
+
# Iterates over all the payload validators and returns false if any of them are false
|
64
|
+
def valid_payload?(payload)
|
65
|
+
return true unless payload_validators.present?
|
66
|
+
|
67
|
+
payload_validators.inject(true) { |is_valid, validator|
|
68
|
+
is_valid && (validator.respond_to?(:call) ? validator.call(payload) : send(validator, payload) )
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def self.validate_routing_key_name(key)
|
75
|
+
return true if key.blank?
|
76
|
+
key.match(/\A([a-z]+\.?)*([a-z]+)\Z/).present?
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.generated_queue_name(routing_key, queue_name)
|
80
|
+
return queue_name if queue_name.present?
|
81
|
+
[ HonestPubsub::Configuration.application_name.to_s.gsub(/[^\w\_]/, ''), routing_key.gsub(".", '_') ].reject(&:blank?).join('_')
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'celluloid/autostart'
|
3
|
+
require 'celluloid/io'
|
4
|
+
require 'awesome_print'
|
5
|
+
require 'active_support/all'
|
6
|
+
require 'optparse'
|
7
|
+
require 'fileutils'
|
8
|
+
|
9
|
+
require_relative './client_queue_listener'
|
10
|
+
|
11
|
+
# In order to use the server, the caller must be able to bring in the necessary
|
12
|
+
# classes for require before instantiating the instance.
|
13
|
+
|
14
|
+
module HonestPubsub
|
15
|
+
module Server
|
16
|
+
|
17
|
+
class SubscriberServer
|
18
|
+
include Celluloid
|
19
|
+
|
20
|
+
def initialize(subscribers)
|
21
|
+
@workers = subscribers.map { |subscriber| create_queue_listeners(subscriber) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def start
|
25
|
+
@workers.each do |worker|
|
26
|
+
puts "Starting worker: #{worker.worker_class.name}"
|
27
|
+
worker.start
|
28
|
+
end
|
29
|
+
|
30
|
+
thread = Thread.current
|
31
|
+
interrupts = ["HUP", "INT", "QUIT", "ABRT", "TERM"]
|
32
|
+
interrupts.each do |signal_name|
|
33
|
+
Signal.trap(signal_name) {
|
34
|
+
puts "Processing #{signal_name}"
|
35
|
+
thread.run
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
Thread.stop
|
40
|
+
|
41
|
+
::HonestPubsub::Logger.new.log_service("all_services", :warn, "Starting shutdown of all services")
|
42
|
+
|
43
|
+
@workers.each do |worker|
|
44
|
+
::HonestPubsub::Logger.new.log_service("all_services", :warn, "Tearing down worker: #{worker.worker_class.name}")
|
45
|
+
begin
|
46
|
+
STDOUT.puts "Tearing down subscriber for #{worker.worker_class.name}"
|
47
|
+
worker.shutdown
|
48
|
+
rescue => e
|
49
|
+
::HonestPubsub::Logger.new.log_service("all_services", :warn, "#{worker.worker_class.name} - did not tear down correctly. Error - #{e.message}")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
ensure
|
53
|
+
HonestPubsub::CLI.instance.remove_pid
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def warn(message)
|
59
|
+
old_behavior = ActiveSupport::Deprecation.behavior
|
60
|
+
ActiveSupport::Deprecation.behavior = [:stderr, :log]
|
61
|
+
ActiveSupport::Deprecation.warn(message)
|
62
|
+
ensure
|
63
|
+
ActiveSupport::Deprecation.behavior = old_behavior
|
64
|
+
end
|
65
|
+
|
66
|
+
def create_queue_listeners(subscriber)
|
67
|
+
routing_key = subscriber.subscribed_key
|
68
|
+
subscribed_queue_name = subscriber.subscribed_queue
|
69
|
+
|
70
|
+
if routing_key.blank?
|
71
|
+
raise ArgumentError.new("Routing key must be provided in #{subscriber.name} using `subscribe_to routing_key`")
|
72
|
+
end
|
73
|
+
|
74
|
+
if subscribed_queue_name.blank?
|
75
|
+
raise ArgumentError.new("Queue Name must be provided in #{subscriber.name} using `subscribe_to routing_key, on: queue_name`")
|
76
|
+
end
|
77
|
+
|
78
|
+
STDOUT.puts "Setting up listener for request_key: #{routing_key} and queue:#{subscribed_queue_name}"
|
79
|
+
ClientQueueListener.new(subscriber, routing_key, subscribed_queue_name)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module HonestPubsub
|
2
|
+
class Subscriber
|
3
|
+
attr_reader :listener
|
4
|
+
attr_reader :exchange
|
5
|
+
attr_reader :channel
|
6
|
+
|
7
|
+
def initialize(routing_key, durable = true, topic="honest")
|
8
|
+
@initial_key = routing_key
|
9
|
+
@durable = durable
|
10
|
+
@topic = topic
|
11
|
+
|
12
|
+
if @initial_key.present?
|
13
|
+
@routing_key = "#{@topic}.#{@initial_key}.#"
|
14
|
+
else
|
15
|
+
@routing_key = "#{@topic}.#"
|
16
|
+
end
|
17
|
+
@logger = ::HonestPubsub::Logger.new
|
18
|
+
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
# name - used to ensure that certain consumers are actually listening to an exchange
|
23
|
+
# pass in a lambda for this method to work. We might only want to expose the content instead of
|
24
|
+
# all 3 chunks.
|
25
|
+
def start(name, blocking=false)
|
26
|
+
@connection = Bunny.new(Configuration.configuration[:connection])
|
27
|
+
begin
|
28
|
+
@connection.start
|
29
|
+
rescue => e
|
30
|
+
Airbrake.notify("RabbitMQ unreachable!", params: { message: e.message}, environment_name: ENV['RAILS_ENV'] )
|
31
|
+
raise e
|
32
|
+
end
|
33
|
+
|
34
|
+
@channel = @connection.create_channel
|
35
|
+
@exchange = @channel.topic(@topic, :durable=>@durable, :auto_delete=>false)
|
36
|
+
|
37
|
+
# FIX!!! -thl
|
38
|
+
# Need to ensure that the ids for a server will be reproducible in case a server
|
39
|
+
# goes down and has to get restarted.
|
40
|
+
if @initial_key.present?
|
41
|
+
@queue = "#{@initial_key}.#{name}"
|
42
|
+
else
|
43
|
+
@queue = "#{name}"
|
44
|
+
end
|
45
|
+
|
46
|
+
queue_arguments = {}
|
47
|
+
queue_arguments["x-dead-letter-exchange"] = Configuration.configuration[:dead_letter] if Configuration.configuration[:dead_letter].present?
|
48
|
+
@listener = @channel.queue(@queue, :arguments=>queue_arguments ).bind(@exchange, :routing_key => @routing_key, :exclusive=>false)
|
49
|
+
# Parameters for subscribe that might be useful:
|
50
|
+
# :block=>true - Used for long running consumer applications. (backend servers?)
|
51
|
+
@consumer = @listener.subscribe(:consumer_tag=>name, :block=>blocking)
|
52
|
+
@consumer.on_delivery do |delivery_info, properties, contents|
|
53
|
+
HonestPubsub.logger.debug( "Message delivery with contents: #{contents}")
|
54
|
+
if delivery_info[:redelivered]
|
55
|
+
Airbrake.notify("PubSub Message redelivery", params: {info: delivery_info, props: properties, contents: contents}, environment_name: ENV['RAILS_ENV'] )
|
56
|
+
end
|
57
|
+
message = ::HonestPubsub::Message.new.parse(contents)
|
58
|
+
@logger.log_receive(delivery_info[:routing_key], message)
|
59
|
+
yield delivery_info, properties, message
|
60
|
+
true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def teardown
|
65
|
+
@consumer.cancel if @consumer.present?
|
66
|
+
@connection.close if @connection.present?
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require "active_support/all"
|
2
|
+
require "bunny"
|
3
|
+
require "yaml"
|
4
|
+
require "airbrake"
|
5
|
+
|
6
|
+
require "honest_pubsub/configuration"
|
7
|
+
require "honest_pubsub/context"
|
8
|
+
require "honest_pubsub/db_logger"
|
9
|
+
require "honest_pubsub/logger"
|
10
|
+
require "honest_pubsub/logging"
|
11
|
+
require "honest_pubsub/message"
|
12
|
+
require "honest_pubsub/publisher"
|
13
|
+
require "honest_pubsub/subscriber"
|
14
|
+
require "honest_pubsub/version"
|
15
|
+
require "honest_pubsub/exceptions/payload_error"
|
16
|
+
require 'honest_pubsub/server'
|
17
|
+
|
18
|
+
if defined?(Rails::Railtie)
|
19
|
+
require "honest_pubsub/middleware"
|
20
|
+
require "honest_pubsub/railtie"
|
21
|
+
end
|
22
|
+
|
23
|
+
module HonestPubsub
|
24
|
+
|
25
|
+
def self.root
|
26
|
+
File.expand_path '../..', __FILE__
|
27
|
+
end
|
28
|
+
|
29
|
+
# This method publishes payload to rabbitmq. All listeners with appropriate
|
30
|
+
# routing keys will receive the payload.
|
31
|
+
# @param [String] routing_key Identifier of the message type
|
32
|
+
# @param [Hash] payload The data that will be passed to the subscriber
|
33
|
+
|
34
|
+
def self.publish(routing_key, payload)
|
35
|
+
Publisher.instance.publish(HonestPubsub::Context.instance, routing_key, payload)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.logger
|
39
|
+
HonestPubsub::Logging.logger
|
40
|
+
end
|
41
|
+
|
42
|
+
# @param [Logger] logger Logger used by HonestPubsub
|
43
|
+
def self.logger=(logger)
|
44
|
+
HonestPubsub::Logging.logger = logger
|
45
|
+
end
|
46
|
+
end
|
data/spec/config.yml
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
test:
|
2
|
+
# Documentation for parameters for rabbit and bunny is located here: http://rubybunny.info/articles/connecting.html
|
3
|
+
connection:
|
4
|
+
host: localhost
|
5
|
+
port: 5672
|
6
|
+
# username: rabbit
|
7
|
+
# password: rabbit
|
8
|
+
heartbeat: 60 # in seconds
|
9
|
+
log_level: 0
|
10
|
+
log_file: test_rabbit.log
|
11
|
+
network_recovery_interval: 10 # in seconds
|
12
|
+
continuation_timeout: 4000 # in milliseconds
|
13
|
+
|
14
|
+
logger:
|
15
|
+
enabled: true
|
16
|
+
level: warn
|
17
|
+
file: test_pubsub.log
|
18
|
+
|
19
|
+
|
20
|
+
pid: pids/honest_pubsub
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'honest_pubsub/cli'
|
3
|
+
|
4
|
+
describe HonestPubsub::CLI do
|
5
|
+
let(:cli) { HonestPubsub::CLI.instance }
|
6
|
+
after(:each) do
|
7
|
+
cli.instance_variable_set(:@subscribers, nil)
|
8
|
+
cli.instance_variable_set(:@pidfile, nil)
|
9
|
+
cli.instance_variable_set(:@require_path, nil)
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#parse' do
|
13
|
+
let(:options) { [] }
|
14
|
+
before { cli.parse(options) }
|
15
|
+
|
16
|
+
context "pidfile" do
|
17
|
+
it "works when nothing is passed" do
|
18
|
+
expect(cli.pidfile).to be_nil
|
19
|
+
end
|
20
|
+
|
21
|
+
context "passed in as -P" do
|
22
|
+
let(:options) { [ "-P", "mypidfile.pid"]}
|
23
|
+
it "parses" do
|
24
|
+
expect(cli.pidfile).to eq('mypidfile.pid')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "passed in as --pidfile" do
|
29
|
+
let(:options) { [ "--pidfile", "mypidfile.pid"]}
|
30
|
+
it "parses" do
|
31
|
+
expect(cli.pidfile).to eq('mypidfile.pid')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "only" do
|
37
|
+
it "works when nothing is passed" do
|
38
|
+
expect(cli.subscribers).to be_nil
|
39
|
+
end
|
40
|
+
|
41
|
+
context "passed in as -o" do
|
42
|
+
let(:options) { [ "-o", "Foobar,Doobar"]}
|
43
|
+
it "parses" do
|
44
|
+
expect(cli.subscribers).to eq([ 'Foobar', 'Doobar'])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "passed in as --only" do
|
49
|
+
let(:options) { [ "--only", "Foobar,Doobar"]}
|
50
|
+
it "parses" do
|
51
|
+
expect(cli.subscribers).to eq([ 'Foobar', 'Doobar'])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context "require" do
|
57
|
+
it "works when nothing is passed" do
|
58
|
+
expect(cli.require_path).to eq(".")
|
59
|
+
end
|
60
|
+
|
61
|
+
context "passed in as -r" do
|
62
|
+
let(:options) { [ "-r", "/path/to/my/file"]}
|
63
|
+
it "parses" do
|
64
|
+
expect(cli.require_path).to eq("/path/to/my/file")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context "passed in as --require" do
|
69
|
+
let(:options) { [ "--require", "/path/to/my/file"]}
|
70
|
+
it "parses" do
|
71
|
+
expect(cli.require_path).to eq("/path/to/my/file")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe '#run' do
|
78
|
+
describe "high-level steps to be completed" do
|
79
|
+
before {
|
80
|
+
allow(cli).to receive(:load_environment)
|
81
|
+
allow(cli).to receive(:write_pidfile)
|
82
|
+
allow(cli).to receive(:load_subscribers)
|
83
|
+
cli.run
|
84
|
+
}
|
85
|
+
|
86
|
+
it "runs" do
|
87
|
+
expect(cli).to have_received(:load_environment)
|
88
|
+
expect(cli).to have_received(:write_pidfile)
|
89
|
+
expect(cli).to have_received(:load_subscribers)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe '#write_pidfile' do
|
95
|
+
before {
|
96
|
+
cli.pidfile = pidfile
|
97
|
+
allow(File).to receive(:open)
|
98
|
+
cli.send(:write_pidfile)
|
99
|
+
}
|
100
|
+
context "no pidfile passed" do
|
101
|
+
let(:pidfile) { nil }
|
102
|
+
it "doesn't create a pidfile" do
|
103
|
+
expect(File).to_not have_received(:open)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context "pidfile path is supplied" do
|
108
|
+
let(:pidfile) { "/path/to/pidfile" }
|
109
|
+
it 'creates file' do
|
110
|
+
expect(File).to have_received(:open).with(pidfile, 'w')
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe '#load_subscribers' do
|
116
|
+
let(:subscribers) { nil }
|
117
|
+
let(:subscriber_server) { double('subscriber_server')}
|
118
|
+
before {
|
119
|
+
cli.subscribers = subscribers
|
120
|
+
allow(subscriber_server).to receive(:start)
|
121
|
+
allow(HonestPubsub::Server::SubscriberServer).to receive(:new).and_return(subscriber_server)
|
122
|
+
cli.send(:load_subscribers)
|
123
|
+
}
|
124
|
+
|
125
|
+
context "No subscribers passed through CLI" do
|
126
|
+
let(:all_subscribers) { HonestPubsub::Server::ClientWorker.class_variable_get(:@@registered_subscribers) }
|
127
|
+
it "loads all subscribers" do
|
128
|
+
expect(all_subscribers).to include(MyTestSubscriber1)
|
129
|
+
expect(all_subscribers).to include(MyTestSubscriber2)
|
130
|
+
expect(all_subscribers).to include(MyTestSubscriber3)
|
131
|
+
expect(HonestPubsub::Server::SubscriberServer).to have_received(:new).with(all_subscribers)
|
132
|
+
expect(subscriber_server).to have_received(:start)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
context "Subscribers passed through CLI" do
|
137
|
+
let(:subscribers) { ['MyTestSubscriber1', 'MyTestSubscriber2'] }
|
138
|
+
it "loads all subscribers" do
|
139
|
+
expect(HonestPubsub::Server::SubscriberServer).to have_received(:new).with([MyTestSubscriber1, MyTestSubscriber2])
|
140
|
+
expect(subscriber_server).to have_received(:start)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|