hutch 0.18.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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