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.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/#.ruby-gemset# +0 -0
  3. data/.gitignore +22 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +92 -0
  8. data/Rakefile +1 -0
  9. data/bin/start_subscribers +14 -0
  10. data/config/pubsub.yml +17 -0
  11. data/honest_pubsub.gemspec +38 -0
  12. data/lib/honest_pubsub/cli.rb +94 -0
  13. data/lib/honest_pubsub/configuration.rb +38 -0
  14. data/lib/honest_pubsub/context.rb +70 -0
  15. data/lib/honest_pubsub/db_logger.rb +52 -0
  16. data/lib/honest_pubsub/exceptions/payload_error.rb +2 -0
  17. data/lib/honest_pubsub/logger.rb +49 -0
  18. data/lib/honest_pubsub/logging.rb +42 -0
  19. data/lib/honest_pubsub/message.rb +50 -0
  20. data/lib/honest_pubsub/middleware.rb +14 -0
  21. data/lib/honest_pubsub/publisher.rb +93 -0
  22. data/lib/honest_pubsub/railtie.rb +27 -0
  23. data/lib/honest_pubsub/server/client_queue_listener.rb +54 -0
  24. data/lib/honest_pubsub/server/client_worker.rb +86 -0
  25. data/lib/honest_pubsub/server/subscriber_server.rb +84 -0
  26. data/lib/honest_pubsub/server.rb +8 -0
  27. data/lib/honest_pubsub/subscriber.rb +69 -0
  28. data/lib/honest_pubsub/version.rb +3 -0
  29. data/lib/honest_pubsub.rb +46 -0
  30. data/spec/config.yml +20 -0
  31. data/spec/honest_pubsub/#subscriber_spec.rb# +9 -0
  32. data/spec/honest_pubsub/cli_spec.rb +145 -0
  33. data/spec/honest_pubsub/server/client_queue_listener_spec.rb +76 -0
  34. data/spec/honest_pubsub/server/client_worker_spec.rb +161 -0
  35. data/spec/honest_pubsub/subscriber_spec.rb +5 -0
  36. data/spec/logger_spec.rb +110 -0
  37. data/spec/message_spec.rb +65 -0
  38. data/spec/spec_helper.rb +49 -0
  39. 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,8 @@
1
+ module HonestPubsub
2
+ module Server
3
+ end
4
+ end
5
+
6
+ require 'honest_pubsub/server/client_queue_listener'
7
+ require 'honest_pubsub/server/client_worker'
8
+ require 'honest_pubsub/server/subscriber_server'
@@ -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,3 @@
1
+ module HonestPubsub
2
+ VERSION = "0.2.2"
3
+ 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,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe HonestPubsub::Subscriber do
4
+ subject { HonestPubsub::Subscriber.new( routing_key ) }
5
+
6
+ describe "#start" do
7
+
8
+ end
9
+ end
@@ -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