shivam 0.0.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/test.yml +57 -0
  3. data/.gitignore +10 -0
  4. data/.rspec +1 -0
  5. data/.yardopts +5 -0
  6. data/CHANGELOG.md +899 -0
  7. data/Gemfile +35 -0
  8. data/Guardfile +14 -0
  9. data/LICENSE +23 -0
  10. data/README.md +679 -0
  11. data/Rakefile +21 -0
  12. data/bin/ci/before_build.sh +20 -0
  13. data/bin/ci/before_build_docker.sh +20 -0
  14. data/bin/ci/install_on_debian.sh +46 -0
  15. data/bin/hutch +8 -0
  16. data/examples/consumer.rb +13 -0
  17. data/examples/producer.rb +10 -0
  18. data/hutch.gemspec +27 -0
  19. data/lib/hutch/acknowledgements/base.rb +16 -0
  20. data/lib/hutch/acknowledgements/nack_on_all_failures.rb +19 -0
  21. data/lib/hutch/adapter.rb +11 -0
  22. data/lib/hutch/adapters/bunny.rb +37 -0
  23. data/lib/hutch/adapters/march_hare.rb +41 -0
  24. data/lib/hutch/broker.rb +384 -0
  25. data/lib/hutch/cli.rb +246 -0
  26. data/lib/hutch/config.rb +305 -0
  27. data/lib/hutch/consumer.rb +125 -0
  28. data/lib/hutch/error_handlers/airbrake.rb +54 -0
  29. data/lib/hutch/error_handlers/base.rb +15 -0
  30. data/lib/hutch/error_handlers/bugsnag.rb +30 -0
  31. data/lib/hutch/error_handlers/honeybadger.rb +43 -0
  32. data/lib/hutch/error_handlers/logger.rb +22 -0
  33. data/lib/hutch/error_handlers/rollbar.rb +28 -0
  34. data/lib/hutch/error_handlers/sentry.rb +26 -0
  35. data/lib/hutch/error_handlers/sentry_raven.rb +31 -0
  36. data/lib/hutch/error_handlers.rb +11 -0
  37. data/lib/hutch/exceptions.rb +14 -0
  38. data/lib/hutch/logging.rb +32 -0
  39. data/lib/hutch/message.rb +31 -0
  40. data/lib/hutch/publisher.rb +75 -0
  41. data/lib/hutch/serializers/identity.rb +19 -0
  42. data/lib/hutch/serializers/json.rb +22 -0
  43. data/lib/hutch/tracers/datadog.rb +18 -0
  44. data/lib/hutch/tracers/newrelic.rb +19 -0
  45. data/lib/hutch/tracers/null_tracer.rb +15 -0
  46. data/lib/hutch/tracers.rb +7 -0
  47. data/lib/hutch/version.rb +3 -0
  48. data/lib/hutch/waiter.rb +104 -0
  49. data/lib/hutch/worker.rb +145 -0
  50. data/lib/hutch.rb +69 -0
  51. data/lib/yard-settings/handler.rb +38 -0
  52. data/lib/yard-settings/yard-settings.rb +2 -0
  53. data/spec/hutch/broker_spec.rb +462 -0
  54. data/spec/hutch/cli_spec.rb +93 -0
  55. data/spec/hutch/config_spec.rb +259 -0
  56. data/spec/hutch/consumer_spec.rb +208 -0
  57. data/spec/hutch/error_handlers/airbrake_spec.rb +49 -0
  58. data/spec/hutch/error_handlers/bugsnag_spec.rb +55 -0
  59. data/spec/hutch/error_handlers/honeybadger_spec.rb +58 -0
  60. data/spec/hutch/error_handlers/logger_spec.rb +28 -0
  61. data/spec/hutch/error_handlers/rollbar_spec.rb +45 -0
  62. data/spec/hutch/error_handlers/sentry_raven_spec.rb +37 -0
  63. data/spec/hutch/error_handlers/sentry_spec.rb +47 -0
  64. data/spec/hutch/logger_spec.rb +34 -0
  65. data/spec/hutch/message_spec.rb +38 -0
  66. data/spec/hutch/serializers/json_spec.rb +17 -0
  67. data/spec/hutch/tracers/datadog_spec.rb +44 -0
  68. data/spec/hutch/waiter_spec.rb +51 -0
  69. data/spec/hutch/worker_spec.rb +184 -0
  70. data/spec/hutch_spec.rb +87 -0
  71. data/spec/spec_helper.rb +42 -0
  72. data/templates/default/class/html/settings.erb +0 -0
  73. data/templates/default/class/setup.rb +4 -0
  74. data/templates/default/fulldoc/html/css/hutch.css +13 -0
  75. data/templates/default/layout/html/setup.rb +7 -0
  76. data/templates/default/method_details/html/settings.erb +5 -0
  77. data/templates/default/method_details/setup.rb +4 -0
  78. data/templates/default/method_details/text/settings.erb +0 -0
  79. data/templates/default/module/html/settings.erb +40 -0
  80. data/templates/default/module/setup.rb +4 -0
  81. metadata +205 -0
