hutch 0.18.0 → 1.1.0

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 (73) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +3 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +20 -8
  5. data/.yardopts +5 -0
  6. data/CHANGELOG.md +466 -2
  7. data/Gemfile +18 -4
  8. data/Guardfile +13 -4
  9. data/LICENSE +2 -1
  10. data/README.md +397 -32
  11. data/Rakefile +8 -1
  12. data/bin/ci/before_build.sh +20 -0
  13. data/bin/ci/install_on_debian.sh +46 -0
  14. data/hutch.gemspec +6 -7
  15. data/lib/hutch/acknowledgements/base.rb +16 -0
  16. data/lib/hutch/acknowledgements/nack_on_all_failures.rb +19 -0
  17. data/lib/hutch/adapters/march_hare.rb +1 -1
  18. data/lib/hutch/broker.rb +127 -103
  19. data/lib/hutch/cli.rb +66 -25
  20. data/lib/hutch/config.rb +230 -55
  21. data/lib/hutch/consumer.rb +42 -3
  22. data/lib/hutch/error_handlers/airbrake.rb +44 -16
  23. data/lib/hutch/error_handlers/base.rb +15 -0
  24. data/lib/hutch/error_handlers/bugsnag.rb +30 -0
  25. data/lib/hutch/error_handlers/honeybadger.rb +33 -18
  26. data/lib/hutch/error_handlers/logger.rb +12 -6
  27. data/lib/hutch/error_handlers/rollbar.rb +28 -0
  28. data/lib/hutch/error_handlers/sentry.rb +15 -12
  29. data/lib/hutch/error_handlers/sentry_raven.rb +31 -0
  30. data/lib/hutch/error_handlers.rb +3 -0
  31. data/lib/hutch/exceptions.rb +8 -1
  32. data/lib/hutch/logging.rb +5 -5
  33. data/lib/hutch/message.rb +2 -4
  34. data/lib/hutch/publisher.rb +75 -0
  35. data/lib/hutch/serializers/identity.rb +19 -0
  36. data/lib/hutch/serializers/json.rb +22 -0
  37. data/lib/hutch/tracers/datadog.rb +17 -0
  38. data/lib/hutch/tracers.rb +1 -0
  39. data/lib/hutch/version.rb +1 -2
  40. data/lib/hutch/waiter.rb +104 -0
  41. data/lib/hutch/worker.rb +81 -75
  42. data/lib/hutch.rb +15 -6
  43. data/lib/yard-settings/handler.rb +38 -0
  44. data/lib/yard-settings/yard-settings.rb +2 -0
  45. data/spec/hutch/broker_spec.rb +162 -77
  46. data/spec/hutch/cli_spec.rb +16 -3
  47. data/spec/hutch/config_spec.rb +121 -22
  48. data/spec/hutch/consumer_spec.rb +82 -4
  49. data/spec/hutch/error_handlers/airbrake_spec.rb +25 -10
  50. data/spec/hutch/error_handlers/bugsnag_spec.rb +55 -0
  51. data/spec/hutch/error_handlers/honeybadger_spec.rb +24 -2
  52. data/spec/hutch/error_handlers/logger_spec.rb +14 -1
  53. data/spec/hutch/error_handlers/rollbar_spec.rb +45 -0
  54. data/spec/hutch/error_handlers/sentry_raven_spec.rb +37 -0
  55. data/spec/hutch/error_handlers/sentry_spec.rb +21 -2
  56. data/spec/hutch/logger_spec.rb +12 -6
  57. data/spec/hutch/message_spec.rb +2 -2
  58. data/spec/hutch/serializers/json_spec.rb +17 -0
  59. data/spec/hutch/tracers/datadog_spec.rb +44 -0
  60. data/spec/hutch/waiter_spec.rb +51 -0
  61. data/spec/hutch/worker_spec.rb +89 -5
  62. data/spec/spec_helper.rb +7 -5
  63. data/templates/default/class/html/settings.erb +0 -0
  64. data/templates/default/class/setup.rb +4 -0
  65. data/templates/default/fulldoc/html/css/hutch.css +13 -0
  66. data/templates/default/layout/html/setup.rb +7 -0
  67. data/templates/default/method_details/html/settings.erb +5 -0
  68. data/templates/default/method_details/setup.rb +4 -0
  69. data/templates/default/method_details/text/settings.erb +0 -0
  70. data/templates/default/module/html/settings.erb +40 -0
  71. data/templates/default/module/setup.rb +4 -0
  72. metadata +62 -43
  73. data/circle.yml +0 -3
