shivam 0.0.0-java

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