honest_pubsub 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|