@@ -0,0 +1,38 @@
1
+ # :nodoc:
2
+ class SettingsHandlerBase < YARD::Handlers::Ruby::Base
3
+ handles method_call :string_setting
4
+ handles method_call :number_setting
5
+ handles method_call :boolean_setting
6
+
7
+ namespace_only
8
+
9
+ def process
10
+ name = statement.parameters.first.jump(:tstring_content, :ident).source
11
+ object = YARD::CodeObjects::MethodObject.new(namespace, name)
12
+ register(object)
13
+
14
+ # Modify the code object for the new instance method
15
+ object.dynamic = true
16
+ # Add custom metadata to the object
17
+ object['custom_field'] = '(Found using method_missing)'
18
+
19
+ # Module-level configuration notes
20
+ hutch_config = YARD::CodeObjects::ModuleObject.new(:root, "Hutch::Config")
21
+ collection_name = statement.first.first
22
+ default_value = statement.parameters[1].jump(:tstring_content, :ident).source
23
+
24
+ (hutch_config['setting_rows'] ||= []) << {
25
+ name: name,
26
+ default_value: default_value,
27
+ type: collection_name.sub('_setting', '').capitalize,
28
+ description: object.docstring,
29
+ first_line_of_description: first_line_of_description(object)
30
+ }
31
+ end
32
+
33
+ def first_line_of_description(object)
34
+ return '' if object.docstring.blank?
35
+
36
+ object.docstring.lines.first
37
+ end
38
+ end
@@ -0,0 +1,2 @@
1
+ YARD::Templates::Engine.register_template_path(File.dirname(__FILE__) + '/../../templates')
2
+ require File.join(File.dirname(__FILE__), 'handler') if RUBY19
@@ -2,8 +2,16 @@ require 'spec_helper'
2
2
  require 'hutch/broker'
3
3
 
4
4
  describe Hutch::Broker do
5
- let(:config) { deep_copy(Hutch::Config.user_config) }
6
- subject(:broker) { Hutch::Broker.new(config) }
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) }
7
15
 
8
16
  describe '#connect' do
9
17
  before { allow(broker).to receive(:set_up_amqp_connection) }
@@ -53,93 +61,183 @@ describe Hutch::Broker do
53
61
  end
54
62
  end
55
63
 
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 }
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
60
69
 
61
- describe '#connection', adapter: :bunny do
62
- subject { super().connection }
63
- it { is_expected.to be_a Hutch::Adapters::BunnyAdapter }
64
- end
70
+ broker.set_up_amqp_connection
71
+ end
72
+ end
65
73
 
66
- describe '#connection', adapter: :march_hare do
67
- subject { super().connection }
68
- it { is_expected.to be_a Hutch::Adapters::MarchHareAdapter }
69
- end
74
+ describe '#open_connection', rabbitmq: true do
75
+ describe 'return value' do
76
+ subject { broker.open_connection }
77
+ after { subject.close }
70
78
 
71
- describe '#channel', adapter: :bunny do
72
- subject { super().channel }
73
- it { is_expected.to be_a Bunny::Channel }
74
- end
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
75
82
 
76
- describe '#channel', adapter: :march_hare do
77
- subject { super().channel }
78
- it { is_expected.to be_a MarchHare::Channel }
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
79
105
  end
80
106
 
81
- describe '#exchange', adapter: :bunny do
82
- subject { super().exchange }
83
- it { is_expected.to be_a Bunny::Exchange }
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
84
115
  end
85
116
 
86
- describe '#exchange', adapter: :march_hare do
87
- subject { super().exchange }
88
- it { is_expected.to be_a MarchHare::Exchange }
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, port: 5671)
123
+ ).and_return(instance_double('Hutch::Adapter', start: nil))
124
+
125
+ broker.open_connection
126
+ end
89
127
  end
90
128
  end
129
+ end
91
130
 
92
- context 'when given invalid details' do
93
- before { config[:mq_host] = 'notarealhost' }
94
- let(:set_up_amqp_connection) { ->{ broker.set_up_amqp_connection } }
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!
95
138
 
96
- specify { expect(set_up_amqp_connection).to raise_error }
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
97
157
  end
98
158
 
99
159
  context 'with channel_prefetch set' do
100
160
  let(:prefetch_value) { 1 }
101
161
  before { config[:channel_prefetch] = prefetch_value }
