dkastner-hutch 0.17.1

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 (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