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,325 @@
1
+ require 'spec_helper'
2
+ require 'hutch/broker'
3
+
4
+ describe Hutch::Broker do
5
+ let(:config) { deep_copy(Hutch::Config.user_config) }
6
+ subject(:broker) { Hutch::Broker.new(config) }
7
+
8
+ describe '#connect' do
9
+ before { allow(broker).to receive(:set_up_amqp_connection) }
10
+ before { allow(broker).to receive(:set_up_api_connection) }
11
+ before { allow(broker).to receive(:disconnect) }
12
+
13
+ it 'sets up the amqp connection' do
14
+ expect(broker).to receive(:set_up_amqp_connection)
15
+ broker.connect
16
+ end
17
+
18
+ it 'sets up the api connection' do
19
+ expect(broker).to receive(:set_up_api_connection)
20
+ broker.connect
21
+ end
22
+
23
+ it 'does not disconnect' do
24
+ expect(broker).not_to receive(:disconnect)
25
+ broker.connect
26
+ end
27
+
28
+ context 'when given a block' do
29
+ it 'disconnects' do
30
+ expect(broker).to receive(:disconnect).once
31
+ broker.connect { }
32
+ end
33
+ end
34
+
35
+ context 'when given a block that fails' do
36
+ let(:exception) { Class.new(StandardError) }
37
+
38
+ it 'disconnects' do
39
+ expect(broker).to receive(:disconnect).once
40
+ expect do
41
+ broker.connect { fail exception }
42
+ end.to raise_error(exception)
43
+ end
44
+ end
45
+
46
+ context "with options" do
47
+ let(:options) { { enable_http_api_use: false } }
48
+
49
+ it "doesnt set up api" do
50
+ expect(broker).not_to receive(:set_up_api_connection)
51
+ broker.connect options
52
+ end
53
+ end
54
+ end
55
+
56
+ describe '#set_up_amqp_connection', rabbitmq: true do
57
+ context 'with valid details' do
58
+ before { broker.set_up_amqp_connection }
59
+ after { broker.disconnect }
60
+
61
+ describe '#connection' do
62
+ subject { super().connection }
63
+ it { is_expected.to be_a Bunny::Session }
64
+ end
65
+
66
+ describe '#channel' do
67
+ subject { super().channel }
68
+ it { is_expected.to be_a Bunny::Channel }
69
+ end
70
+
71
+ describe '#exchange' do
72
+ subject { super().exchange }
73
+ it { is_expected.to be_a Bunny::Exchange }
74
+ end
75
+ end
76
+
77
+ context 'when given invalid details' do
78
+ before { config[:mq_host] = 'notarealhost' }
79
+ let(:set_up_amqp_connection) { ->{ broker.set_up_amqp_connection } }
80
+
81
+ specify { expect(set_up_amqp_connection).to raise_error }
82
+ end
83
+
84
+ context 'with channel_prefetch set' do
85
+ let(:prefetch_value) { 1 }
86
+ before { config[:channel_prefetch] = prefetch_value }
87
+ after { broker.disconnect }
88
+
89
+ it "set's channel's prefetch" do
90
+ expect_any_instance_of(Bunny::Channel).
91
+ to receive(:prefetch).with(prefetch_value)
92
+ broker.set_up_amqp_connection
93
+ end
94
+ end
95
+
96
+ context 'with force_publisher_confirms set' do
97
+ let(:force_publisher_confirms_value) { true }
98
+ before { config[:force_publisher_confirms] = force_publisher_confirms_value }
99
+ after { broker.disconnect }
100
+
101
+ it 'waits for confirmation' do
102
+ expect_any_instance_of(Bunny::Channel).
103
+ to receive(:confirm_select)
104
+ broker.set_up_amqp_connection
105
+ end
106
+ end
107
+ end
108
+
109
+ describe '#set_up_api_connection', rabbitmq: true do
110
+ context 'with valid details' do
111
+ before { broker.set_up_api_connection }
112
+ after { broker.disconnect }
113
+
114
+ describe '#api_client' do
115
+ subject { super().api_client }
116
+ it { is_expected.to be_a CarrotTop }
117
+ end
118
+ end
119
+
120
+ context 'when given invalid details' do
121
+ before { config[:mq_api_host] = 'notarealhost' }
122
+ after { broker.disconnect }
123
+ let(:set_up_api_connection) { ->{ broker.set_up_api_connection } }
124
+
125
+ specify { expect(set_up_api_connection).to raise_error }
126
+ end
127
+ end
128
+
129
+ describe '#queue' do
130
+ let(:channel) { double('Channel') }
131
+ let(:arguments) { { foo: :bar } }
132
+ before { allow(broker).to receive(:channel) { channel } }
133
+
134
+ it 'applies a global namespace' do
135
+ config[:namespace] = 'mirror-all.service'
136
+ expect(broker.channel).to receive(:queue) do |*args|
137
+ args.first == ''
138
+ args.last == arguments
139
+ end
140
+ broker.queue('test', arguments)
141
+ end
142
+ end
143
+
144
+ describe '#bindings', rabbitmq: true do
145
+ around { |example| broker.connect { example.run } }
146
+ subject { broker.bindings }
147
+
148
+ context 'with no bindings' do
149
+ describe '#keys' do
150
+ subject { super().keys }
151
+ it { is_expected.not_to include 'test' }
152
+ end
153
+ end
154
+
155
+ context 'with a binding' do
156
+ around do |example|
157
+ queue = broker.queue('test').bind(broker.exchange, routing_key: 'key')
158
+ example.run
159
+ queue.unbind(broker.exchange, routing_key: 'key').delete
160
+ end
161
+
162
+ it { is_expected.to include({ 'test' => ['key'] }) }
163
+ end
164
+ end
165
+
166
+ describe '#bind_queue' do
167
+
168
+ around { |example| broker.connect { example.run } }
169
+
170
+ let(:routing_keys) { %w( a b c ) }
171
+ let(:queue) { double('Queue', bind: nil, unbind: nil, name: 'consumer') }
172
+ before { allow(broker).to receive(:bindings).and_return('consumer' => ['d']) }
173
+
174
+ it 'calls bind for each routing key' do
175
+ routing_keys.each do |key|
176
+ expect(queue).to receive(:bind).with(broker.exchange, routing_key: key)
177
+ end
178
+ broker.bind_queue(queue, routing_keys)
179
+ end
180
+
181
+ it 'calls unbind for each redundant existing binding' do
182
+ expect(queue).to receive(:unbind).with(broker.exchange, routing_key: 'd')
183
+ broker.bind_queue(queue, routing_keys)
184
+ end
185
+
186
+ context '(rabbitmq integration test)', rabbitmq: true do
187
+ let(:queue) { broker.queue('consumer') }
188
+ let(:routing_key) { 'key' }
189
+
190
+ before { allow(broker).to receive(:bindings).and_call_original }
191
+ before { queue.bind(broker.exchange, routing_key: 'redundant-key') }
192
+ after { queue.unbind(broker.exchange, routing_key: routing_key).delete }
193
+
194
+ it 'results in the correct bindings' do
195
+ broker.bind_queue(queue, [routing_key])
196
+ expect(broker.bindings).to include({ queue.name => [routing_key] })
197
+ end
198
+ end
199
+ end
200
+
201
+ describe '#wait_on_threads' do
202
+ let(:thread) { double('Thread') }
203
+ before { allow(broker).to receive(:work_pool_threads).and_return(threads) }
204
+
205
+ context 'when all threads finish within the timeout' do
206
+ let(:threads) { [double(join: thread), double(join: thread)] }
207
+ specify { expect(broker.wait_on_threads(1)).to be_truthy }
208
+ end
209
+
210
+ context 'when timeout expires for one thread' do
211
+ let(:threads) { [double(join: thread), double(join: nil)] }
212
+ specify { expect(broker.wait_on_threads(1)).to be_falsey }
213
+ end
214
+ end
215
+
216
+ describe '#stop' do
217
+ let(:thread_1) { double('Thread') }
218
+ let(:thread_2) { double('Thread') }
219
+ let(:work_pool) { double('Bunny::ConsumerWorkPool') }
220
+ let(:config) { { graceful_exit_timeout: 2 } }
221
+
222
+ before do
223
+ allow(broker).to receive(:channel_work_pool).and_return(work_pool)
224
+ end
225
+
226
+ it 'gracefully stops the work pool' do
227
+ expect(work_pool).to receive(:shutdown)
228
+ expect(work_pool).to receive(:join).with(2)
229
+ expect(work_pool).to receive(:kill)
230
+
231
+ broker.stop
232
+ end
233
+ end
234
+
235
+ describe '#publish' do
236
+ context 'with a valid connection' do
237
+ before { broker.set_up_amqp_connection }
238
+ after { broker.disconnect }
239
+
240
+ it 'publishes to the exchange' do
241
+ expect(broker.exchange).to receive(:publish).once
242
+ broker.publish('test.key', 'message')
243
+ end
244
+
245
+ it 'sets default properties' do
246
+ expect(broker.exchange).to receive(:publish).with(
247
+ JSON.dump("message"),
248
+ hash_including(
249
+ persistent: true,
250
+ routing_key: 'test.key',
251
+ content_type: 'application/json'
252
+ )
253
+ )
254
+
255
+ broker.publish('test.key', 'message')
256
+ end
257
+
258
+ it 'allows passing message properties' do
259
+ expect(broker.exchange).to receive(:publish).once
260
+ broker.publish('test.key', 'message', {expiration: "2000", persistent: false})
261
+ end
262
+
263
+ context 'when there are global properties' do
264
+ context 'as a hash' do
265
+ before do
266
+ allow(Hutch).to receive(:global_properties).and_return(app_id: 'app')
267
+ end
268
+
269
+ it 'merges the properties' do
270
+ expect(broker.exchange).
271
+ to receive(:publish).with('"message"', hash_including(app_id: 'app'))
272
+ broker.publish('test.key', 'message')
273
+ end
274
+ end
275
+
276
+ context 'as a callable object' do
277
+ before do
278
+ allow(Hutch).to receive(:global_properties).and_return(proc { { app_id: 'app' } })
279
+ end
280
+
281
+ it 'calls the proc and merges the properties' do
282
+ expect(broker.exchange).
283
+ to receive(:publish).with('"message"', hash_including(app_id: 'app'))
284
+ broker.publish('test.key', 'message')
285
+ end
286
+ end
287
+ end
288
+
289
+ context 'with force_publisher_confirms not set in the config' do
290
+ it 'does not wait for confirms on the channel' do
291
+ expect_any_instance_of(Bunny::Channel).
292
+ to_not receive(:wait_for_confirms)
293
+ broker.publish('test.key', 'message')
294
+ end
295
+ end
296
+
297
+ context 'with force_publisher_confirms set in the config' do
298
+ let(:force_publisher_confirms_value) { true }
299
+
300
+ before do
301
+ config[:force_publisher_confirms] = force_publisher_confirms_value
302
+ end
303
+
304
+ it 'waits for confirms on the channel' do
305
+ expect_any_instance_of(Bunny::Channel).
306
+ to receive(:wait_for_confirms)
307
+ broker.publish('test.key', 'message')
308
+ end
309
+ end
310
+ end
311
+
312
+ context 'without a valid connection' do
313
+ it 'raises an exception' do
314
+ expect { broker.publish('test.key', 'message') }.
315
+ to raise_exception(Hutch::PublishError)
316
+ end
317
+
318
+ it 'logs an error' do
319
+ expect(broker.logger).to receive(:error)
320
+ broker.publish('test.key', 'message') rescue nil
321
+ end
322
+ end
323
+ end
324
+ end
325
+
@@ -0,0 +1,80 @@
1
+ require 'hutch/cli'
2
+ require 'tempfile'
3
+
4
+ describe Hutch::CLI do
5
+ let(:cli) { Hutch::CLI.new }
6
+
7
+ describe "#parse_options" do
8
+ context "--config" do
9
+ context "when the config file does not exist" do
10
+ let(:file) { "/path/to/nonexistant/file" }
11
+ before { allow(STDERR).to receive(:write) }
12
+
13
+ it "bails" do
14
+ expect {
15
+ cli.parse_options(["--config=#{file}"])
16
+ }.to raise_error SystemExit
17
+ end
18
+ end
19
+
20
+ context "when the config file exists" do
21
+ let(:file) do
22
+ Tempfile.new("hutch-test-config.yaml").to_path
23
+ end
24
+
25
+ it "parses the config" do
26
+ expect(Hutch::Config).to receive(:load_from_file)
27
+ cli.parse_options(["--config=#{file}"])
28
+ end
29
+ end
30
+ end
31
+
32
+ context "--mq-tls-key" do
33
+ context "when the keyfile file does not exist" do
34
+ let(:file) { "/path/to/nonexistant/file" }
35
+ before { allow(STDERR).to receive(:write) }
36
+
37
+ it "bails" do
38
+ expect {
39
+ cli.parse_options(["--mq-tls-key=#{file}"])
40
+ }.to raise_error SystemExit
41
+ end
42
+ end
43
+
44
+ context "when the keyfile file exists" do
45
+ let(:file) do
46
+ Tempfile.new("hutch-test-key.pem").to_path
47
+ end
48
+
49
+ it "sets mq_tls_key to the file" do
50
+ expect(Hutch::Config).to receive(:mq_tls_key=)
51
+ cli.parse_options(["--mq-tls-key=#{file}"])
52
+ end
53
+ end
54
+ end
55
+
56
+ context "--mq-tls-cert" do
57
+ context "when the certfile file does not exist" do
58
+ let(:file) { "/path/to/nonexistant/file" }
59
+ before { allow(STDERR).to receive(:write) }
60
+
61
+ it "bails" do
62
+ expect {
63
+ cli.parse_options(["--mq-tls-cert=#{file}"])
64
+ }.to raise_error SystemExit
65
+ end
66
+ end
67
+
68
+ context "when the certfile file exists" do
69
+ let(:file) do
70
+ Tempfile.new("hutch-test-cert.pem").to_path
71
+ end
72
+
73
+ it "sets mq_tls_cert to the file" do
74
+ expect(Hutch::Config).to receive(:mq_tls_cert=)
75
+ cli.parse_options(["--mq-tls-cert=#{file}"])
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,126 @@
1
+ require 'hutch/config'
2
+ require 'tempfile'
3
+
4
+ describe Hutch::Config do
5
+ let(:new_value) { 'not-localhost' }
6
+
7
+ describe '.get' do
8
+ context 'for valid attributes' do
9
+ subject { Hutch::Config.get(:mq_host) }
10
+
11
+ context 'with no overridden value' do
12
+ it { is_expected.to eq('localhost') }
13
+ end
14
+
15
+ context 'with an overridden value' do
16
+ before { allow(Hutch::Config).to receive_messages(user_config: { mq_host: new_value }) }
17
+ it { is_expected.to eq(new_value) }
18
+ end
19
+ end
20
+
21
+ context 'for invalid attributes' do
22
+ let(:invalid_get) { ->{ Hutch::Config.get(:invalid_attr) } }
23
+ specify { expect(invalid_get).to raise_error Hutch::UnknownAttributeError }
24
+ end
25
+ end
26
+
27
+ describe '.set' do
28
+ context 'for valid attributes' do
29
+ before { Hutch::Config.set(:mq_host, new_value) }
30
+ subject { Hutch::Config.user_config[:mq_host] }
31
+
32
+ context 'sets value in user config hash' do
33
+ it { is_expected.to eq(new_value) }
34
+ end
35
+ end
36
+
37
+ context 'for invalid attributes' do
38
+ let(:invalid_set) { ->{ Hutch::Config.set(:invalid_attr, new_value) } }
39
+ specify { expect(invalid_set).to raise_error Hutch::UnknownAttributeError }
40
+ end
41
+ end
42
+
43
+ describe 'a magic getter' do
44
+ context 'for a valid attribute' do
45
+ it 'calls get' do
46
+ expect(Hutch::Config).to receive(:get).with(:mq_host)
47
+ Hutch::Config.mq_host
48
+ end
49
+ end
50
+
51
+ context 'for an invalid attribute' do
52
+ let(:invalid_getter) { ->{ Hutch::Config.invalid_attr } }
53
+ specify { expect(invalid_getter).to raise_error NoMethodError }
54
+ end
55
+ end
56
+
57
+ describe 'a magic setter' do
58
+ context 'for a valid attribute' do
59
+ it 'calls set' do
60
+ expect(Hutch::Config).to receive(:set).with(:mq_host, new_value)
61
+ Hutch::Config.mq_host = new_value
62
+ end
63
+ end
64
+
65
+ context 'for an invalid attribute' do
66
+ let(:invalid_setter) { ->{ Hutch::Config.invalid_attr = new_value } }
67
+ specify { expect(invalid_setter).to raise_error NoMethodError }
68
+ end
69
+ end
70
+
71
+ describe '.load_from_file' do
72
+ let(:host) { 'broker.yourhost.com' }
73
+ let(:username) { 'calvin' }
74
+ let(:file) do
75
+ Tempfile.new('configs.yaml').tap do |t|
76
+ t.write(YAML.dump(config_data))
77
+ t.rewind
78
+ end
79
+ end
80
+
81
+ context 'when an attribute is invalid' do
82
+ let(:config_data) { { random_attribute: 'socks' } }
83
+ it 'raises an error' do
84
+ expect {
85
+ Hutch::Config.load_from_file(file)
86
+ }.to raise_error(NoMethodError)
87
+ end
88
+ end
89
+
90
+ context 'when attributes are valid' do
91
+ let(:config_data) { { mq_host: host, mq_username: username } }
92
+
93
+ it 'loads in the config data' do
94
+ Hutch::Config.load_from_file(file)
95
+ expect(Hutch::Config.mq_host).to eq host
96
+ expect(Hutch::Config.mq_username).to eq username
97
+ end
98
+ end
99
+ end
100
+
101
+ describe '.load_from_file' do
102
+ let(:host) { 'localhost' }
103
+ let(:username) { 'calvin' }
104
+ let(:file) do
105
+ Tempfile.new('configs.yaml').tap do |t|
106
+ t.write(config_contents)
107
+ t.rewind
108
+ end
109
+ end
110
+
111
+ context 'when using ERb' do
112
+ let(:config_contents) do
113
+ <<-YAML
114
+ mq_host: 'localhost'
115
+ mq_username: '<%= "calvin" %>'
116
+ YAML
117
+ end
118
+
119
+ it 'loads in the config data' do
120
+ Hutch::Config.load_from_file(file)
121
+ expect(Hutch::Config.mq_host).to eq host
122
+ expect(Hutch::Config.mq_username).to eq username
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,130 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hutch::Consumer do
4
+ around(:each) do |example|
5
+ isolate_constants do
6
+ example.run
7
+ end
8
+ end
9
+
10
+ let(:simple_consumer) do
11
+ unless defined? SimpleConsumer
12
+ class SimpleConsumer
13
+ include Hutch::Consumer
14
+ consume 'hutch.test1'
15
+ end
16
+ end
17
+ SimpleConsumer
18
+ end
19
+
20
+ let(:complex_consumer) do
21
+ unless defined? ComplexConsumer
22
+ class ComplexConsumer
23
+ include Hutch::Consumer
24
+ consume 'hutch.test1', 'hutch.test2'
25
+ arguments foo: :bar
26
+ end
27
+ end
28
+ ComplexConsumer
29
+ end
30
+
31
+ describe 'module inclusion' do
32
+ it 'registers the class as a consumer' do
33
+ expect(Hutch).to receive(:register_consumer) do |klass|
34
+ expect(klass).to eq(simple_consumer)
35
+ end
36
+
37
+ simple_consumer
38
+ end
39
+ end
40
+
41
+
42
+ describe '.consume' do
43
+ it 'saves the routing key to the consumer' do
44
+ expect(simple_consumer.routing_keys).to include 'hutch.test1'
45
+ end
46
+
47
+ context 'with multiple routing keys' do
48
+ it 'registers the class once for each routing key' do
49
+ expect(complex_consumer.routing_keys).to include 'hutch.test1'
50
+ expect(complex_consumer.routing_keys).to include 'hutch.test2'
51
+ end
52
+ end
53
+
54
+ context 'when given the same routing key multiple times' do
55
+ subject { simple_consumer.routing_keys }
56
+ before { simple_consumer.consume 'hutch.test1' }
57
+
58
+ describe '#length' do
59
+ subject { super().length }
60
+ it { is_expected.to eq(1)}
61
+ end
62
+ end
63
+ end
64
+
65
+ describe '.queue_name' do
66
+ let(:queue_name) { 'foo' }
67
+
68
+ it 'overrides the queue name' do
69
+ simple_consumer.queue_name(queue_name)
70
+ expect(simple_consumer.get_queue_name).to eq(queue_name)
71
+ end
72
+ end
73
+
74
+ describe '.arguments' do
75
+ let(:args) { { foo: :bar} }
76
+
77
+ it 'overrides the arguments' do
78
+ simple_consumer.arguments(args)
79
+ expect(simple_consumer.get_arguments).to eq(args)
80
+ end
81
+
82
+ end
83
+
84
+ describe '.get_arguments' do
85
+
86
+ context 'when defined' do
87
+ it { expect(complex_consumer.get_arguments).to eq(foo: :bar) }
88
+ end
89
+
90
+ context 'when not defined' do
91
+ it { expect(simple_consumer.get_arguments).to eq({}) }
92
+ end
93
+
94
+ end
95
+
96
+ describe '.get_queue_name' do
97
+
98
+ context 'when queue name has been set explicitly' do
99
+ it 'returns the give queue name' do
100
+ class Foo
101
+ include Hutch::Consumer
102
+ queue_name "bar"
103
+ end
104
+
105
+ expect(Foo.get_queue_name).to eq("bar")
106
+ end
107
+ end
108
+
109
+ context 'when no queue name has been set' do
110
+ it 'replaces module separators with colons' do
111
+ module Foo
112
+ class Bar
113
+ include Hutch::Consumer
114
+ end
115
+ end
116
+
117
+ expect(Foo::Bar.get_queue_name).to eq('foo:bar')
118
+ end
119
+
120
+ it 'converts camelcase class names to snake case' do
121
+ class FooBarBAZ
122
+ include Hutch::Consumer
123
+ end
124
+
125
+ expect(FooBarBAZ.get_queue_name).to eq('foo_bar_baz')
126
+ end
127
+ end
128
+ end
129
+ end
130
+
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hutch::ErrorHandlers::Airbrake do
4
+ let(:error_handler) { Hutch::ErrorHandlers::Airbrake.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 Airbrake" 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
+ :parameters => {
25
+ :payload => payload,
26
+ :consumer => consumer,
27
+ },
28
+ :cgi_data => ENV.to_hash,
29
+ }
30
+ expect(::Airbrake).to receive(:notify_or_ignore).with(ex, message)
31
+ error_handler.handle(message_id, payload, consumer, ex)
32
+ end
33
+ end
34
+ end