102
- after { broker.disconnect }
103
162
 
104
163
  it "set's channel's prefetch", adapter: :bunny do
105
- expect_any_instance_of(Bunny::Channel).
106
- to receive(:prefetch).with(prefetch_value)
107
- broker.set_up_amqp_connection
164
+ expect_any_instance_of(Bunny::Channel).to receive(:prefetch).with(prefetch_value)
165
+ broker.open_channel
108
166
  end
109
167
 
110
168
  it "set's channel's prefetch", adapter: :march_hare do
111
- expect_any_instance_of(MarchHare::Channel).
112
- to receive(:prefetch=).with(prefetch_value)
113
- broker.set_up_amqp_connection
169
+ expect_any_instance_of(MarchHare::Channel).to receive(:prefetch=).with(prefetch_value)
170
+ broker.open_channel
114
171
  end
115
172
  end
116
173
 
117
174
  context 'with force_publisher_confirms set' do
118
175
  let(:force_publisher_confirms_value) { true }
119
176
  before { config[:force_publisher_confirms] = force_publisher_confirms_value }
120
- after { broker.disconnect }
121
177
 
122
178
  it 'waits for confirmation', adapter: :bunny do
123
- expect_any_instance_of(Bunny::Channel).
124
- to receive(:confirm_select)
125
- broker.set_up_amqp_connection
179
+ expect_any_instance_of(Bunny::Channel).to receive(:confirm_select)
180
+ broker.open_channel
126
181
  end
127
182
 
128
183
  it 'waits for confirmation', adapter: :march_hare do
129
- expect_any_instance_of(MarchHare::Channel).
130
- to receive(:confirm_select)
131
- broker.set_up_amqp_connection
184
+ expect_any_instance_of(MarchHare::Channel).to receive(:confirm_select)
185
+ broker.open_channel
132
186
  end
133
187
  end
134
188
  end
135
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
+
136
234
  describe '#set_up_api_connection', rabbitmq: true do
137
235
  context 'with valid details' do
138
236
  before { broker.set_up_api_connection }
139
237
  after { broker.disconnect }
140
238
 
141
239
  describe '#api_client' do
142
- subject { super().api_client }
240
+ subject { broker.api_client }
143
241
  it { is_expected.to be_a CarrotTop }
144
242
  end
145
243
  end
@@ -149,7 +247,7 @@ describe Hutch::Broker do
149
247
  after { broker.disconnect }
150
248
  let(:set_up_api_connection) { ->{ broker.set_up_api_connection } }
151
249
 
152
- specify { expect(set_up_api_connection).to raise_error }
250
+ specify { expect(set_up_api_connection).to raise_error(StandardError) }
153
251
  end
154
252
  end
155
253
 
@@ -192,7 +290,7 @@ describe Hutch::Broker do
192
290
 
193
291
  describe '#bind_queue' do
194
292
 
195
- around { |example| broker.connect { example.run } }
293
+ around { |example| broker.connect(host: "127.0.0.1") { example.run } }
196
294
 
197
295
  let(:routing_keys) { %w( a b c ) }
198
296
  let(:queue) { double('Queue', bind: nil, unbind: nil, name: 'consumer') }
@@ -225,21 +323,6 @@ describe Hutch::Broker do
225
323
  end
226
324
  end
227
325
 
228
- describe '#wait_on_threads' do
229
- let(:thread) { double('Thread') }
230
- before { allow(broker).to receive(:work_pool_threads).and_return(threads) }
231
-
232
- context 'when all threads finish within the timeout' do
233
- let(:threads) { [double(join: thread), double(join: thread)] }
234
- specify { expect(broker.wait_on_threads(1)).to be_truthy }
235
- end
236
-
237
- context 'when timeout expires for one thread' do
238
- let(:threads) { [double(join: thread), double(join: nil)] }
239
- specify { expect(broker.wait_on_threads(1)).to be_falsey }
240
- end
241
- end
242
-
243
326
  describe '#stop', adapter: :bunny do
244
327
  let(:thread_1) { double('Thread') }
245
328
  let(:thread_2) { double('Thread') }
@@ -280,12 +363,12 @@ describe Hutch::Broker do
280
363
 
281
364
  it 'publishes to the exchange' do
282
365
  expect(broker.exchange).to receive(:publish).once
283
- broker.publish('test.key', 'message')
366
+ broker.publish('test.key', {key: "value"})
284
367
  end
285
368
 