@@ -0,0 +1,462 @@
1
+ require 'spec_helper'
2
+ require 'hutch/broker'
3
+
4
+ describe Hutch::Broker do
5
+ before do
6
+ Hutch::Config.initialize(client_logger: Hutch::Logging.logger)
7
+ @config = Hutch::Config.to_hash
8
+ end
9
+ let!(:config) { @config }
10
+ after do
11
+ Hutch::Config.instance_variable_set(:@config, nil)
12
+ Hutch::Config.initialize
13
+ end
14
+ let(:broker) { Hutch::Broker.new(config) }
15
+
16
+ describe '#connect' do
17
+ before { allow(broker).to receive(:set_up_amqp_connection) }
18
+ before { allow(broker).to receive(:set_up_api_connection) }
19
+ before { allow(broker).to receive(:disconnect) }
20
+
21
+ it 'sets up the amqp connection' do
22
+ expect(broker).to receive(:set_up_amqp_connection)
23
+ broker.connect
24
+ end
25
+
26
+ it 'sets up the api connection' do
27
+ expect(broker).to receive(:set_up_api_connection)
28
+ broker.connect
29
+ end
30
+
31
+ it 'does not disconnect' do
32
+ expect(broker).not_to receive(:disconnect)
33
+ broker.connect
34
+ end
35
+
36
+ context 'when given a block' do
37
+ it 'disconnects' do
38
+ expect(broker).to receive(:disconnect).once
39
+ broker.connect { }
40
+ end
41
+ end
42
+
43
+ context 'when given a block that fails' do
44
+ let(:exception) { Class.new(StandardError) }
45
+
46
+ it 'disconnects' do
47
+ expect(broker).to receive(:disconnect).once
48
+ expect do
49
+ broker.connect { fail exception }
50
+ end.to raise_error(exception)
51
+ end
52
+ end
53
+
54
+ context "with options" do
55
+ let(:options) { { enable_http_api_use: false } }
56
+
57
+ it "doesnt set up api" do
58
+ expect(broker).not_to receive(:set_up_api_connection)
59
+ broker.connect options
60
+ end
61
+ end
62
+ end
63
+
64
+ describe '#set_up_amqp_connection' do
65
+ it 'opens a connection, channel and declares an exchange' do
66
+ expect(broker).to receive(:open_connection!).ordered
67
+ expect(broker).to receive(:open_channel!).ordered
68
+ expect(broker).to receive(:declare_exchange!).ordered
69
+
70
+ broker.set_up_amqp_connection
71
+ end
72
+ end
73
+
74
+ describe '#open_connection', rabbitmq: true do
75
+ describe 'return value' do
76
+ subject { broker.open_connection }
77
+ after { subject.close }
78
+
79
+ it(nil, adapter: :bunny) { is_expected.to be_a Hutch::Adapters::BunnyAdapter }
80
+ it(nil, adapter: :march_hare) { is_expected.to be_a Hutch::Adapters::MarchHareAdapter }
81
+ end
82
+
83
+ context 'when given invalid details' do
84
+ before { config[:mq_host] = 'notarealhost' }
85
+ it { expect { broker.open_connection }.to raise_error(StandardError) }
86
+ end
87
+
88
+ it 'does not set #connection' do
89
+ connection = broker.open_connection
90
+
91
+ expect(broker.connection).to be_nil
92
+
93
+ connection.close
94
+ end
95
+
96
+ context 'when configured with a URI' do
97
+ context 'which specifies the port' do
98
+ before { config[:uri] = 'amqp://guest:guest@127.0.0.1:5672/' }
99
+
100
+ it 'successfully connects' do
101
+ c = broker.open_connection
102
+ expect(c).to be_open
103
+ c.close
104
+ end
105
+ end
106
+
107
+ context 'which does not specify port and uses the amqp scheme' do
108
+ before { config[:uri] = 'amqp://guest:guest@127.0.0.1/' }
109
+
110
+ it 'successfully connects' do
111
+ c = broker.open_connection
112
+ expect(c).to be_open
113
+ c.close
114
+ end
115
+ end
116
+
117
+ context 'which specifies the amqps scheme' do
118
+ before { config[:uri] = 'amqps://guest:guest@127.0.0.1/' }
119
+
120
+ it 'utilises TLS' do
121
+ expect(Hutch::Adapter).to receive(:new).with(
122
+ hash_including(tls: true)
123
+ ).and_return(instance_double('Hutch::Adapter', start: nil))
124
+
125
+ broker.open_connection
126
+ end
127
+ end
128
+ end
129
+ end
130
+
131
+ describe '#open_connection!' do
132
+ it 'sets the #connection to #open_connection' do
133
+ connection = double('connection').as_null_object
134
+
135
+ expect(broker).to receive(:open_connection).and_return(connection)
136
+
137
+ broker.open_connection!
138
+
139
+ expect(broker.connection).to eq(connection)
140
+ end
141
+ end
142
+
143
+ describe '#open_channel', rabbitmq: true do
144
+ before { broker.open_connection! }
145
+ after { broker.disconnect }
146
+
147
+ describe 'return value' do
148
+ subject { broker.open_channel }
149
+
150
+ it(nil, adapter: :bunny) { is_expected.to be_a Bunny::Channel }
151
+ it(nil, adapter: :march_hare) { is_expected.to be_a MarchHare::Channel }
152
+ end
153
+
154
+ it 'does not set #channel' do
155
+ broker.open_channel
156
+ expect(broker.channel).to be_nil
157
+ end
158
+
159
+ context 'with channel_prefetch set' do
160
+ let(:prefetch_value) { 1 }
161
+ before { config[:channel_prefetch] = prefetch_value }
162
+
163
+ it "set's channel's prefetch", adapter: :bunny do
164
+ expect_any_instance_of(Bunny::Channel).to receive(:prefetch).with(prefetch_value)
165
+ broker.open_channel
166
+ end
167
+
168
+ it "set's channel's prefetch", adapter: :march_hare do
169
+ expect_any_instance_of(MarchHare::Channel).to receive(:prefetch=).with(prefetch_value)
170
+ broker.open_channel
171
+ end
172
+ end
173
+
174
+ context 'with force_publisher_confirms set' do
175
+ let(:force_publisher_confirms_value) { true }
176
+ before { config[:force_publisher_confirms] = force_publisher_confirms_value }
177
+
178
+ it 'waits for confirmation', adapter: :bunny do
179
+ expect_any_instance_of(Bunny::Channel).to receive(:confirm_select)
180
+ broker.open_channel
181
+ end
182
+
183
+ it 'waits for confirmation', adapter: :march_hare do
184
+ expect_any_instance_of(MarchHare::Channel).to receive(:confirm_select)
185
+ broker.open_channel
186
+ end
187
+ end
188
+ end
189
+
190
+ describe '#open_channel!' do
191
+ it 'sets the #channel to #open_channel' do
192
+ channel = double('channel').as_null_object
193
+
194
+ expect(broker).to receive(:open_channel).and_return(channel)
195
+
196
+ broker.open_channel!
197
+
198
+ expect(broker.channel).to eq(channel)
199
+ end
200
+ end
201
+
202
+ describe '#declare_exchange' do
203
+ before do
204
+ broker.open_connection!
205
+ broker.open_channel!
206
+ end
207
+ after { broker.disconnect }
208
+
209
+ describe 'return value' do
210
+ subject { broker.declare_exchange }
211
+
212
+ it(nil, adapter: :bunny) { is_expected.to be_a Bunny::Exchange }
213
+ it(nil, adapter: :march_hare) { is_expected.to be_a MarchHare::Exchange }
214
+ end
215
+
216
+ it 'does not set #exchange' do
217
+ broker.declare_exchange
218
+ expect(broker.exchange).to be_nil
219
+ end
220
+ end
221
+
222
+ describe '#declare_exchange!' do
223
+ it 'sets the #exchange to #declare_exchange' do
224
+ exchange = double('exchange').as_null_object
225
+
226
+ expect(broker).to receive(:declare_exchange).and_return(exchange)
227
+
228
+ broker.declare_exchange!
229
+
230
+ expect(broker.exchange).to eq(exchange)
231
+ end
232
+ end
233
+
234
+ describe '#set_up_api_connection', rabbitmq: true do
235
+ context 'with valid details' do
236
+ before { broker.set_up_api_connection }
237
+ after { broker.disconnect }
238
+
239
+ describe '#api_client' do
240
+ subject { broker.api_client }
241
+ it { is_expected.to be_a CarrotTop }
242
+ end
243
+ end
244
+
245
+ context 'when given invalid details' do
246
+ before { config[:mq_api_host] = 'notarealhost' }
247
+ after { broker.disconnect }
248
+ let(:set_up_api_connection) { broker.set_up_api_connection }
249
+
250
+ specify { expect { broker.set_up_api_connection }.to raise_error(StandardError) }
251
+ end
252
+ end
253
+
254
+ describe '#queue' do
255
+ let(:channel) { double('Channel') }
256
+ let(:arguments) { { foo: :bar } }
257
+ before { allow(broker).to receive(:channel) { channel } }
258
+
259
+ it 'applies a global namespace' do
260
+ config[:namespace] = 'mirror-all.service'
261
+ expect(broker.channel).to receive(:queue) do |*args|
262
+ args.first == ''
263
+ args.last == arguments
264
+ end
265
+ broker.queue('test'.freeze, arguments: arguments)
266
+ end
267
+ end
268
+
269
+ describe '#bindings', rabbitmq: true do
270
+ around { |example| broker.connect { example.run } }
271
+ subject { broker.bindings }
272
+
273
+ context 'with no bindings' do
274
+ describe '#keys' do
275
+ subject { super().keys }
276
+ it { is_expected.not_to include 'test' }
277
+ end
278
+ end
279
+
280
+ context 'with a binding' do
281
+ around do |example|
282
+ queue = broker.queue('test').bind(broker.exchange, routing_key: 'key')
283
+ example.run
284
+ queue.unbind(broker.exchange, routing_key: 'key').delete
285
+ end
286
+
287
+ it { is_expected.to include({ 'test' => ['key'] }) }
288
+ end
289
+ end
290
+
291
+ describe '#bind_queue' do
292
+
293
+ around { |example| broker.connect(host: "127.0.0.1") { example.run } }
294
+
295
+ let(:routing_keys) { %w( a b c ) }
296
+ let(:queue) { double('Queue', bind: nil, unbind: nil, name: 'consumer') }
297
+ before { allow(broker).to receive(:bindings).and_return('consumer' => ['d']) }
298
+
299
+ it 'calls bind for each routing key' do
300
+ routing_keys.each do |key|
301
+ expect(queue).to receive(:bind).with(broker.exchange, routing_key: key)
302
+ end
303
+ broker.bind_queue(queue, routing_keys)
304
+ end
305
+
306
+ it 'calls unbind for each redundant existing binding' do
307
+ expect(queue).to receive(:unbind).with(broker.exchange, routing_key: 'd')
308
+ broker.bind_queue(queue, routing_keys)
309
+ end
310
+
311
+ context '(rabbitmq integration test)', rabbitmq: true do
312
+ let(:queue) { broker.queue('consumer') }
313
+ let(:routing_key) { 'key' }
314
+
315
+ before { allow(broker).to receive(:bindings).and_call_original }
316
+ before { queue.bind(broker.exchange, routing_key: 'redundant-key') }
317
+ after { queue.unbind(broker.exchange, routing_key: routing_key).delete }
318
+
319
+ it 'results in the correct bindings' do
320
+ broker.bind_queue(queue, [routing_key])
321
+ expect(broker.bindings).to include({ queue.name => [routing_key] })
322
+ end
323
+ end
324
+ end
325
+
326
+ describe '#stop', adapter: :bunny do
327
+ let(:thread_1) { double('Thread') }
328
+ let(:thread_2) { double('Thread') }
329
+ let(:work_pool) { double('Bunny::ConsumerWorkPool') }
330
+ let(:config) { { graceful_exit_timeout: 2 } }
331
+
332
+ before do
333
+ allow(broker).to receive(:channel_work_pool).and_return(work_pool)
334
+ end
335
+
336
+ it 'gracefully stops the work pool' do
337
+ expect(work_pool).to receive(:shutdown)
338
+ expect(work_pool).to receive(:join).with(2)
339
+ expect(work_pool).to receive(:kill)
340
+
341
+ broker.stop
342
+ end
343
+ end
344
+
345
+ describe '#stop', adapter: :march_hare do
346
+ let(:channel) { double('MarchHare::Channel')}
347
+
348
+ before do
349
+ allow(broker).to receive(:channel).and_return(channel)
350
+ end
351
+
352
+ it 'gracefully stops the channel' do
353
+ expect(channel).to receive(:close)
354
+
355
+ broker.stop
356
+ end
357
+ end
358
+
359
+ describe '#publish' do
360
+ context 'with a valid connection' do
361
+ before { broker.set_up_amqp_connection }
362
+ after { broker.disconnect }
363
+
364
+ it 'publishes to the exchange' do
365
+ expect(broker.exchange).to receive(:publish).once
366
+ broker.publish('test.key', {key: "value"})
367
+ end
368
+
369
+ it 'sets default properties' do
370
+ expect(broker.exchange).to receive(:publish).with(
371
+ JSON.dump({key: "value"}),
372
+ hash_including(
373
+ persistent: true,
374
+ routing_key: 'test.key',
375
+ content_type: 'application/json'
376
+ )
377
+ )
378
+
379
+ broker.publish('test.key', {key: "value"})
380
+ end
381
+
382
+ it 'allows passing message properties' do
383
+ expect(broker.exchange).to receive(:publish).once
384
+ broker.publish('test.key', {key: "value"}, {expiration: "2000", persistent: false})
385
+ end
386
+
387
+ context 'when there are global properties' do
388
+ context 'as a hash' do
389
+ before do
390
+ allow(Hutch).to receive(:global_properties).and_return(app_id: 'app')
391
+ end
392
+
393
+ it 'merges the properties' do
394
+ expect(broker.exchange).
395
+ to receive(:publish).with('{"key":"value"}', hash_including(app_id: 'app'))
396
+ broker.publish('test.key', {key: "value"})
397
+ end
398
+ end
399
+
400
+ context 'as a callable object' do
401
+ before do
402
+ allow(Hutch).to receive(:global_properties).and_return(proc { { app_id: 'app' } })
403
+ end
404
+
405
+ it 'calls the proc and merges the properties' do
406
+ expect(broker.exchange).
407
+ to receive(:publish).with('{"key":"value"}', hash_including(app_id: 'app'))
408
+ broker.publish('test.key', {key: "value"})
409
+ end
410
+ end
411
+ end
412
+
413
+ context 'with force_publisher_confirms not set in the config' do
414
+ it 'does not wait for confirms on the channel', adapter: :bunny do
415
+ expect_any_instance_of(Bunny::Channel).
416
+ to_not receive(:wait_for_confirms)
417
+ broker.publish('test.key', {key: "value"})
418
+ end
419
+
420
+ it 'does not wait for confirms on the channel', adapter: :march_hare do
421
+ expect_any_instance_of(MarchHare::Channel).
422
+ to_not receive(:wait_for_confirms)
423
+ broker.publish('test.key', {key: "value"})
424
+ end
425
+ end
426
+
427
+ context 'with force_publisher_confirms set in the config' do
428
+ let(:force_publisher_confirms_value) { true }
429
+
430
+ before do
431
+ config[:force_publisher_confirms] = force_publisher_confirms_value
432
+ end
433
+
434
+ it 'waits for confirms on the channel', adapter: :bunny do
435
+ expect_any_instance_of(Bunny::Channel).
436
+ to receive(:wait_for_confirms)
437
+ broker.publish('test.key', {key: "value"})
438
+ end
439
+
440
+ it 'waits for confirms on the channel', adapter: :march_hare do
441
+ expect_any_instance_of(MarchHare::Channel).
442
+ to receive(:wait_for_confirms)
443
+ broker.publish('test.key', {key: "value"})
444
+ end
445
+ end
446
+ end
447
+
448
+ context 'without a valid connection' do
449
+ before { broker.set_up_amqp_connection; broker.disconnect }
450
+
451
+ it 'raises an exception' do
452
+ expect { broker.publish('test.key', {key: "value"}) }.
453
+ to raise_exception(Hutch::PublishError)
454
+ end
455
+
456
+ it 'logs an error' do
457
+ expect(broker.logger).to receive(:error)
458
+ broker.publish('test.key', {key: "value"}) rescue nil
459
+ end
460
+ end
461
+ end
462
+ end
@@ -0,0 +1,93 @@
1
+ require 'hutch/cli'
2
+ require 'tempfile'
3
+
4
+ describe Hutch::CLI do
5
+ let(:cli) { Hutch::CLI.new }
6
+
7
+ describe "#start_work_loop" do
8
+ context "connection error during setup" do
9
+ let(:error) { Hutch::ConnectionError.new }
10
+ it "gets reported using error handlers" do
11
+ allow(Hutch).to receive(:connect).and_raise(error)
12
+ Hutch::Config[:error_handlers].each do |backend|
13
+ expect(backend).to receive(:handle_setup_exception).with(error)
14
+ end
15
+ cli.start_work_loop
16
+ end
17
+ end
18
+ end
19
+
20
+ describe "#parse_options" do
21
+ context "--config" do
22
+ context "when the config file does not exist" do
23
+ let(:file) { "/path/to/nonexistant/file" }
24
+ before { allow(STDERR).to receive(:write) }
25
+
26
+ it "bails" do
27
+ expect {
28
+ cli.parse_options(["--config=#{file}"])
29
+ }.to raise_error SystemExit, "Config file '/path/to/nonexistant/file' not found"
30
+ end
31
+ end
32
+
33
+ context "when the config file exists" do
34
+ let(:file) do
35
+ Tempfile.new("hutch-test-config.yaml").to_path
36
+ end
37
+
38
+ it "parses the config" do
39
+ expect(Hutch::Config).to receive(:load_from_file)
40
+ cli.parse_options(["--config=#{file}"])
41
+ end
42
+ end
43
+ end
44
+
45
+ context "--mq-tls-key" do
46
+ context "when the keyfile file does not exist" do
47
+ let(:file) { "/path/to/nonexistant/file" }
48
+ before { allow(STDERR).to receive(:write) }
49
+
50
+ it "bails" do
51
+ expect {
52
+ cli.parse_options(["--mq-tls-key=#{file}"])
53
+ }.to raise_error SystemExit, "Private key file '/path/to/nonexistant/file' not found"
54
+ end
55
+ end
56
+
57
+ context "when the keyfile file exists" do
58
+ let(:file) do
59
+ Tempfile.new("hutch-test-key.pem").to_path
60
+ end
61
+
62
+ it "sets mq_tls_key to the file" do
63
+ expect(Hutch::Config).to receive(:mq_tls_key=)
64
+ cli.parse_options(["--mq-tls-key=#{file}"])
65
+ end
66
+ end
67
+ end
68
+
69
+ context "--mq-tls-cert" do
70
+ context "when the certfile file does not exist" do
71
+ let(:file) { "/path/to/nonexistant/file" }
72
+ before { allow(STDERR).to receive(:write) }
73
+
74
+ it "bails" do
75
+ expect {
76
+ cli.parse_options(["--mq-tls-cert=#{file}"])
77
+ }.to raise_error SystemExit, "Certificate file '/path/to/nonexistant/file' not found"
78
+ end
79
+ end
80
+
81
+ context "when the certfile file exists" do
82
+ let(:file) do
83
+ Tempfile.new("hutch-test-cert.pem").to_path
84
+ end
85
+
86
+ it "sets mq_tls_cert to the file" do
87
+ expect(Hutch::Config).to receive(:mq_tls_cert=)
88
+ cli.parse_options(["--mq-tls-cert=#{file}"])
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end