dkastner-hutch 0.17.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/.travis.yml +7 -0
  4. data/CHANGELOG.md +397 -0
  5. data/Gemfile +22 -0
  6. data/Guardfile +5 -0
  7. data/LICENSE +22 -0
  8. data/README.md +315 -0
  9. data/Rakefile +14 -0
  10. data/bin/hutch +8 -0
  11. data/circle.yml +3 -0
  12. data/examples/consumer.rb +13 -0
  13. data/examples/producer.rb +10 -0
  14. data/hutch.gemspec +24 -0
  15. data/lib/hutch/broker.rb +356 -0
  16. data/lib/hutch/cli.rb +205 -0
  17. data/lib/hutch/config.rb +121 -0
  18. data/lib/hutch/consumer.rb +66 -0
  19. data/lib/hutch/error_handlers/airbrake.rb +26 -0
  20. data/lib/hutch/error_handlers/honeybadger.rb +28 -0
  21. data/lib/hutch/error_handlers/logger.rb +16 -0
  22. data/lib/hutch/error_handlers/sentry.rb +23 -0
  23. data/lib/hutch/error_handlers.rb +8 -0
  24. data/lib/hutch/exceptions.rb +7 -0
  25. data/lib/hutch/logging.rb +32 -0
  26. data/lib/hutch/message.rb +33 -0
  27. data/lib/hutch/tracers/newrelic.rb +19 -0
  28. data/lib/hutch/tracers/null_tracer.rb +15 -0
  29. data/lib/hutch/tracers.rb +6 -0
  30. data/lib/hutch/version.rb +4 -0
  31. data/lib/hutch/worker.rb +110 -0
  32. data/lib/hutch.rb +60 -0
  33. data/spec/hutch/broker_spec.rb +325 -0
  34. data/spec/hutch/cli_spec.rb +80 -0
  35. data/spec/hutch/config_spec.rb +126 -0
  36. data/spec/hutch/consumer_spec.rb +130 -0
  37. data/spec/hutch/error_handlers/airbrake_spec.rb +34 -0
  38. data/spec/hutch/error_handlers/honeybadger_spec.rb +36 -0
  39. data/spec/hutch/error_handlers/logger_spec.rb +15 -0
  40. data/spec/hutch/error_handlers/sentry_spec.rb +20 -0
  41. data/spec/hutch/logger_spec.rb +28 -0
  42. data/spec/hutch/message_spec.rb +38 -0
  43. data/spec/hutch/worker_spec.rb +98 -0
  44. data/spec/hutch_spec.rb +87 -0
  45. data/spec/spec_helper.rb +35 -0
  46. metadata +187 -0
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hutch::ErrorHandlers::Honeybadger do
4
+ let(:error_handler) { Hutch::ErrorHandlers::Honeybadger.new }
5
+
6
+ describe '#handle' do
7
+ let(:error) do
8
+ begin
9
+ raise "Stuff went wrong"
10
+ rescue RuntimeError => err
11
+ err
12
+ end
13
+ end
14
+
15
+ it "logs the error to Honeybadger" do
16
+ message_id = "1"
17
+ payload = "{}"
18
+ consumer = double
19
+ ex = error
20
+ message = {
21
+ :error_class => ex.class.name,
22
+ :error_message => "#{ ex.class.name }: #{ ex.message }",
23
+ :backtrace => ex.backtrace,
24
+ :context => {
25
+ :message_id => message_id,
26
+ :consumer => consumer
27
+ },
28
+ :parameters => {
29
+ :payload => payload
30
+ }
31
+ }
32
+ expect(::Honeybadger).to receive(:notify_or_ignore).with(message)
33
+ error_handler.handle(message_id, payload, consumer, ex)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hutch::ErrorHandlers::Logger do
4
+ let(:error_handler) { Hutch::ErrorHandlers::Logger.new }
5
+
6
+ describe '#handle' do
7
+ let(:error) { double(message: "Stuff went wrong", class: "RuntimeError",
8
+ backtrace: ["line 1", "line 2"]) }
9
+
10
+ it "logs three separate lines" do
11
+ expect(Hutch::Logging.logger).to receive(:error).exactly(3).times
12
+ error_handler.handle("1", "{}", double, error)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hutch::ErrorHandlers::Sentry do
4
+ let(:error_handler) { Hutch::ErrorHandlers::Sentry.new }
5
+
6
+ describe '#handle' do
7
+ let(:error) do
8
+ begin
9
+ raise "Stuff went wrong"
10
+ rescue RuntimeError => err
11
+ err
12
+ end
13
+ end
14
+
15
+ it "logs the error to Sentry" do
16
+ expect(Raven).to receive(:capture_exception).with(error)
17
+ error_handler.handle("1", "{}", double, error)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hutch::Logging do
4
+ let(:dummy_object) do
5
+ class DummyObject
6
+ include Hutch::Logging
7
+ end
8
+ end
9
+
10
+ describe '#logger' do
11
+ context 'with the default logger' do
12
+ subject { Hutch::Logging.logger }
13
+
14
+ it { is_expected.to be_instance_of(Logger) }
15
+ end
16
+
17
+ context 'with a custom logger' do
18
+ let(:dummy_logger) { double("Dummy logger", warn: true, info: true) }
19
+ after { Hutch::Logging.setup_logger }
20
+
21
+ it "users the custom logger" do
22
+ Hutch::Logging.logger = dummy_logger
23
+ expect(Hutch::Logging.logger).to eq(dummy_logger)
24
+ end
25
+ end
26
+ end
27
+ end
28
+
@@ -0,0 +1,38 @@
1
+ require 'hutch/message'
2
+
3
+ describe Hutch::Message do
4
+ let(:delivery_info) { double('Delivery Info') }
5
+ let(:props) { double('Properties') }
6
+ let(:body) {{ foo: 'bar' }.with_indifferent_access}
7
+ let(:json_body) { MultiJson.dump(body) }
8
+ subject(:message) { Hutch::Message.new(delivery_info, props, json_body) }
9
+
10
+ describe '#body' do
11
+ subject { super().body }
12
+ it { is_expected.to eq(body) }
13
+ end
14
+
15
+ describe '[]' do
16
+ subject { message[:foo] }
17
+ it { is_expected.to eq('bar') }
18
+ end
19
+
20
+ [:message_id, :timestamp].each do |method|
21
+ describe method.to_s do
22
+ it 'delegates to @properties' do
23
+ expect(props).to receive(method)
24
+ message.send(method)
25
+ end
26
+ end
27
+ end
28
+
29
+ [:routing_key, :exchange].each do |method|
30
+ describe method.to_s do
31
+ it 'delegates to @delivery_info' do
32
+ expect(delivery_info).to receive(method)
33
+ message.send(method)
34
+ end
35
+ end
36
+ end
37
+ end
38
+
@@ -0,0 +1,98 @@
1
+ require 'spec_helper'
2
+ require 'hutch/worker'
3
+
4
+ describe Hutch::Worker do
5
+ let(:consumer) { double('Consumer', routing_keys: %w( a b c ),
6
+ get_queue_name: 'consumer', get_arguments: {}) }
7
+ let(:consumers) { [consumer, double('Consumer')] }
8
+ let(:broker) { Hutch::Broker.new }
9
+ subject(:worker) { Hutch::Worker.new(broker, consumers) }
10
+
11
+ describe '#setup_queues' do
12
+ it 'sets up queues for each of the consumers' do
13
+ consumers.each do |consumer|
14
+ expect(worker).to receive(:setup_queue).with(consumer)
15
+ end
16
+ worker.setup_queues
17
+ end
18
+ end
19
+
20
+ describe '#setup_queue' do
21
+ let(:queue) { double('Queue', bind: nil, subscribe: nil) }
22
+ before { allow(worker).to receive_messages(consumer_queue: queue) }
23
+ before { allow(broker).to receive_messages(queue: queue, bind_queue: nil) }
24
+
25
+ it 'creates a queue' do
26
+ expect(broker).to receive(:queue).with(consumer.get_queue_name, consumer.get_arguments).and_return(queue)
27
+ worker.setup_queue(consumer)
28
+ end
29
+
30
+ it 'binds the queue to each of the routing keys' do
31
+ expect(broker).to receive(:bind_queue).with(queue, %w( a b c ))
32
+ worker.setup_queue(consumer)
33
+ end
34
+
35
+ it 'sets up a subscription' do
36
+ expect(queue).to receive(:subscribe).with(manual_ack: true)
37
+ worker.setup_queue(consumer)
38
+ end
39
+ end
40
+
41
+ describe '#handle_message' do
42
+ let(:payload) { '{}' }
43
+ let(:consumer_instance) { double('Consumer instance') }
44
+ let(:delivery_info) { double('Delivery Info', routing_key: '',
45
+ delivery_tag: 'dt') }
46
+ let(:properties) { double('Properties', message_id: nil) }
47
+ before { allow(consumer).to receive_messages(new: consumer_instance) }
48
+ before { allow(broker).to receive(:ack) }
49
+ before { allow(broker).to receive(:nack) }
50
+ before { allow(consumer_instance).to receive(:broker=) }
51
+ before { allow(consumer_instance).to receive(:delivery_info=) }
52
+
53
+ it 'passes the message to the consumer' do
54
+ expect(consumer_instance).to receive(:process).
55
+ with(an_instance_of(Hutch::Message))
56
+ worker.handle_message(consumer, delivery_info, properties, payload)
57
+ end
58
+
59
+ it 'acknowledges the message' do
60
+ allow(consumer_instance).to receive(:process)
61
+ expect(broker).to receive(:ack).with(delivery_info.delivery_tag)
62
+ worker.handle_message(consumer, delivery_info, properties, payload)
63
+ end
64
+
65
+ context 'when the consumer raises an exception' do
66
+ before { allow(consumer_instance).to receive(:process).and_raise('a consumer error') }
67
+
68
+ it 'logs the error' do
69
+ Hutch::Config[:error_handlers].each do |backend|
70
+ expect(backend).to receive(:handle)
71
+ end
72
+ worker.handle_message(consumer, delivery_info, properties, payload)
73
+ end
74
+
75
+ it 'rejects the message' do
76
+ expect(broker).to receive(:nack).with(delivery_info.delivery_tag)
77
+ worker.handle_message(consumer, delivery_info, properties, payload)
78
+ end
79
+ end
80
+
81
+ context "when the payload is not valid json" do
82
+ let(:payload) { "Not Valid JSON" }
83
+
84
+ it 'logs the error' do
85
+ Hutch::Config[:error_handlers].each do |backend|
86
+ expect(backend).to receive(:handle)
87
+ end
88
+ worker.handle_message(consumer, delivery_info, properties, payload)
89
+ end
90
+
91
+ it 'rejects the message' do
92
+ expect(broker).to receive(:nack).with(delivery_info.delivery_tag)
93
+ worker.handle_message(consumer, delivery_info, properties, payload)
94
+ end
95
+ end
96
+ end
97
+ end
98
+
@@ -0,0 +1,87 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hutch do
4
+ describe '.register_consumer' do
5
+ let(:consumer_a) { double('Consumer') }
6
+ let(:consumer_b) { double('Consumer') }
7
+
8
+ it 'saves the consumers in the global consumer list' do
9
+ Hutch.register_consumer(consumer_a)
10
+ Hutch.register_consumer(consumer_b)
11
+ expect(Hutch.consumers).to include consumer_a
12
+ expect(Hutch.consumers).to include consumer_b
13
+ end
14
+ end
15
+
16
+ describe '.connect' do
17
+ context 'not connected' do
18
+ let(:options) { double 'options' }
19
+ let(:config) { double 'config' }
20
+ let(:broker) { double 'broker' }
21
+ let(:action) { Hutch.connect(options, config) }
22
+
23
+ it 'passes options and config' do
24
+ expect(Hutch::Broker).to receive(:new).with(config).and_return broker
25
+ expect(broker).to receive(:connect).with options
26
+
27
+ action
28
+ end
29
+
30
+ end
31
+
32
+ context 'connected' do
33
+ before { allow(Hutch).to receive(:connected?).and_return true }
34
+
35
+ it 'does not reconnect' do
36
+ expect(Hutch::Broker).not_to receive :new
37
+ Hutch.connect
38
+ end
39
+ end
40
+ end
41
+
42
+ describe '.connected?' do
43
+ subject(:connected?) { Hutch.connected? }
44
+
45
+ before { allow(Hutch).to receive(:broker).and_return(broker) }
46
+
47
+ context 'without a broker' do
48
+ let(:broker) { nil }
49
+
50
+ it { expect(connected?).to be_falsey }
51
+ end
52
+
53
+ context 'without a connection' do
54
+ let(:connection) { nil }
55
+ let(:broker) { double(:broker, connection: connection) }
56
+
57
+ it { expect(connected?).to be_falsey }
58
+ end
59
+
60
+ context 'with a closed connection' do
61
+ let(:connection) { double(:connection, open?: false) }
62
+ let(:broker) { double(:broker, connection: connection) }
63
+
64
+ it { expect(connected?).to be_falsey }
65
+ end
66
+
67
+ context 'with an opened connection' do
68
+ let(:connection) { double(:connection, open?: true) }
69
+ let(:broker) { double(:broker, connection: connection) }
70
+
71
+ it { expect(connected?).to be_truthy }
72
+ end
73
+ end
74
+
75
+ describe '#publish' do
76
+ let(:broker) { double(Hutch::Broker) }
77
+ let(:args) { ['test.key', 'message', { headers: { foo: 'bar' } }] }
78
+
79
+ before { allow(Hutch).to receive(:broker).and_return(broker) }
80
+
81
+ it 'delegates to Hutch::Broker#publish' do
82
+ expect(broker).to receive(:publish).with(*args)
83
+ Hutch.publish(*args)
84
+ end
85
+ end
86
+ end
87
+
@@ -0,0 +1,35 @@
1
+ require 'simplecov'
2
+ if ENV['TRAVIS']
3
+ require 'coveralls'
4
+ SimpleCov.formatter = Coveralls::SimpleCov::Formatter
5
+ end
6
+
7
+ SimpleCov.start do
8
+ add_filter '/spec/'
9
+ add_filter '/.bundle/'
10
+ end
11
+
12
+ require 'raven'
13
+ require 'hutch'
14
+ require 'logger'
15
+
16
+ RSpec.configure do |config|
17
+ config.before(:all) { Hutch::Config.log_level = Logger::FATAL }
18
+ config.raise_errors_for_deprecations!
19
+ end
20
+
21
+ # Constants (classes, etc) defined within a block passed to this method
22
+ # will be removed from the global namespace after the block as run.
23
+ def isolate_constants
24
+ existing_constants = Object.constants
25
+ yield
26
+ ensure
27
+ (Object.constants - existing_constants).each do |constant|
28
+ Object.send(:remove_const, constant)
29
+ end
30
+ end
31
+
32
+ def deep_copy(obj)
33
+ Marshal.load(Marshal.dump(obj))
34
+ end
35
+
metadata ADDED
@@ -0,0 +1,187 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dkastner-hutch
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.17.1
5
+ platform: ruby
6
+ authors:
7
+ - Harry Marr
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-08-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bunny
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 1.7.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 1.7.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: carrot-top
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.0.7
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.0.7
41
+ - !ruby/object:Gem::Dependency
42
+ name: multi_json
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.5'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.5'
55
+ - !ruby/object:Gem::Dependency
56
+ name: activesupport
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.7.1
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.7.1
97
+ description: Hutch is a Ruby library for enabling asynchronous inter-service communication
98
+ using RabbitMQ.
99
+ email:
100
+ - developers@gocardless.com
101
+ executables:
102
+ - hutch
103
+ extensions: []
104
+ extra_rdoc_files: []
105
+ files:
106
+ - ".gitignore"
107
+ - ".travis.yml"
108
+ - CHANGELOG.md
109
+ - Gemfile
110
+ - Guardfile
111
+ - LICENSE
112
+ - README.md
113
+ - Rakefile
114
+ - bin/hutch
115
+ - circle.yml
116
+ - examples/consumer.rb
117
+ - examples/producer.rb
118
+ - hutch.gemspec
119
+ - lib/hutch.rb
120
+ - lib/hutch/broker.rb
121
+ - lib/hutch/cli.rb
122
+ - lib/hutch/config.rb
123
+ - lib/hutch/consumer.rb
124
+ - lib/hutch/error_handlers.rb
125
+ - lib/hutch/error_handlers/airbrake.rb
126
+ - lib/hutch/error_handlers/honeybadger.rb
127
+ - lib/hutch/error_handlers/logger.rb
128
+ - lib/hutch/error_handlers/sentry.rb
129
+ - lib/hutch/exceptions.rb
130
+ - lib/hutch/logging.rb
131
+ - lib/hutch/message.rb
132
+ - lib/hutch/tracers.rb
133
+ - lib/hutch/tracers/newrelic.rb
134
+ - lib/hutch/tracers/null_tracer.rb
135
+ - lib/hutch/version.rb
136
+ - lib/hutch/worker.rb
137
+ - spec/hutch/broker_spec.rb
138
+ - spec/hutch/cli_spec.rb
139
+ - spec/hutch/config_spec.rb
140
+ - spec/hutch/consumer_spec.rb
141
+ - spec/hutch/error_handlers/airbrake_spec.rb
142
+ - spec/hutch/error_handlers/honeybadger_spec.rb
143
+ - spec/hutch/error_handlers/logger_spec.rb
144
+ - spec/hutch/error_handlers/sentry_spec.rb
145
+ - spec/hutch/logger_spec.rb
146
+ - spec/hutch/message_spec.rb
147
+ - spec/hutch/worker_spec.rb
148
+ - spec/hutch_spec.rb
149
+ - spec/spec_helper.rb
150
+ homepage: https://github.com/gocardless/hutch
151
+ licenses:
152
+ - MIT
153
+ metadata: {}
154
+ post_install_message:
155
+ rdoc_options: []
156
+ require_paths:
157
+ - lib
158
+ required_ruby_version: !ruby/object:Gem::Requirement
159
+ requirements:
160
+ - - ">="
161
+ - !ruby/object:Gem::Version
162
+ version: '0'
163
+ required_rubygems_version: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - ">="
166
+ - !ruby/object:Gem::Version
167
+ version: '0'
168
+ requirements: []
169
+ rubyforge_project:
170
+ rubygems_version: 2.2.2
171
+ signing_key:
172
+ specification_version: 4
173
+ summary: Easy inter-service communication using RabbitMQ.
174
+ test_files:
175
+ - spec/hutch/broker_spec.rb
176
+ - spec/hutch/cli_spec.rb
177
+ - spec/hutch/config_spec.rb
178
+ - spec/hutch/consumer_spec.rb
179
+ - spec/hutch/error_handlers/airbrake_spec.rb
180
+ - spec/hutch/error_handlers/honeybadger_spec.rb
181
+ - spec/hutch/error_handlers/logger_spec.rb
182
+ - spec/hutch/error_handlers/sentry_spec.rb
183
+ - spec/hutch/logger_spec.rb
184
+ - spec/hutch/message_spec.rb
185
+ - spec/hutch/worker_spec.rb
186
+ - spec/hutch_spec.rb
187
+ - spec/spec_helper.rb