286
369
  it 'sets default properties' do
287
370
  expect(broker.exchange).to receive(:publish).with(
288
- JSON.dump("message"),
371
+ JSON.dump({key: "value"}),
289
372
  hash_including(
290
373
  persistent: true,
291
374
  routing_key: 'test.key',
@@ -293,12 +376,12 @@ describe Hutch::Broker do
293
376
  )
294
377
  )
295
378
 
296
- broker.publish('test.key', 'message')
379
+ broker.publish('test.key', {key: "value"})
297
380
  end
298
381
 
299
382
  it 'allows passing message properties' do
300
383
  expect(broker.exchange).to receive(:publish).once
301
- broker.publish('test.key', 'message', {expiration: "2000", persistent: false})
384
+ broker.publish('test.key', {key: "value"}, {expiration: "2000", persistent: false})
302
385
  end
303
386
 
304
387
  context 'when there are global properties' do
@@ -309,8 +392,8 @@ describe Hutch::Broker do
309
392
 
310
393
  it 'merges the properties' do
311
394
  expect(broker.exchange).
312
- to receive(:publish).with('"message"', hash_including(app_id: 'app'))
313
- broker.publish('test.key', 'message')
395
+ to receive(:publish).with('{"key":"value"}', hash_including(app_id: 'app'))
396
+ broker.publish('test.key', {key: "value"})
314
397
  end
315
398
  end
316
399
 
@@ -321,8 +404,8 @@ describe Hutch::Broker do
321
404
 
322
405
  it 'calls the proc and merges the properties' do
323
406
  expect(broker.exchange).
324
- to receive(:publish).with('"message"', hash_including(app_id: 'app'))
325
- broker.publish('test.key', 'message')
407
+ to receive(:publish).with('{"key":"value"}', hash_including(app_id: 'app'))
408
+ broker.publish('test.key', {key: "value"})
326
409
  end
327
410
  end
328
411
  end
@@ -331,13 +414,13 @@ describe Hutch::Broker do
331
414
  it 'does not wait for confirms on the channel', adapter: :bunny do
332
415
  expect_any_instance_of(Bunny::Channel).
333
416
  to_not receive(:wait_for_confirms)
334
- broker.publish('test.key', 'message')
417
+ broker.publish('test.key', {key: "value"})
335
418
  end
336
419
 
337
420
  it 'does not wait for confirms on the channel', adapter: :march_hare do
338
421
  expect_any_instance_of(MarchHare::Channel).
339
422
  to_not receive(:wait_for_confirms)
340
- broker.publish('test.key', 'message')
423
+ broker.publish('test.key', {key: "value"})
341
424
  end
342
425
  end
343
426
 
@@ -351,26 +434,28 @@ describe Hutch::Broker do
351
434
  it 'waits for confirms on the channel', adapter: :bunny do
352
435
  expect_any_instance_of(Bunny::Channel).
353
436
  to receive(:wait_for_confirms)
354
- broker.publish('test.key', 'message')
437
+ broker.publish('test.key', {key: "value"})
355
438
  end
356
439
 
357
440
  it 'waits for confirms on the channel', adapter: :march_hare do
358
441
  expect_any_instance_of(MarchHare::Channel).
359
442
  to receive(:wait_for_confirms)
360
- broker.publish('test.key', 'message')
443
+ broker.publish('test.key', {key: "value"})
361
444
  end
362
445
  end
363
446
  end
364
447
 
365
448
  context 'without a valid connection' do
449
+ before { broker.set_up_amqp_connection; broker.disconnect }
450
+
366
451
  it 'raises an exception' do
367
- expect { broker.publish('test.key', 'message') }.
452
+ expect { broker.publish('test.key', {key: "value"}) }.
368
453
  to raise_exception(Hutch::PublishError)
369
454
  end
370
455
 
371
456
  it 'logs an error' do
372
457
  expect(broker.logger).to receive(:error)
373
- broker.publish('test.key', 'message') rescue nil
458
+ broker.publish('test.key', {key: "value"}) rescue nil
374
459
  end
375
460
  end
376
461
  end
@@ -4,6 +4,19 @@ require 'tempfile'
4
4
  describe Hutch::CLI do
5
5
  let(:cli) { Hutch::CLI.new }
6
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
+
7
20
  describe "#parse_options" do
8
21
  context "--config" do
9
22
  context "when the config file does not exist" do
@@ -13,7 +26,7 @@ describe Hutch::CLI do
13
26
  it "bails" do
