honest_pubsub 0.2.2

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