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.
- checksums.yaml +7 -0
- data/.gitignore +7 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +397 -0
- data/Gemfile +22 -0
- data/Guardfile +5 -0
- data/LICENSE +22 -0
- data/README.md +315 -0
- data/Rakefile +14 -0
- data/bin/hutch +8 -0
- data/circle.yml +3 -0
- data/examples/consumer.rb +13 -0
- data/examples/producer.rb +10 -0
- data/hutch.gemspec +24 -0
- data/lib/hutch/broker.rb +356 -0
- data/lib/hutch/cli.rb +205 -0
- data/lib/hutch/config.rb +121 -0
- data/lib/hutch/consumer.rb +66 -0
- data/lib/hutch/error_handlers/airbrake.rb +26 -0
- data/lib/hutch/error_handlers/honeybadger.rb +28 -0
- data/lib/hutch/error_handlers/logger.rb +16 -0
- data/lib/hutch/error_handlers/sentry.rb +23 -0
- data/lib/hutch/error_handlers.rb +8 -0
- data/lib/hutch/exceptions.rb +7 -0
- data/lib/hutch/logging.rb +32 -0
- data/lib/hutch/message.rb +33 -0
- data/lib/hutch/tracers/newrelic.rb +19 -0
- data/lib/hutch/tracers/null_tracer.rb +15 -0
- data/lib/hutch/tracers.rb +6 -0
- data/lib/hutch/version.rb +4 -0
- data/lib/hutch/worker.rb +110 -0
- data/lib/hutch.rb +60 -0
- data/spec/hutch/broker_spec.rb +325 -0
- data/spec/hutch/cli_spec.rb +80 -0
- data/spec/hutch/config_spec.rb +126 -0
- data/spec/hutch/consumer_spec.rb +130 -0
- data/spec/hutch/error_handlers/airbrake_spec.rb +34 -0
- data/spec/hutch/error_handlers/honeybadger_spec.rb +36 -0
- data/spec/hutch/error_handlers/logger_spec.rb +15 -0
- data/spec/hutch/error_handlers/sentry_spec.rb +20 -0
- data/spec/hutch/logger_spec.rb +28 -0
- data/spec/hutch/message_spec.rb +38 -0
- data/spec/hutch/worker_spec.rb +98 -0
- data/spec/hutch_spec.rb +87 -0
- data/spec/spec_helper.rb +35 -0
- 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
|