14
27
  expect {
15
28
  cli.parse_options(["--config=#{file}"])
16
- }.to raise_error SystemExit
29
+ }.to raise_error SystemExit, "Config file '/path/to/nonexistant/file' not found"
17
30
  end
18
31
  end
19
32
 
@@ -37,7 +50,7 @@ describe Hutch::CLI do
37
50
  it "bails" do
38
51
  expect {
39
52
  cli.parse_options(["--mq-tls-key=#{file}"])
40
- }.to raise_error SystemExit
53
+ }.to raise_error SystemExit, "Private key file '/path/to/nonexistant/file' not found"
41
54
  end
42
55
  end
43
56
 
@@ -61,7 +74,7 @@ describe Hutch::CLI do
61
74
  it "bails" do
62
75
  expect {
63
76
  cli.parse_options(["--mq-tls-cert=#{file}"])
64
- }.to raise_error SystemExit
77
+ }.to raise_error SystemExit, "Certificate file '/path/to/nonexistant/file' not found"
65
78
  end
66
79
  end
67
80
 
@@ -4,23 +4,40 @@ require 'tempfile'
4
4
  describe Hutch::Config do
5
5
  let(:new_value) { 'not-localhost' }
6
6
 
7
+ before do
8
+ Hutch::Config.instance_variable_set(:@config, nil)
9
+ Hutch::Config.initialize
10
+ end
11
+
12
+ after do
13
+ Hutch::Config.instance_variable_set(:@config, nil)
14
+ end
15
+
7
16
  describe '.get' do
8
17
  context 'for valid attributes' do
9
18
  subject { Hutch::Config.get(:mq_host) }
10
19
 
11
20
  context 'with no overridden value' do
12
- it { is_expected.to eq('localhost') }
21
+ it { is_expected.to eq('127.0.0.1') }
13
22
  end
14
23
 
15
24
  context 'with an overridden value' do
16
- before { allow(Hutch::Config).to receive_messages(user_config: { mq_host: new_value }) }
25
+ before do
26
+ Hutch::Config.set(:mq_host, new_value)
27
+ end
28
+
17
29
  it { is_expected.to eq(new_value) }
18
30
  end
19
31
  end
20
32
 
21
33
  context 'for invalid attributes' do
22
- let(:invalid_get) { ->{ Hutch::Config.get(:invalid_attr) } }
23
- specify { expect(invalid_get).to raise_error Hutch::UnknownAttributeError }
34
+ let(:invalid_get) do
35
+ -> { Hutch::Config.get(:invalid_attr) }
36
+ end
37
+
38
+ specify do
39
+ expect(invalid_get).to raise_error Hutch::UnknownAttributeError
40
+ end
24
41
  end
25
42
  end
26
43
 
@@ -32,11 +49,54 @@ describe Hutch::Config do
32
49
  context 'sets value in user config hash' do
33
50
  it { is_expected.to eq(new_value) }
34
51
  end
52
+
53
+ context 'type casting' do
54
+ context 'number attributes' do
55
+ before { Hutch::Config.set(:heartbeat, new_value) }
56
+ subject(:value) { Hutch::Config.user_config[:heartbeat] }
57
+
58
+ let(:new_value) { "0" }
59
+
60
+
61
+ specify 'casts values to integers' do
62
+ expect(value).to eq 0
63
+ end
64
+ end
65
+ end
66
+
67
+ context 'boolean attributes' do
68
+ before { Hutch::Config.set(:autoload_rails, new_value) }
69
+ subject(:value) { Hutch::Config.user_config[:autoload_rails] }
70
+
71
+ let(:new_value) { "t" }
72
+
73
+
74
+ specify 'casts values to booleans' do
75
+ expect(value).to eq true
76
+ end
77
+ end
78
+
79
+ context 'string attributes' do
80
+ before { Hutch::Config.set(:mq_exchange_type, new_value) }
81
+ subject(:value) { Hutch::Config.user_config[:mq_exchange_type] }
82
+
83
+ let(:new_value) { 1 }
84
+
85
+
86
+ specify 'does not perform any typecasting' do
87
+ expect(value).to eq new_value
88
+ end
89
+ end
35
90
  end
36
91
 
37
92
  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 }
93
+ let(:invalid_set) do
94
+ -> { Hutch::Config.set(:invalid_attr, new_value) }
95
+ end
96
+
97
+ specify do
98
+ expect(invalid_set).to raise_error Hutch::UnknownAttributeError
99
+ end
40
100
  end
41
101
  end
42
102
 
@@ -49,9 +109,33 @@ describe Hutch::Config do
49
109
  end
50
110
 
51
111
  context 'for an invalid attribute' do
52
- let(:invalid_getter) { ->{ Hutch::Config.invalid_attr } }
112
+ let(:invalid_getter) { -> { Hutch::Config.invalid_attr } }
53
113
  specify { expect(invalid_getter).to raise_error NoMethodError }
54
114
  end
115
+
116
+ context 'for an ENV-overriden value attribute' do
117
+ around do |example|
118
+ ENV['HUTCH_MQ_HOST'] = 'example.com'
119
+ ENV['HUTCH_MQ_PORT'] = '10001'
120
+ ENV['HUTCH_MQ_TLS'] = 'true'
121
+ example.run
122
+ ENV.delete('HUTCH_MQ_HOST')
123
+ ENV.delete('HUTCH_MQ_PORT')
124
+ ENV.delete('HUTCH_MQ_TLS')
125
+ end
126
+
127
+ it 'returns the override' do
128
+ expect(Hutch::Config.mq_host).to eq 'example.com'
129
+ end
130
+
131
+ it 'returns the override for integers' do
132
+ expect(Hutch::Config.mq_port).to eq 10_001
133
+ end
134
+
135
+ it 'returns the override for booleans' do
136
+ expect(Hutch::Config.mq_tls).to eq true
137
+ end
138
+ end
55
139
  end
56
140
 
57
141
  describe 'a magic setter' do
@@ -63,7 +147,7 @@ describe Hutch::Config do
63
147
  end
64
148
 
65
149
  context 'for an invalid attribute' do
66
- let(:invalid_setter) { ->{ Hutch::Config.invalid_attr = new_value } }
150
+ let(:invalid_setter) { -> { Hutch::Config.invalid_attr = new_value } }
67
151
  specify { expect(invalid_setter).to raise_error NoMethodError }
68
152
  end
69
153
  end
@@ -81,9 +165,9 @@ describe Hutch::Config do
81
165
  context 'when an attribute is invalid' do
82
166
  let(:config_data) { { random_attribute: 'socks' } }
83
167
  it 'raises an error' do
84
- expect {
168
+ expect do
85
169
  Hutch::Config.load_from_file(file)
86
- }.to raise_error(NoMethodError)
170
+ end.to raise_error(NoMethodError)
87
171
  end
88
172
  end
89
173
 
@@ -96,26 +180,21 @@ describe Hutch::Config do
96
180
  expect(Hutch::Config.mq_username).to eq username
97
181
  end
98
182
  end
99
- end
100
183
 
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
184
+ context 'when using ERB' do
185
+ let(:host) { 'localhost' }
186
+ let(:file) do
187
+ Tempfile.new('configs.yaml').tap do |t|
188
+ t.write(config_contents)
189
+ t.rewind
190
+ end
108
191
  end
109
- end
110
-
111
- context 'when using ERb' do
112
192
  let(:config_contents) do
113
193
  <<-YAML
114
194
  mq_host: 'localhost'
115
195
  mq_username: '<%= "calvin" %>'
116
196
  YAML
117
197
  end
118
-
119
198
  it 'loads in the config data' do
120
199
  Hutch::Config.load_from_file(file)
121
200
  expect(Hutch::Config.mq_host).to eq host
@@ -123,4 +202,24 @@ YAML
123
202
  end
124
203
  end
125
204
  end
205
+
206
+ context 'developer ergonomics' do
207
+ it 'will accept strings and symbols as config keys' do
208
+ expect(Hutch::Config.get(:mq_host)).to eq '127.0.0.1'
209
+ expect(Hutch::Config.get('mq_host')).to eq '127.0.0.1'
210
+ end
211
+
212
+ describe 'it will not overwrite existing config' do
213
+ it 'with defaults' do
214
+ expect(Hutch::Config.get(:mq_host)).to eq '127.0.0.1'
215
+ Hutch::Config.initialize
216
+
217
+ Hutch::Config.set(:mq_host, 'example2.com')
218
+
219
+ expect(Hutch::Config.get(:mq_host)).to eq 'example2.com'
220
+ Hutch::Config.initialize
221
+ expect(Hutch::Config.get(:mq_host)).to eq 'example2.com'
222
+ end
223
+ end
224
+ end
126
225
  end