plumbum 0.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.
@@ -0,0 +1,2122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/sleeping_king_studios/deferred/provider'
4
+
5
+ require 'plumbum/rspec/deferred'
6
+
7
+ module Plumbum::RSpec::Deferred
8
+ # Deferred examples for validating Plumbum::Consumer implementations.
9
+ module ConsumerExamples
10
+ include RSpec::SleepingKingStudios::Deferred::Provider
11
+
12
+ define_method :tools do
13
+ SleepingKingStudios::Tools::Toolbelt.instance
14
+ end
15
+
16
+ deferred_context 'with example providers' do
17
+ example_class 'Spec::MutableProvider', Module do |klass|
18
+ klass.include Plumbum::Providers::Singular
19
+
20
+ klass.define_method :initialize do |key:, value:|
21
+ @key = key.to_s
22
+ @value = value
23
+ end
24
+
25
+ klass.attr_writer :value
26
+ end
27
+ end
28
+
29
+ deferred_context 'when an included module defines dependencies' do
30
+ let(:included_dependencies) { %w[context] }
31
+
32
+ before(:example) do
33
+ included_module.plumbum_dependency('context')
34
+ end
35
+ end
36
+
37
+ deferred_context 'when an included module defines providers' do
38
+ let(:context_provider) do
39
+ Plumbum::OneProvider.new(:context, value: Object.new.freeze)
40
+ end
41
+ let(:expected_providers) do
42
+ super() << context_provider
43
+ end
44
+
45
+ before(:example) do
46
+ included_module.plumbum_provider context_provider
47
+ end
48
+ end
49
+
50
+ deferred_context 'when the class defines dependencies' do
51
+ let(:class_dependencies) { %w[env tools] }
52
+
53
+ before(:example) do
54
+ described_class.plumbum_dependency('env')
55
+ described_class.plumbum_dependency('tools')
56
+ end
57
+ end
58
+
59
+ deferred_context 'when the class defines providers' do
60
+ let(:config_provider) do
61
+ Plumbum::ManyProvider.new(
62
+ values: { env: 'test', repository: { books: [] }, tools: {} }
63
+ )
64
+ end
65
+ let(:tools_provider) do
66
+ Plumbum::OneProvider.new(:tools, value: { string_tools: {} })
67
+ end
68
+ let(:expected_providers) do
69
+ super() << tools_provider << config_provider
70
+ end
71
+
72
+ before(:example) do
73
+ described_class.plumbum_provider config_provider
74
+ described_class.plumbum_provider tools_provider
75
+ end
76
+ end
77
+
78
+ deferred_context 'when the parent class defines dependencies' do
79
+ let(:parent_dependencies) { %w[repository tools] }
80
+
81
+ before(:example) do
82
+ parent_class.plumbum_dependency('repository')
83
+ parent_class.plumbum_dependency('tools')
84
+ end
85
+ end
86
+
87
+ deferred_context 'when the parent class defines providers' do
88
+ let(:options_provider) do
89
+ Plumbum::OneProvider.new(:options, value: { key: 'value' })
90
+ end
91
+ let(:expected_providers) do
92
+ super() << options_provider
93
+ end
94
+
95
+ before(:example) do
96
+ parent_class.plumbum_provider options_provider
97
+ end
98
+ end
99
+
100
+ deferred_examples 'should implement the Consumer class methods' do
101
+ describe '.plumbum_dependency' do
102
+ deferred_examples 'should define the delegated method' \
103
+ do |scoped: false, visibility: :public|
104
+ if scoped
105
+ it 'should not define the unscoped delegated method' do
106
+ expect(subject).not_to respond_to(delegated_name)
107
+
108
+ define_dependencies
109
+
110
+ expect(subject).not_to respond_to(delegated_name)
111
+ end
112
+ end
113
+
114
+ if visibility == :private
115
+ it 'should define the private delegated method',
116
+ :aggregate_failures \
117
+ do
118
+ expect(subject).not_to respond_to(method_name)
119
+
120
+ define_dependencies
121
+
122
+ expect(subject).not_to respond_to(method_name)
123
+ expect(subject)
124
+ .to respond_to(method_name, true)
125
+ .with_unlimited_arguments
126
+ .and_any_keywords
127
+ .and_a_block
128
+ end
129
+ else
130
+ it 'should define the delegated method', :aggregate_failures do
131
+ expect(subject).not_to respond_to(method_name)
132
+
133
+ define_dependencies
134
+
135
+ expect(subject)
136
+ .to respond_to(method_name)
137
+ .with_unlimited_arguments
138
+ .and_any_keywords
139
+ .and_a_block
140
+ end
141
+ end
142
+ end
143
+
144
+ deferred_examples 'should define the predicate method' \
145
+ do |scoped: false, visibility: :public|
146
+ if scoped
147
+ it 'should not define the unscoped predicate method' do
148
+ expect(subject).not_to respond_to(predicate_name)
149
+
150
+ define_dependencies
151
+
152
+ expect(subject).not_to respond_to(predicate_name)
153
+ end
154
+ end
155
+
156
+ if visibility == :private
157
+ it 'should define the private predicate method',
158
+ :aggregate_failures \
159
+ do
160
+ expect(subject).not_to respond_to(:"#{method_name}?")
161
+
162
+ define_dependencies
163
+
164
+ expect(subject).not_to respond_to(:"#{method_name}?")
165
+ expect(subject)
166
+ .to respond_to(:"#{method_name}?", true)
167
+ .with(0).arguments
168
+ end
169
+ else
170
+ it 'should define the predicate method', :aggregate_failures do
171
+ expect(subject).not_to respond_to(:"#{method_name}?")
172
+
173
+ define_dependencies
174
+
175
+ expect(subject)
176
+ .to respond_to(:"#{method_name}?")
177
+ .with(0).arguments
178
+ end
179
+ end
180
+ end
181
+
182
+ deferred_examples 'should define the reader method' \
183
+ do |scoped: false, visibility: :public|
184
+ if scoped
185
+ it 'should not define the unscoped reader method' do
186
+ expect(subject).not_to respond_to(reader_name)
187
+
188
+ define_dependencies
189
+
190
+ expect(subject).not_to respond_to(reader_name)
191
+ end
192
+ end
193
+
194
+ if visibility == :private
195
+ it 'should define the private reader method', :aggregate_failures do
196
+ expect(subject).not_to respond_to(method_name)
197
+
198
+ define_dependencies
199
+
200
+ expect(subject).not_to respond_to(method_name)
201
+ expect(subject).to respond_to(method_name, true).with(0).arguments
202
+ end
203
+ else
204
+ it 'should define the reader method', :aggregate_failures do
205
+ expect(subject).not_to respond_to(method_name)
206
+
207
+ define_dependencies
208
+
209
+ expect(subject).to respond_to(method_name).with(0).arguments
210
+ end
211
+ end
212
+ end
213
+
214
+ deferred_examples 'should define dependency' \
215
+ do |dependency_name, allow_as: true, to: nil, visibility: :public|
216
+ let(:expected_key) { (to || dependency_name).to_s.split('.').first }
217
+ let(:reader_name) { dependency_name.to_sym }
218
+ let(:method_name) { reader_name }
219
+
220
+ it 'should add the key to the dependency keys' do
221
+ expect { define_dependencies }.to(
222
+ change { dependency_keys }.to(include(expected_key))
223
+ )
224
+ end
225
+
226
+ include_deferred 'should define the reader method',
227
+ visibility: visibility
228
+
229
+ if allow_as
230
+ describe 'with as: nil' do
231
+ let(:options) { super().merge(as: nil) }
232
+
233
+ it 'should add the key to the dependency keys' do
234
+ expect { define_dependencies }.to(
235
+ change { dependency_keys }.to(include(expected_key))
236
+ )
237
+ end
238
+
239
+ include_deferred 'should define the reader method',
240
+ visibility: visibility
241
+ end
242
+
243
+ describe 'with as: an Object' do
244
+ let(:options) { super().merge(as: Object.new.freeze) }
245
+ let(:error_message) do
246
+ tools
247
+ .assertions
248
+ .error_message_for(
249
+ 'sleeping_king_studios.tools.assertions.name',
250
+ as: :as
251
+ )
252
+ end
253
+
254
+ it 'should raise an exception' do
255
+ expect { define_dependencies }
256
+ .to raise_error ArgumentError, error_message
257
+ end
258
+ end
259
+
260
+ describe 'with as: an empty String' do
261
+ let(:options) { super().merge(as: '') }
262
+ let(:error_message) do
263
+ tools
264
+ .assertions
265
+ .error_message_for(
266
+ 'sleeping_king_studios.tools.assertions.presence',
267
+ as: :as
268
+ )
269
+ end
270
+
271
+ it 'should raise an exception' do
272
+ expect { define_dependencies }
273
+ .to raise_error ArgumentError, error_message
274
+ end
275
+ end
276
+
277
+ describe 'with as: an empty Symbol' do
278
+ let(:options) { super().merge(as: :'') }
279
+ let(:error_message) do
280
+ tools
281
+ .assertions
282
+ .error_message_for(
283
+ 'sleeping_king_studios.tools.assertions.presence',
284
+ as: :as
285
+ )
286
+ end
287
+
288
+ it 'should raise an exception' do
289
+ expect { define_dependencies }
290
+ .to raise_error ArgumentError, error_message
291
+ end
292
+ end
293
+
294
+ describe 'with as: a String' do
295
+ let(:scoped_name) { :"scoped_#{reader_name}" }
296
+ let(:method_name) { scoped_name.to_s }
297
+ let(:options) { super().merge(as: method_name) }
298
+
299
+ it 'should add the key to the dependency keys' do
300
+ expect { define_dependencies }
301
+ .to(change { dependency_keys }.to(include(expected_key)))
302
+ end
303
+
304
+ include_deferred 'should define the reader method',
305
+ scoped: true,
306
+ visibility: visibility
307
+ end
308
+
309
+ describe 'with as: a Symbol' do
310
+ let(:scoped_name) { :"scoped_#{reader_name}" }
311
+ let(:method_name) { scoped_name }
312
+ let(:options) { super().merge(as: method_name) }
313
+
314
+ it 'should add the key to the dependency keys' do
315
+ expect { define_dependencies }
316
+ .to change { dependency_keys }.to(include(expected_key))
317
+ end
318
+
319
+ include_deferred 'should define the reader method',
320
+ scoped: true,
321
+ visibility: visibility
322
+ end
323
+ end
324
+
325
+ describe 'with predicate: true' do
326
+ let(:predicate_name) { :"#{reader_name}?" }
327
+ let(:options) { super().merge(predicate: true) }
328
+
329
+ it 'should add the key to the dependency keys' do
330
+ expect { define_dependencies }
331
+ .to change { dependency_keys }.to(include(expected_key))
332
+ end
333
+
334
+ include_deferred 'should define the predicate method',
335
+ visibility: visibility
336
+
337
+ include_deferred 'should define the reader method',
338
+ visibility: visibility
339
+
340
+ if allow_as
341
+ describe 'with as: value' do
342
+ let(:scoped_name) { :"scoped_#{reader_name}" }
343
+ let(:method_name) { scoped_name.to_s }
344
+ let(:options) { super().merge(as: method_name) }
345
+
346
+ include_deferred 'should define the predicate method',
347
+ scoped: true,
348
+ visibility: visibility
349
+
350
+ include_deferred 'should define the reader method',
351
+ scoped: true,
352
+ visibility: visibility
353
+ end
354
+ end
355
+ end
356
+ end
357
+
358
+ deferred_examples 'should define method dependency' \
359
+ do |dependency_name, allow_as: true, to: nil, visibility: :public|
360
+ let(:expected_key) { (to || dependency_name).to_s.split('.').first }
361
+ let(:delegated_name) { dependency_name.to_sym }
362
+ let(:method_name) { delegated_name }
363
+
364
+ it 'should add the key to the dependency keys' do
365
+ expect { define_dependencies }.to(
366
+ change { dependency_keys }.to(include(expected_key))
367
+ )
368
+ end
369
+
370
+ include_deferred 'should define the delegated method',
371
+ visibility: visibility
372
+
373
+ if allow_as
374
+ describe 'with as: nil' do
375
+ let(:options) { super().merge(as: nil) }
376
+
377
+ it 'should add the key to the dependency keys' do
378
+ expect { define_dependencies }.to(
379
+ change { dependency_keys }.to(include(expected_key))
380
+ )
381
+ end
382
+
383
+ include_deferred 'should define the delegated method',
384
+ visibility: visibility
385
+ end
386
+
387
+ describe 'with as: a String' do
388
+ let(:scoped_name) { :"scoped_#{delegated_name}" }
389
+ let(:method_name) { scoped_name.to_s }
390
+ let(:options) { super().merge(as: method_name) }
391
+
392
+ it 'should add the key to the dependency keys' do
393
+ expect { define_dependencies }
394
+ .to(change { dependency_keys }.to(include(expected_key)))
395
+ end
396
+
397
+ include_deferred 'should define the delegated method',
398
+ scoped: true,
399
+ visibility: visibility
400
+ end
401
+
402
+ describe 'with as: a Symbol' do
403
+ let(:scoped_name) { :"scoped_#{delegated_name}" }
404
+ let(:method_name) { scoped_name.to_sym }
405
+ let(:options) { super().merge(as: method_name) }
406
+
407
+ it 'should add the key to the dependency keys' do
408
+ expect { define_dependencies }
409
+ .to(change { dependency_keys }.to(include(expected_key)))
410
+ end
411
+
412
+ include_deferred 'should define the delegated method',
413
+ scoped: true,
414
+ visibility: visibility
415
+ end
416
+ end
417
+ end
418
+
419
+ let(:keys) { [key] }
420
+ let(:options) { {} }
421
+ let(:expected_keywords) do
422
+ %i[
423
+ as
424
+ default
425
+ memoize
426
+ optional
427
+ predicate
428
+ private
429
+ scope
430
+ ]
431
+ end
432
+
433
+ define_method :define_dependencies do
434
+ described_class.plumbum_dependency(*keys, **options)
435
+ end
436
+
437
+ define_method :dependency_keys do
438
+ described_class.plumbum_dependency_keys(cache: false)
439
+ end
440
+
441
+ define_method :tools do
442
+ SleepingKingStudios::Tools::Toolbelt.instance
443
+ end
444
+
445
+ it 'should define the class method' do
446
+ expect(described_class)
447
+ .to respond_to(:plumbum_dependency)
448
+ .with(1).argument
449
+ .and_unlimited_arguments
450
+ .and_keywords(*expected_keywords)
451
+ end
452
+
453
+ describe 'with nil' do
454
+ let(:error_message) do
455
+ tools
456
+ .assertions
457
+ .error_message_for(
458
+ 'sleeping_king_studios.tools.assertions.presence',
459
+ as: :key
460
+ )
461
+ end
462
+
463
+ it 'should raise an exception' do
464
+ expect { described_class.plumbum_dependency(nil) }
465
+ .to raise_error ArgumentError, error_message
466
+ end
467
+ end
468
+
469
+ describe 'with an Object' do
470
+ let(:error_message) do
471
+ tools
472
+ .assertions
473
+ .error_message_for(
474
+ 'sleeping_king_studios.tools.assertions.name',
475
+ as: :key
476
+ )
477
+ end
478
+
479
+ it 'should raise an exception' do
480
+ expect { described_class.plumbum_dependency(Object.new.freeze) }
481
+ .to raise_error ArgumentError, error_message
482
+ end
483
+ end
484
+
485
+ describe 'with an empty String' do
486
+ let(:error_message) do
487
+ tools
488
+ .assertions
489
+ .error_message_for(
490
+ 'sleeping_king_studios.tools.assertions.presence',
491
+ as: :key
492
+ )
493
+ end
494
+
495
+ it 'should raise an exception' do
496
+ expect { described_class.plumbum_dependency('') }
497
+ .to raise_error ArgumentError, error_message
498
+ end
499
+ end
500
+
501
+ describe 'with an empty Symbol' do
502
+ let(:error_message) do
503
+ tools
504
+ .assertions
505
+ .error_message_for(
506
+ 'sleeping_king_studios.tools.assertions.presence',
507
+ as: :key
508
+ )
509
+ end
510
+
511
+ it 'should raise an exception' do
512
+ expect { described_class.plumbum_dependency(:'') }
513
+ .to raise_error ArgumentError, error_message
514
+ end
515
+ end
516
+
517
+ describe 'with a valid String' do
518
+ let(:key) { 'valid' }
519
+
520
+ include_deferred 'should define dependency', 'valid'
521
+
522
+ it 'should return the reader name' do
523
+ expect(described_class.plumbum_dependency(key)).to be :valid
524
+ end
525
+
526
+ describe 'with as: value' do
527
+ let(:scoped_name) { :scoped_valid }
528
+ let(:method_name) { scoped_name.to_s }
529
+ let(:options) { super().merge(as: method_name) }
530
+
531
+ it 'should return the reader name' do
532
+ expect(described_class.plumbum_dependency(key, as: method_name))
533
+ .to be :scoped_valid
534
+ end
535
+ end
536
+
537
+ describe 'with private: value' do
538
+ let(:options) { super().merge(private: true) }
539
+
540
+ include_deferred 'should define dependency',
541
+ 'valid',
542
+ visibility: :private
543
+ end
544
+
545
+ describe 'with scope: value' do
546
+ let(:options) { super().merge(scope: 'application.data') }
547
+
548
+ include_deferred 'should define dependency',
549
+ 'valid',
550
+ to: 'application.data'
551
+ end
552
+ end
553
+
554
+ describe 'with a valid Symbol' do
555
+ let(:key) { :valid }
556
+
557
+ include_deferred 'should define dependency', 'valid'
558
+
559
+ it 'should return the reader name' do
560
+ expect(described_class.plumbum_dependency(key))
561
+ .to be :valid
562
+ end
563
+
564
+ describe 'with as: value' do
565
+ let(:scoped_name) { :scoped_valid }
566
+ let(:method_name) { scoped_name.to_s }
567
+ let(:options) { super().merge(as: method_name) }
568
+
569
+ it 'should return the reader name' do
570
+ expect(described_class.plumbum_dependency(key, as: method_name))
571
+ .to be :scoped_valid
572
+ end
573
+ end
574
+
575
+ describe 'with private: value' do
576
+ let(:options) { super().merge(private: true) }
577
+
578
+ include_deferred 'should define dependency',
579
+ 'valid',
580
+ visibility: :private
581
+ end
582
+
583
+ describe 'with scope: value' do
584
+ let(:options) { super().merge(scope: 'application.data') }
585
+
586
+ include_deferred 'should define dependency',
587
+ 'valid',
588
+ to: 'application.data'
589
+ end
590
+ end
591
+
592
+ describe 'with a dot-separated String' do
593
+ let(:key) { 'application.tools.object_tools' }
594
+
595
+ include_deferred 'should define dependency',
596
+ :object_tools,
597
+ to: 'application.tools'
598
+
599
+ it 'should return the reader name' do
600
+ expect(described_class.plumbum_dependency(key))
601
+ .to be :object_tools
602
+ end
603
+
604
+ describe 'with as: value' do
605
+ let(:scoped_name) { :scoped_object_tools }
606
+ let(:method_name) { scoped_name.to_s }
607
+ let(:options) { super().merge(as: method_name) }
608
+
609
+ it 'should return the reader name' do
610
+ expect(described_class.plumbum_dependency(key, as: method_name))
611
+ .to be :scoped_object_tools
612
+ end
613
+ end
614
+
615
+ describe 'with private: value' do
616
+ let(:options) { super().merge(private: true) }
617
+
618
+ include_deferred 'should define dependency',
619
+ :object_tools,
620
+ to: 'application.tools',
621
+ visibility: :private
622
+ end
623
+
624
+ describe 'with scope: value' do
625
+ let(:options) { super().merge(scope: 'config') }
626
+
627
+ include_deferred 'should define dependency',
628
+ 'object_tools',
629
+ to: 'config.application.tools'
630
+ end
631
+ end
632
+
633
+ describe 'with a dot-separated Symbol' do
634
+ let(:key) { :'application.tools.object_tools' }
635
+
636
+ include_deferred 'should define dependency',
637
+ :object_tools,
638
+ to: 'application.tools'
639
+
640
+ it 'should return the reader name' do
641
+ expect(described_class.plumbum_dependency(key))
642
+ .to be :object_tools
643
+ end
644
+
645
+ describe 'with as: value' do
646
+ let(:scoped_name) { :scoped_object_tools }
647
+ let(:method_name) { scoped_name.to_s }
648
+ let(:options) { super().merge(as: method_name) }
649
+
650
+ it 'should return the reader name' do
651
+ expect(described_class.plumbum_dependency(key, as: method_name))
652
+ .to be :scoped_object_tools
653
+ end
654
+ end
655
+
656
+ describe 'with private: value' do
657
+ let(:options) { super().merge(private: true) }
658
+
659
+ include_deferred 'should define dependency',
660
+ :object_tools,
661
+ to: 'application.tools',
662
+ visibility: :private
663
+ end
664
+
665
+ describe 'with scope: value' do
666
+ let(:options) { super().merge(scope: 'config') }
667
+
668
+ include_deferred 'should define dependency',
669
+ 'object_tools',
670
+ to: 'config.application.tools'
671
+ end
672
+ end
673
+
674
+ describe 'with multiple invalid keys' do
675
+ let(:keys) do
676
+ [
677
+ 'valid',
678
+ nil,
679
+ 'application.tools.object_tools'
680
+ ]
681
+ end
682
+ let(:error_message) do
683
+ tools
684
+ .assertions
685
+ .error_message_for(
686
+ 'sleeping_king_studios.tools.assertions.presence',
687
+ as: :key
688
+ )
689
+ end
690
+
691
+ it 'should raise an exception' do
692
+ expect { described_class.plumbum_dependency(*keys) }
693
+ .to raise_error ArgumentError, error_message
694
+ end
695
+ end
696
+
697
+ describe 'with multiple valid keys' do
698
+ let(:keys) do
699
+ [
700
+ 'valid',
701
+ 'application.tools.object_tools'
702
+ ]
703
+ end
704
+
705
+ include_deferred 'should define dependency',
706
+ 'valid',
707
+ allow_as: false
708
+
709
+ include_deferred 'should define dependency',
710
+ :object_tools,
711
+ to: 'application.tools',
712
+ allow_as: false
713
+
714
+ describe 'with as: value' do
715
+ let(:method_name) { 'scoped_valid' }
716
+ let(:error_message) do
717
+ 'invalid option :as when providing multiple keys'
718
+ end
719
+
720
+ it 'should raise an exception' do
721
+ expect do
722
+ described_class.plumbum_dependency(*keys, as: method_name)
723
+ end
724
+ .to raise_error ArgumentError, error_message
725
+ end
726
+ end
727
+
728
+ describe 'with private: value' do
729
+ let(:options) { super().merge(private: true) }
730
+
731
+ include_deferred 'should define dependency',
732
+ 'valid',
733
+ allow_as: false,
734
+ visibility: :private
735
+
736
+ include_deferred 'should define dependency',
737
+ :object_tools,
738
+ to: 'application.tools',
739
+ allow_as: false,
740
+ visibility: :private
741
+ end
742
+
743
+ describe 'with scope: value' do
744
+ let(:options) { super().merge(scope: 'config') }
745
+
746
+ include_deferred 'should define dependency',
747
+ 'valid',
748
+ to: 'config',
749
+ allow_as: false
750
+
751
+ include_deferred 'should define dependency',
752
+ :object_tools,
753
+ to: 'config.application.tools',
754
+ allow_as: false
755
+ end
756
+ end
757
+
758
+ describe 'with a bare method name' do
759
+ let(:error_message) do
760
+ 'delegated methods must have a scope - use a scoped key or pass ' \
761
+ 'the :scope option to #dependency'
762
+ end
763
+
764
+ it 'should raise an exception' do
765
+ expect { described_class.plumbum_dependency('#launch') }
766
+ .to raise_error ArgumentError, error_message
767
+ end
768
+ end
769
+
770
+ describe 'with a method name String with scope: value' do
771
+ let(:options) { super().merge(scope: 'application.tools') }
772
+ let(:key) { '#fetch' }
773
+
774
+ include_deferred 'should define method dependency',
775
+ :fetch,
776
+ to: 'application.tools'
777
+
778
+ it 'should return the reader name' do
779
+ expect(described_class.plumbum_dependency(key, **options))
780
+ .to be :fetch
781
+ end
782
+
783
+ describe 'with memoize: value' do
784
+ let(:options) { super().merge(memoize: false) }
785
+ let(:error_message) do
786
+ 'invalid option :memoize for method dependency'
787
+ end
788
+
789
+ it 'should raise an exception' do
790
+ expect { define_dependencies }
791
+ .to raise_error ArgumentError, error_message
792
+ end
793
+ end
794
+
795
+ describe 'with private: value' do
796
+ let(:options) { super().merge(private: true) }
797
+
798
+ include_deferred 'should define method dependency',
799
+ :fetch,
800
+ to: 'application.tools',
801
+ visibility: :private
802
+ end
803
+
804
+ describe 'with predicate: value' do
805
+ let(:options) { super().merge(predicate: true) }
806
+ let(:error_message) do
807
+ 'invalid option :predicate for method dependency'
808
+ end
809
+
810
+ it 'should raise an exception' do
811
+ expect { define_dependencies }
812
+ .to raise_error ArgumentError, error_message
813
+ end
814
+ end
815
+ end
816
+
817
+ describe 'with a method name Symbol with scope: value' do
818
+ let(:options) { super().merge(scope: 'application.tools') }
819
+ let(:key) { :'#fetch' }
820
+
821
+ include_deferred 'should define method dependency',
822
+ :fetch,
823
+ to: 'application.tools'
824
+
825
+ it 'should return the reader name' do
826
+ expect(described_class.plumbum_dependency(key, **options))
827
+ .to be :fetch
828
+ end
829
+
830
+ describe 'with memoize: value' do
831
+ let(:options) { super().merge(memoize: false) }
832
+ let(:error_message) do
833
+ 'invalid option :memoize for method dependency'
834
+ end
835
+
836
+ it 'should raise an exception' do
837
+ expect { define_dependencies }
838
+ .to raise_error ArgumentError, error_message
839
+ end
840
+ end
841
+
842
+ describe 'with private: value' do
843
+ let(:options) { super().merge(private: true) }
844
+
845
+ include_deferred 'should define method dependency',
846
+ :fetch,
847
+ to: 'application.tools',
848
+ visibility: :private
849
+ end
850
+
851
+ describe 'with predicate: value' do
852
+ let(:options) { super().merge(predicate: true) }
853
+ let(:error_message) do
854
+ 'invalid option :predicate for method dependency'
855
+ end
856
+
857
+ it 'should raise an exception' do
858
+ expect { define_dependencies }
859
+ .to raise_error ArgumentError, error_message
860
+ end
861
+ end
862
+ end
863
+
864
+ describe 'with a dot-separated method name String' do
865
+ let(:key) { 'application.tools.#fetch' }
866
+
867
+ include_deferred 'should define method dependency',
868
+ :fetch,
869
+ to: 'application.tools'
870
+
871
+ it 'should return the reader name' do
872
+ expect(described_class.plumbum_dependency(key))
873
+ .to be :fetch
874
+ end
875
+
876
+ describe 'with memoize: value' do
877
+ let(:options) { super().merge(memoize: false) }
878
+ let(:error_message) do
879
+ 'invalid option :memoize for method dependency'
880
+ end
881
+
882
+ it 'should raise an exception' do
883
+ expect { define_dependencies }
884
+ .to raise_error ArgumentError, error_message
885
+ end
886
+ end
887
+
888
+ describe 'with predicate: value' do
889
+ let(:options) { super().merge(predicate: true) }
890
+ let(:error_message) do
891
+ 'invalid option :predicate for method dependency'
892
+ end
893
+
894
+ it 'should raise an exception' do
895
+ expect { define_dependencies }
896
+ .to raise_error ArgumentError, error_message
897
+ end
898
+ end
899
+ end
900
+
901
+ describe 'with a dot-separated method name Symbol' do
902
+ let(:key) { :'application.tools.#fetch' }
903
+
904
+ include_deferred 'should define method dependency',
905
+ :fetch,
906
+ to: 'application.tools'
907
+
908
+ it 'should return the reader name' do
909
+ expect(described_class.plumbum_dependency(key))
910
+ .to be :fetch
911
+ end
912
+
913
+ describe 'with memoize: value' do
914
+ let(:options) { super().merge(memoize: false) }
915
+ let(:error_message) do
916
+ 'invalid option :memoize for method dependency'
917
+ end
918
+
919
+ it 'should raise an exception' do
920
+ expect { define_dependencies }
921
+ .to raise_error ArgumentError, error_message
922
+ end
923
+ end
924
+
925
+ describe 'with predicate: value' do
926
+ let(:options) { super().merge(predicate: true) }
927
+ let(:error_message) do
928
+ 'invalid option :predicate for method dependency'
929
+ end
930
+
931
+ it 'should raise an exception' do
932
+ expect { define_dependencies }
933
+ .to raise_error ArgumentError, error_message
934
+ end
935
+ end
936
+ end
937
+ end
938
+
939
+ describe '.plumbum_dependency_keys' do
940
+ it 'should define the method' do
941
+ expect(described_class)
942
+ .to respond_to(:plumbum_dependency_keys)
943
+ .with(0).arguments
944
+ .and_keywords(:cache)
945
+ end
946
+
947
+ it { expect(described_class.plumbum_dependency_keys).to be == Set.new }
948
+
949
+ wrap_deferred 'when an included module defines dependencies' do
950
+ let(:expected_dependencies) { included_dependencies }
951
+
952
+ it 'should return the class dependencies' do
953
+ expect(described_class.plumbum_dependency_keys)
954
+ .to be_a(Set)
955
+ .and match_array(expected_dependencies)
956
+ end
957
+ end
958
+
959
+ wrap_deferred 'when the parent class defines dependencies' do
960
+ let(:expected_dependencies) { parent_dependencies }
961
+
962
+ it 'should return the class dependencies' do
963
+ expect(described_class.plumbum_dependency_keys)
964
+ .to be_a(Set)
965
+ .and match_array(expected_dependencies)
966
+ end
967
+ end
968
+
969
+ wrap_deferred 'when the class defines dependencies' do
970
+ let(:expected_dependencies) { class_dependencies }
971
+
972
+ it 'should return the class dependencies' do
973
+ expect(described_class.plumbum_dependency_keys)
974
+ .to be_a(Set)
975
+ .and match_array(expected_dependencies)
976
+ end
977
+ end
978
+
979
+ context 'when the class and ancestors define dependencies' do
980
+ let(:expected_dependencies) do
981
+ [
982
+ *parent_dependencies,
983
+ *class_dependencies,
984
+ *included_dependencies
985
+ ].uniq
986
+ end
987
+
988
+ include_deferred 'when the parent class defines dependencies'
989
+ include_deferred 'when an included module defines dependencies'
990
+ include_deferred 'when the class defines dependencies'
991
+
992
+ it 'should return the parent class and class dependencies' do
993
+ expect(described_class.plumbum_dependency_keys)
994
+ .to be_a(Set)
995
+ .and match_array(expected_dependencies)
996
+ end
997
+ end
998
+
999
+ context 'when the dependency keys change' do
1000
+ before(:example) do
1001
+ # Memoize value.
1002
+ described_class.plumbum_dependency_keys
1003
+
1004
+ described_class.plumbum_dependency 'added_dependency'
1005
+ end
1006
+
1007
+ it 'should not update the dependency keys' do
1008
+ expect(described_class.plumbum_dependency_keys).to be == Set.new
1009
+ end
1010
+
1011
+ describe 'with cache: false' do
1012
+ it 'should update the dependency keys' do
1013
+ expect(described_class.plumbum_dependency_keys(cache: false))
1014
+ .to be == Set.new(%w[added_dependency])
1015
+ end
1016
+ end
1017
+
1018
+ describe 'with cache: true' do
1019
+ it 'should not update the dependency keys' do
1020
+ expect(described_class.plumbum_dependency_keys(cache: true))
1021
+ .to be == Set.new
1022
+ end
1023
+ end
1024
+ end
1025
+ end
1026
+
1027
+ describe '.plumbum_provider' do
1028
+ it 'should define the method' do
1029
+ expect(described_class)
1030
+ .to respond_to(:plumbum_provider)
1031
+ .with(1).argument
1032
+ end
1033
+
1034
+ describe 'with nil' do
1035
+ let(:error_message) { 'provider does not respond to #get' }
1036
+
1037
+ it 'should raise an exception' do
1038
+ expect { described_class.plumbum_provider(nil) }
1039
+ .to raise_error ArgumentError, error_message
1040
+ end
1041
+ end
1042
+
1043
+ describe 'with an Object' do
1044
+ let(:error_message) { 'provider does not respond to #get' }
1045
+
1046
+ it 'should raise an exception' do
1047
+ expect { described_class.plumbum_provider(Object.new.freeze) }
1048
+ .to raise_error ArgumentError, error_message
1049
+ end
1050
+ end
1051
+
1052
+ describe 'with a Provider' do
1053
+ let(:provider) { Plumbum::ManyProvider.new }
1054
+
1055
+ define_method :defined_providers do
1056
+ described_class.plumbum_providers(cache: false)
1057
+ end
1058
+
1059
+ it { expect(described_class.plumbum_provider(provider)).to be nil }
1060
+
1061
+ it 'should add the provider to #plumbum_providers',
1062
+ :aggregate_failures \
1063
+ do
1064
+ expect { described_class.plumbum_provider(provider) }.to(
1065
+ change { defined_providers.count }.by(1)
1066
+ )
1067
+
1068
+ expect(defined_providers.first).to be provider
1069
+ end
1070
+
1071
+ wrap_deferred 'when the class defines providers' do
1072
+ it 'should add the provider to #plumbum_providers',
1073
+ :aggregate_failures \
1074
+ do
1075
+ expect { described_class.plumbum_provider(provider) }.to(
1076
+ change { defined_providers.count }.by(1)
1077
+ )
1078
+
1079
+ expect(defined_providers.first).to be provider
1080
+ end
1081
+ end
1082
+ end
1083
+
1084
+ describe 'with an object implementing the Provider interface' do
1085
+ let(:provider) do
1086
+ Spec::CustomProvider.new(hostname: 'localhost', port: '3000')
1087
+ end
1088
+
1089
+ example_constant 'Spec::CustomProvider' do
1090
+ Data.define(:hostname, :port) do
1091
+ def get(key) = has?(key) ? send(key) : nil
1092
+
1093
+ def has?(key) = members.include?(key.to_sym)
1094
+ end
1095
+ end
1096
+
1097
+ define_method :defined_providers do
1098
+ described_class.plumbum_providers(cache: false)
1099
+ end
1100
+
1101
+ it { expect(described_class.plumbum_provider(provider)).to be nil }
1102
+
1103
+ it 'should add the provider to #plumbum_providers',
1104
+ :aggregate_failures \
1105
+ do
1106
+ expect { described_class.plumbum_provider(provider) }.to(
1107
+ change { defined_providers.count }.by(1)
1108
+ )
1109
+
1110
+ expect(defined_providers.first).to be provider
1111
+ end
1112
+
1113
+ wrap_deferred 'when the class defines providers' do
1114
+ it 'should add the provider to #plumbum_providers',
1115
+ :aggregate_failures \
1116
+ do
1117
+ expect { described_class.plumbum_provider(provider) }.to(
1118
+ change { defined_providers.count }.by(1)
1119
+ )
1120
+
1121
+ expect(defined_providers.first).to be provider
1122
+ end
1123
+ end
1124
+ end
1125
+
1126
+ describe 'with an object that partially implements the interface' do
1127
+ let(:provider) do
1128
+ Spec::PartialProvider.new(hostname: 'localhost', port: '3000')
1129
+ end
1130
+ let(:error_message) { 'provider does not respond to #has?' }
1131
+
1132
+ example_constant 'Spec::PartialProvider' do
1133
+ Data.define(:hostname, :port) do
1134
+ def get(key) = send(key)
1135
+ end
1136
+ end
1137
+
1138
+ it 'should raise an exception' do
1139
+ expect { described_class.plumbum_provider(provider) }
1140
+ .to raise_error ArgumentError, error_message
1141
+ end
1142
+ end
1143
+ end
1144
+
1145
+ describe '.plumbum_providers' do
1146
+ let(:expected_providers) { [] }
1147
+
1148
+ it 'should define the method' do
1149
+ expect(described_class)
1150
+ .to respond_to(:plumbum_providers)
1151
+ .with(0).arguments
1152
+ .and_keywords(:cache)
1153
+ end
1154
+
1155
+ it { expect(described_class.plumbum_providers).to be == [] }
1156
+
1157
+ wrap_deferred 'when the parent class defines providers' do
1158
+ it 'should return the expected providers' do
1159
+ expect(described_class.plumbum_providers).to eq(expected_providers)
1160
+ end
1161
+ end
1162
+
1163
+ wrap_deferred 'when the class defines providers' do
1164
+ it 'should return the expected providers' do
1165
+ expect(described_class.plumbum_providers).to eq(expected_providers)
1166
+ end
1167
+ end
1168
+
1169
+ wrap_deferred 'when an included module defines dependencies' do
1170
+ it 'should return the expected providers' do
1171
+ expect(described_class.plumbum_providers).to eq(expected_providers)
1172
+ end
1173
+ end
1174
+
1175
+ context 'when the class and ancestors define providers' do
1176
+ include_deferred 'when the parent class defines providers'
1177
+ include_deferred 'when an included module defines providers'
1178
+ include_deferred 'when the class defines providers'
1179
+
1180
+ it 'should return the expected providers' do
1181
+ expect(described_class.plumbum_providers).to eq(expected_providers)
1182
+ end
1183
+ end
1184
+
1185
+ context 'when the providers change' do
1186
+ let(:generic_provider) { Plumbum::ManyProvider.new }
1187
+
1188
+ before(:example) do
1189
+ # Memoize value.
1190
+ described_class.plumbum_providers
1191
+
1192
+ described_class.plumbum_provider generic_provider
1193
+ end
1194
+
1195
+ it 'should not update the dependency keys' do
1196
+ expect(described_class.plumbum_providers).to be == []
1197
+ end
1198
+
1199
+ describe 'with cache: false' do
1200
+ it 'should update the dependency keys' do
1201
+ expect(described_class.plumbum_providers(cache: false))
1202
+ .to be == [generic_provider]
1203
+ end
1204
+ end
1205
+
1206
+ describe 'with cache: true' do
1207
+ it 'should not update the dependency keys' do
1208
+ expect(described_class.plumbum_providers(cache: true))
1209
+ .to be == []
1210
+ end
1211
+ end
1212
+ end
1213
+ end
1214
+ end
1215
+
1216
+ deferred_examples 'should implement the Consumer instance methods' do
1217
+ describe '#get_plumbum_dependency' do
1218
+ it 'should define the method' do
1219
+ expect(subject)
1220
+ .to respond_to(:get_plumbum_dependency)
1221
+ .with(1).argument
1222
+ .and_keywords(:optional)
1223
+ end
1224
+
1225
+ describe 'with nil' do
1226
+ let(:error_message) do
1227
+ tools
1228
+ .assertions
1229
+ .error_message_for(
1230
+ 'sleeping_king_studios.tools.assertions.presence',
1231
+ as: :key
1232
+ )
1233
+ end
1234
+
1235
+ it 'should raise an exception' do
1236
+ expect { subject.get_plumbum_dependency(nil) }
1237
+ .to raise_error ArgumentError, error_message
1238
+ end
1239
+ end
1240
+
1241
+ describe 'with an Object' do
1242
+ let(:error_message) do
1243
+ tools
1244
+ .assertions
1245
+ .error_message_for(
1246
+ 'sleeping_king_studios.tools.assertions.name',
1247
+ as: :key
1248
+ )
1249
+ end
1250
+
1251
+ it 'should raise an exception' do
1252
+ expect { subject.get_plumbum_dependency(Object.new.freeze) }
1253
+ .to raise_error ArgumentError, error_message
1254
+ end
1255
+ end
1256
+
1257
+ describe 'with an empty String' do
1258
+ let(:error_message) do
1259
+ tools
1260
+ .assertions
1261
+ .error_message_for(
1262
+ 'sleeping_king_studios.tools.assertions.presence',
1263
+ as: :key
1264
+ )
1265
+ end
1266
+
1267
+ it 'should raise an exception' do
1268
+ expect { subject.get_plumbum_dependency('') }
1269
+ .to raise_error ArgumentError, error_message
1270
+ end
1271
+ end
1272
+
1273
+ describe 'with an empty Symbol' do
1274
+ let(:error_message) do
1275
+ tools
1276
+ .assertions
1277
+ .error_message_for(
1278
+ 'sleeping_king_studios.tools.assertions.presence',
1279
+ as: :key
1280
+ )
1281
+ end
1282
+
1283
+ it 'should raise an exception' do
1284
+ expect { subject.get_plumbum_dependency(:'') }
1285
+ .to raise_error ArgumentError, error_message
1286
+ end
1287
+ end
1288
+
1289
+ describe 'with a non-matching String' do
1290
+ let(:error_message) do
1291
+ 'dependency not found with key "invalid"'
1292
+ end
1293
+
1294
+ it 'should raise an exception' do
1295
+ expect { subject.get_plumbum_dependency('invalid') }.to raise_error(
1296
+ Plumbum::Errors::MissingDependencyError,
1297
+ error_message
1298
+ )
1299
+ end
1300
+ end
1301
+
1302
+ describe 'with a non-matching Symbol' do
1303
+ let(:error_message) do
1304
+ 'dependency not found with key :invalid'
1305
+ end
1306
+
1307
+ it 'should raise an exception' do
1308
+ expect { subject.get_plumbum_dependency(:invalid) }.to raise_error(
1309
+ Plumbum::Errors::MissingDependencyError,
1310
+ error_message
1311
+ )
1312
+ end
1313
+ end
1314
+
1315
+ wrap_deferred 'when the class defines providers' do
1316
+ describe 'with a non-matching String' do
1317
+ let(:error_message) do
1318
+ 'dependency not found with key "invalid"'
1319
+ end
1320
+
1321
+ it 'should raise an exception' do
1322
+ expect { subject.get_plumbum_dependency('invalid') }
1323
+ .to raise_error(
1324
+ Plumbum::Errors::MissingDependencyError,
1325
+ error_message
1326
+ )
1327
+ end
1328
+ end
1329
+
1330
+ describe 'with a non-matching Symbol' do
1331
+ let(:error_message) do
1332
+ 'dependency not found with key :invalid'
1333
+ end
1334
+
1335
+ it 'should raise an exception' do
1336
+ expect { subject.get_plumbum_dependency(:invalid) }
1337
+ .to raise_error(
1338
+ Plumbum::Errors::MissingDependencyError,
1339
+ error_message
1340
+ )
1341
+ end
1342
+ end
1343
+
1344
+ describe 'with a matching String' do
1345
+ it 'should return the dependency value' do
1346
+ expect(subject.get_plumbum_dependency('env'))
1347
+ .to eq(config_provider.values['env'])
1348
+ end
1349
+
1350
+ context 'with a String matching multiple providers' do
1351
+ it 'should return the value from the last provider' do
1352
+ expect(subject.get_plumbum_dependency('tools'))
1353
+ .to eq(tools_provider.value)
1354
+ end
1355
+ end
1356
+ end
1357
+
1358
+ describe 'with a matching Symbol' do
1359
+ it 'should return the dependency value' do
1360
+ expect(subject.get_plumbum_dependency(:env))
1361
+ .to eq(config_provider.values['env'])
1362
+ end
1363
+
1364
+ context 'with a Symbol matching multiple providers' do
1365
+ it 'should return the value from the last provider' do
1366
+ expect(subject.get_plumbum_dependency(:tools))
1367
+ .to eq(tools_provider.value)
1368
+ end
1369
+ end
1370
+ end
1371
+ end
1372
+ end
1373
+
1374
+ describe '#has_plumbum_dependency?' do
1375
+ it 'should define the method' do
1376
+ expect(subject)
1377
+ .to respond_to(:has_plumbum_dependency?)
1378
+ .with(1).argument
1379
+ end
1380
+
1381
+ describe 'with nil' do
1382
+ let(:error_message) do
1383
+ tools
1384
+ .assertions
1385
+ .error_message_for(
1386
+ 'sleeping_king_studios.tools.assertions.presence',
1387
+ as: :key
1388
+ )
1389
+ end
1390
+
1391
+ it 'should raise an exception' do
1392
+ expect { subject.has_plumbum_dependency?(nil) }
1393
+ .to raise_error ArgumentError, error_message
1394
+ end
1395
+ end
1396
+
1397
+ describe 'with an Object' do
1398
+ let(:error_message) do
1399
+ tools
1400
+ .assertions
1401
+ .error_message_for(
1402
+ 'sleeping_king_studios.tools.assertions.name',
1403
+ as: :key
1404
+ )
1405
+ end
1406
+
1407
+ it 'should raise an exception' do
1408
+ expect { subject.has_plumbum_dependency?(Object.new.freeze) }
1409
+ .to raise_error ArgumentError, error_message
1410
+ end
1411
+ end
1412
+
1413
+ describe 'with an empty String' do
1414
+ let(:error_message) do
1415
+ tools
1416
+ .assertions
1417
+ .error_message_for(
1418
+ 'sleeping_king_studios.tools.assertions.presence',
1419
+ as: :key
1420
+ )
1421
+ end
1422
+
1423
+ it 'should raise an exception' do
1424
+ expect { subject.has_plumbum_dependency?('') }
1425
+ .to raise_error ArgumentError, error_message
1426
+ end
1427
+ end
1428
+
1429
+ describe 'with an empty Symbol' do
1430
+ let(:error_message) do
1431
+ tools
1432
+ .assertions
1433
+ .error_message_for(
1434
+ 'sleeping_king_studios.tools.assertions.presence',
1435
+ as: :key
1436
+ )
1437
+ end
1438
+
1439
+ it 'should raise an exception' do
1440
+ expect { subject.has_plumbum_dependency?(:'') }
1441
+ .to raise_error ArgumentError, error_message
1442
+ end
1443
+ end
1444
+
1445
+ describe 'with a non-matching String' do
1446
+ it { expect(subject.has_plumbum_dependency?('invalid')).to be false }
1447
+ end
1448
+
1449
+ describe 'with a non-matching Symbol' do
1450
+ it { expect(subject.has_plumbum_dependency?(:invalid)).to be false }
1451
+ end
1452
+
1453
+ wrap_deferred 'when the class defines providers' do
1454
+ describe 'with a non-matching String' do
1455
+ let(:value) { 'invalid' }
1456
+
1457
+ it { expect(subject.has_plumbum_dependency?(value)).to be false }
1458
+ end
1459
+
1460
+ describe 'with a non-matching Symbol' do
1461
+ it { expect(subject.has_plumbum_dependency?(:invalid)).to be false }
1462
+ end
1463
+
1464
+ describe 'with a matching String' do
1465
+ it { expect(subject.has_plumbum_dependency?('tools')).to be true }
1466
+ end
1467
+
1468
+ describe 'with a matching Symbol' do
1469
+ it { expect(subject.has_plumbum_dependency?(:tools)).to be true }
1470
+ end
1471
+ end
1472
+ end
1473
+ end
1474
+
1475
+ deferred_examples 'should generate the dependency methods' do
1476
+ describe '#:dependency' do
1477
+ it { expect(consumer).not_to respond_to(:tools) }
1478
+
1479
+ wrap_deferred 'when the class defines dependencies' do
1480
+ let(:error_message) do
1481
+ 'dependency not found with key "tools"'
1482
+ end
1483
+
1484
+ it { expect(consumer).to respond_to(:tools).with(0).arguments }
1485
+
1486
+ it 'should raise an exception' do
1487
+ expect { consumer.tools }.to raise_error(
1488
+ Plumbum::Errors::MissingDependencyError,
1489
+ error_message
1490
+ )
1491
+ end
1492
+
1493
+ wrap_deferred 'when the class defines providers' do
1494
+ it { expect(consumer.tools).to be tools_provider.value }
1495
+
1496
+ context 'when the class overwrites the method' do
1497
+ before(:example) do
1498
+ described_class.define_method(:tools) do
1499
+ { tools: super() }
1500
+ end
1501
+ end
1502
+
1503
+ it 'should use the class definition' do
1504
+ expect(consumer.tools)
1505
+ .to eq({ tools: tools_provider.value })
1506
+ end
1507
+ end
1508
+ end
1509
+ end
1510
+
1511
+ context 'when the class defines a dependency with as: value' do
1512
+ let(:error_message) do
1513
+ 'dependency not found with key "railtie"'
1514
+ end
1515
+
1516
+ before(:example) do
1517
+ described_class.plumbum_dependency('railtie', as: 'integration')
1518
+ end
1519
+
1520
+ it { expect(consumer).to respond_to(:integration).with(0).arguments }
1521
+
1522
+ it 'should raise an exception' do
1523
+ expect { consumer.integration }.to raise_error(
1524
+ Plumbum::Errors::MissingDependencyError,
1525
+ error_message
1526
+ )
1527
+ end
1528
+
1529
+ context 'when the class includes a provider for the dependency' do
1530
+ let(:railtie_provider) do
1531
+ Plumbum::OneProvider.new(:railtie, value: Object.new.freeze)
1532
+ end
1533
+
1534
+ before(:example) do
1535
+ described_class.plumbum_provider railtie_provider
1536
+ end
1537
+
1538
+ it 'should return the dependency value' do
1539
+ expect(consumer.integration).to be railtie_provider.value
1540
+ end
1541
+ end
1542
+ end
1543
+
1544
+ context 'when the class defines a dependency with default: a Proc' do
1545
+ let(:default) { -> { { id: SecureRandom.uuid } } }
1546
+
1547
+ before(:example) do
1548
+ described_class.plumbum_dependency('tools', default:)
1549
+ end
1550
+
1551
+ it { expect(consumer).to respond_to(:tools).with(0).arguments }
1552
+
1553
+ it { expect(consumer.tools).to match({ id: an_instance_of(String) }) }
1554
+
1555
+ context 'when the class includes a provider for the dependency' do
1556
+ include_deferred 'when the class defines providers'
1557
+
1558
+ it { expect(consumer.tools).to be tools_provider.value }
1559
+ end
1560
+ end
1561
+
1562
+ context 'when the class defines a dependency with default: value' do
1563
+ let(:default) { {} }
1564
+
1565
+ before(:example) do
1566
+ described_class.plumbum_dependency('tools', default:)
1567
+ end
1568
+
1569
+ it { expect(consumer).to respond_to(:tools).with(0).arguments }
1570
+
1571
+ it { expect(consumer.tools).to be == default }
1572
+
1573
+ context 'when the class includes a provider for the dependency' do
1574
+ include_deferred 'when the class defines providers'
1575
+
1576
+ it { expect(consumer.tools).to be tools_provider.value }
1577
+ end
1578
+ end
1579
+
1580
+ context 'when the class defines a dependency with memoize: false' do
1581
+ let(:error_message) do
1582
+ 'dependency not found with key "request"'
1583
+ end
1584
+
1585
+ before(:example) do
1586
+ described_class.plumbum_dependency('request', memoize: false)
1587
+ end
1588
+
1589
+ it { expect(consumer).to respond_to(:request).with(0).arguments }
1590
+
1591
+ it 'should raise an exception' do
1592
+ expect { consumer.request }.to raise_error(
1593
+ Plumbum::Errors::MissingDependencyError,
1594
+ error_message
1595
+ )
1596
+ end
1597
+
1598
+ context 'when the class includes a provider for the dependency' do
1599
+ let(:original_value) { { http_method: :get } }
1600
+
1601
+ example_constant 'Spec::RequestProvider' do
1602
+ Spec::MutableProvider.new(key: :request, value: original_value)
1603
+ end
1604
+
1605
+ before(:example) do
1606
+ described_class.plumbum_dependency('request', memoize: false)
1607
+
1608
+ described_class.plumbum_provider Spec::RequestProvider
1609
+ end
1610
+
1611
+ it { expect(consumer.request).to be Spec::RequestProvider.value }
1612
+
1613
+ context 'when the provider value changes' do
1614
+ let(:changed_value) { { http_method: :post } }
1615
+
1616
+ before(:example) do
1617
+ consumer.request # Cache the dependency.
1618
+
1619
+ Spec::RequestProvider.value = changed_value
1620
+ end
1621
+
1622
+ it { expect(consumer.request).to be Spec::RequestProvider.value }
1623
+ end
1624
+ end
1625
+ end
1626
+
1627
+ context 'when the class defines a dependency with memoize: true' do
1628
+ let(:error_message) do
1629
+ 'dependency not found with key "request"'
1630
+ end
1631
+
1632
+ before(:example) do
1633
+ described_class.plumbum_dependency('request', memoize: true)
1634
+ end
1635
+
1636
+ it { expect(consumer).to respond_to(:request).with(0).arguments }
1637
+
1638
+ it 'should raise an exception' do
1639
+ expect { consumer.request }.to raise_error(
1640
+ Plumbum::Errors::MissingDependencyError,
1641
+ error_message
1642
+ )
1643
+ end
1644
+
1645
+ context 'when the class includes a provider for the dependency' do
1646
+ let(:original_value) { { http_method: :get } }
1647
+
1648
+ example_constant 'Spec::RequestProvider' do
1649
+ Spec::MutableProvider.new(key: :request, value: original_value)
1650
+ end
1651
+
1652
+ before(:example) do
1653
+ described_class.plumbum_dependency('request', memoize: true)
1654
+
1655
+ described_class.plumbum_provider Spec::RequestProvider
1656
+ end
1657
+
1658
+ it { expect(consumer.request).to be Spec::RequestProvider.value }
1659
+
1660
+ context 'when the provider value changes' do
1661
+ let(:changed_value) { { http_method: :post } }
1662
+
1663
+ before(:example) do
1664
+ consumer.request # Cache the dependency.
1665
+
1666
+ Spec::RequestProvider.value = changed_value
1667
+ end
1668
+
1669
+ it { expect(consumer.request).to be original_value }
1670
+ end
1671
+ end
1672
+ end
1673
+
1674
+ context 'when the class defines a dependency with optional: false' do
1675
+ let(:error_message) do
1676
+ 'dependency not found with key "railtie"'
1677
+ end
1678
+
1679
+ before(:example) do
1680
+ described_class.plumbum_dependency('railtie', optional: false)
1681
+ end
1682
+
1683
+ it { expect(consumer).to respond_to(:railtie).with(0).arguments }
1684
+
1685
+ it 'should raise an exception' do
1686
+ expect { consumer.railtie }.to raise_error(
1687
+ Plumbum::Errors::MissingDependencyError,
1688
+ error_message
1689
+ )
1690
+ end
1691
+
1692
+ context 'when the class includes a provider for the dependency' do
1693
+ let(:railtie_provider) do
1694
+ Plumbum::OneProvider.new(:railtie, value: Object.new.freeze)
1695
+ end
1696
+
1697
+ before(:example) do
1698
+ described_class.plumbum_provider railtie_provider
1699
+ end
1700
+
1701
+ it { expect(consumer.railtie).to be railtie_provider.value }
1702
+ end
1703
+ end
1704
+
1705
+ context 'when the class defines a dependency with optional: true' do
1706
+ before(:example) do
1707
+ described_class.plumbum_dependency('railtie', optional: true)
1708
+ end
1709
+
1710
+ it { expect(consumer).to respond_to(:railtie).with(0).arguments }
1711
+
1712
+ it { expect(consumer.railtie).to be nil }
1713
+
1714
+ context 'when the class includes a provider for the dependency' do
1715
+ let(:railtie_provider) do
1716
+ Plumbum::OneProvider.new(:railtie, value: Object.new.freeze)
1717
+ end
1718
+
1719
+ before(:example) do
1720
+ described_class.plumbum_provider railtie_provider
1721
+ end
1722
+
1723
+ it { expect(consumer.railtie).to be railtie_provider.value }
1724
+ end
1725
+ end
1726
+
1727
+ context 'when the class defines a dependency with scope: value' do
1728
+ let(:error_message) do
1729
+ 'dependency not found with key "application"'
1730
+ end
1731
+
1732
+ before(:example) do
1733
+ described_class.plumbum_dependency(
1734
+ 'object_tools',
1735
+ scope: 'application.tools'
1736
+ )
1737
+ end
1738
+
1739
+ it { expect(consumer).to respond_to(:object_tools).with(0).arguments }
1740
+
1741
+ it 'should raise an exception' do
1742
+ expect { consumer.object_tools }.to raise_error(
1743
+ Plumbum::Errors::MissingDependencyError,
1744
+ error_message
1745
+ )
1746
+ end
1747
+
1748
+ context 'when the class includes a provider for the dependency' do
1749
+ let(:object_tools) { Object.new.freeze }
1750
+ let(:tools) { Struct.new(:object_tools).new(object_tools) }
1751
+ let(:application) { Struct.new(:tools).new(tools) }
1752
+ let(:application_provider) do
1753
+ Plumbum::OneProvider.new(:application, value: application)
1754
+ end
1755
+
1756
+ before(:example) do
1757
+ described_class.plumbum_provider application_provider
1758
+ end
1759
+
1760
+ it { expect(consumer.object_tools).to be object_tools }
1761
+ end
1762
+ end
1763
+
1764
+ context 'when the class defines a scoped dependency' do
1765
+ let(:error_message) do
1766
+ 'dependency not found with key "application"'
1767
+ end
1768
+
1769
+ before(:example) do
1770
+ described_class.plumbum_dependency('application.tools.object_tools')
1771
+ end
1772
+
1773
+ it { expect(consumer).to respond_to(:object_tools).with(0).arguments }
1774
+
1775
+ it 'should raise an exception' do
1776
+ expect { consumer.object_tools }.to raise_error(
1777
+ Plumbum::Errors::MissingDependencyError,
1778
+ error_message
1779
+ )
1780
+ end
1781
+
1782
+ context 'when the class includes a provider for the dependency' do
1783
+ let(:object_tools) { Object.new.freeze }
1784
+ let(:tools) { Struct.new(:object_tools).new(object_tools) }
1785
+ let(:application) { Struct.new(:tools).new(tools) }
1786
+ let(:application_provider) do
1787
+ Plumbum::OneProvider.new(:application, value: application)
1788
+ end
1789
+
1790
+ before(:example) do
1791
+ described_class.plumbum_provider application_provider
1792
+ end
1793
+
1794
+ it { expect(consumer.object_tools).to be object_tools }
1795
+ end
1796
+ end
1797
+
1798
+ context 'when the class defines a scoped dependency with as: value' do
1799
+ let(:error_message) do
1800
+ 'dependency not found with key "application"'
1801
+ end
1802
+
1803
+ before(:example) do
1804
+ described_class
1805
+ .plumbum_dependency('application.tools.object_tools', as: :obj)
1806
+ end
1807
+
1808
+ it { expect(consumer).to respond_to(:obj).with(0).arguments }
1809
+
1810
+ it 'should raise an exception' do
1811
+ expect { consumer.obj }.to raise_error(
1812
+ Plumbum::Errors::MissingDependencyError,
1813
+ error_message
1814
+ )
1815
+ end
1816
+
1817
+ context 'when the class includes a provider for the dependency' do
1818
+ let(:object_tools) { Object.new.freeze }
1819
+ let(:tools) { Struct.new(:object_tools).new(object_tools) }
1820
+ let(:application) { Struct.new(:tools).new(tools) }
1821
+ let(:application_provider) do
1822
+ Plumbum::OneProvider.new(:application, value: application)
1823
+ end
1824
+
1825
+ before(:example) do
1826
+ described_class.plumbum_provider application_provider
1827
+ end
1828
+
1829
+ it { expect(consumer.obj).to be object_tools }
1830
+ end
1831
+ end
1832
+
1833
+ context 'when the class defines a scoped dependency with invalid path' \
1834
+ do
1835
+ let(:error_message) do
1836
+ 'dependency not found with key "application"'
1837
+ end
1838
+
1839
+ before(:example) do
1840
+ described_class.plumbum_dependency('application.tools.basic_tools')
1841
+ end
1842
+
1843
+ it { expect(consumer).to respond_to(:basic_tools).with(0).arguments }
1844
+
1845
+ it 'should raise an exception' do
1846
+ expect { consumer.basic_tools }.to raise_error(
1847
+ Plumbum::Errors::MissingDependencyError,
1848
+ error_message
1849
+ )
1850
+ end
1851
+
1852
+ context 'when the class includes a provider for the dependency' do
1853
+ let(:object_tools) { Object.new.freeze }
1854
+ let(:tools) { Struct.new(:object_tools).new(object_tools) }
1855
+ let(:application) { Struct.new(:tools).new(tools) }
1856
+ let(:application_provider) do
1857
+ Plumbum::OneProvider.new(:application, value: application)
1858
+ end
1859
+ let(:error_message) { 'undefined method "basic_tools"' }
1860
+
1861
+ before(:example) do
1862
+ described_class.plumbum_provider application_provider
1863
+ end
1864
+
1865
+ it 'should raise an exception' do
1866
+ expect { consumer.basic_tools }
1867
+ .to raise_error(
1868
+ Plumbum::Errors::MissingDependencyError,
1869
+ error_message
1870
+ )
1871
+ end
1872
+ end
1873
+ end
1874
+
1875
+ context 'when the class defines an optional scoped dependency' do
1876
+ before(:example) do
1877
+ described_class.plumbum_dependency(
1878
+ 'application.tools.object_tools',
1879
+ optional: true
1880
+ )
1881
+ end
1882
+
1883
+ it { expect(consumer).to respond_to(:object_tools).with(0).arguments }
1884
+
1885
+ it { expect(consumer.object_tools).to be nil }
1886
+
1887
+ context 'when the class includes a provider for the dependency' do
1888
+ let(:object_tools) { Object.new.freeze }
1889
+ let(:tools) { Struct.new(:object_tools).new(object_tools) }
1890
+ let(:application) { Struct.new(:tools).new(tools) }
1891
+ let(:application_provider) do
1892
+ Plumbum::OneProvider.new(:application, value: application)
1893
+ end
1894
+
1895
+ before(:example) do
1896
+ described_class.plumbum_provider application_provider
1897
+ end
1898
+
1899
+ it { expect(consumer.object_tools).to be object_tools }
1900
+ end
1901
+ end
1902
+
1903
+ context 'when the class defines a method dependency' do
1904
+ let(:error_message) do
1905
+ 'dependency not found with key "application"'
1906
+ end
1907
+
1908
+ before(:example) do
1909
+ described_class.plumbum_dependency(
1910
+ '#restart',
1911
+ scope: 'application.manager'
1912
+ )
1913
+ end
1914
+
1915
+ it 'should define the delegator method' do
1916
+ expect(consumer)
1917
+ .to respond_to(:restart)
1918
+ .with_unlimited_arguments
1919
+ .and_any_keywords
1920
+ .and_a_block
1921
+ end
1922
+
1923
+ it 'should raise an exception' do
1924
+ expect { consumer.restart }.to raise_error(
1925
+ Plumbum::Errors::MissingDependencyError,
1926
+ error_message
1927
+ )
1928
+ end
1929
+
1930
+ context 'when the class includes a provider for the dependency' do
1931
+ let(:manager) { double('application_manager', restart: nil) }
1932
+ let(:application) { Struct.new(:manager).new(manager) }
1933
+ let(:application_provider) do
1934
+ Plumbum::OneProvider.new(:application, value: application)
1935
+ end
1936
+
1937
+ before(:example) do
1938
+ described_class.plumbum_provider application_provider
1939
+ end
1940
+
1941
+ it 'should delegate to the dependency' do
1942
+ consumer.restart
1943
+
1944
+ expect(manager).to have_received(:restart).with(no_args)
1945
+ end
1946
+
1947
+ describe 'with parameters' do
1948
+ let(:arguments) { ['all-applications'] }
1949
+ let(:keywords) { { force: true } }
1950
+
1951
+ it 'should delegate to the dependency', :aggregate_failures do
1952
+ restarted = false
1953
+
1954
+ allow(manager).to receive(:restart) { restarted = true }
1955
+
1956
+ consumer.restart(*arguments, **keywords)
1957
+ expect(manager)
1958
+ .to have_received(:restart)
1959
+ .with(*arguments, **keywords)
1960
+
1961
+ expect(restarted).to be true
1962
+ end
1963
+ end
1964
+ end
1965
+ end
1966
+
1967
+ context 'when the class includes a mutable provider' do
1968
+ let(:original_value) { { http_method: :get } }
1969
+
1970
+ example_constant 'Spec::RequestProvider' do
1971
+ Spec::MutableProvider.new(key: :request, value: original_value)
1972
+ end
1973
+
1974
+ before(:example) do
1975
+ described_class.plumbum_dependency('request')
1976
+
1977
+ described_class.plumbum_provider Spec::RequestProvider
1978
+ end
1979
+
1980
+ it { expect(consumer.request).to be Spec::RequestProvider.value }
1981
+
1982
+ context 'when the provider value changes' do
1983
+ let(:changed_value) { { http_method: :post } }
1984
+
1985
+ before(:example) do
1986
+ consumer.request # Cache the dependency.
1987
+
1988
+ Spec::RequestProvider.value = changed_value
1989
+ end
1990
+
1991
+ it { expect(consumer.request).to be original_value }
1992
+ end
1993
+ end
1994
+ end
1995
+
1996
+ describe '#:dependency?' do
1997
+ wrap_deferred 'when the class defines dependencies' do
1998
+ it { expect(consumer).not_to respond_to(:tools?) }
1999
+ end
2000
+
2001
+ context 'when the class defines a dependency with predicate: false' do
2002
+ before(:example) do
2003
+ described_class.plumbum_dependency('flag_enabled', predicate: false)
2004
+ end
2005
+
2006
+ it { expect(consumer).not_to respond_to(:flag_enabled?) }
2007
+ end
2008
+
2009
+ context 'when the class defines a dependency with predicate: true' do
2010
+ before(:example) do
2011
+ described_class.plumbum_dependency('flag_enabled', predicate: true)
2012
+ end
2013
+
2014
+ it 'should define the predicate' do
2015
+ expect(consumer).to respond_to(:flag_enabled?).with(0).arguments
2016
+ end
2017
+
2018
+ it { expect(consumer.flag_enabled?).to be false }
2019
+
2020
+ context 'with as: value' do
2021
+ before(:example) do
2022
+ described_class
2023
+ .plumbum_dependency('flag_enabled', as: 'flag', predicate: true)
2024
+ end
2025
+
2026
+ it { expect(consumer).to respond_to(:flag?).with(0).arguments }
2027
+
2028
+ it { expect(consumer.flag?).to be false }
2029
+
2030
+ context 'when the class defines providers' do
2031
+ let(:flag_provider) do
2032
+ Plumbum::OneProvider.new(:flag_enabled, value: false)
2033
+ end
2034
+
2035
+ before(:example) do
2036
+ described_class.plumbum_provider flag_provider
2037
+ end
2038
+
2039
+ it { expect(consumer.flag?).to be true }
2040
+
2041
+ context 'when the class overwrites the method' do
2042
+ before(:example) do
2043
+ described_class.define_method(:flag?) do
2044
+ super().to_s
2045
+ end
2046
+ end
2047
+
2048
+ it 'should use the class definition' do
2049
+ expect(consumer.flag?).to eq 'true'
2050
+ end
2051
+ end
2052
+ end
2053
+ end
2054
+
2055
+ context 'when the class overwrites the method' do
2056
+ before(:example) do
2057
+ described_class.define_method(:flag_enabled?) do
2058
+ super().to_s
2059
+ end
2060
+ end
2061
+
2062
+ it 'should use the class definition' do
2063
+ expect(consumer.flag_enabled?).to eq 'false'
2064
+ end
2065
+ end
2066
+
2067
+ context 'when the class defines providers' do
2068
+ let(:flag_provider) do
2069
+ Plumbum::OneProvider.new(:flag_enabled, value: false)
2070
+ end
2071
+
2072
+ before(:example) do
2073
+ described_class.plumbum_provider flag_provider
2074
+ end
2075
+
2076
+ it { expect(consumer.flag_enabled?).to be true }
2077
+
2078
+ context 'when the class overwrites the method' do
2079
+ before(:example) do
2080
+ described_class.define_method(:flag_enabled?) do
2081
+ super().to_s
2082
+ end
2083
+ end
2084
+
2085
+ it 'should use the class definition' do
2086
+ expect(consumer.flag_enabled?).to eq 'true'
2087
+ end
2088
+ end
2089
+ end
2090
+ end
2091
+
2092
+ context 'when the class defines a drilled dependency with a predicate' \
2093
+ do
2094
+ before(:example) do
2095
+ described_class.plumbum_dependency(
2096
+ 'application.tools.object_tools',
2097
+ predicate: true
2098
+ )
2099
+ end
2100
+
2101
+ it 'should define the predicate' do
2102
+ expect(consumer).to respond_to(:object_tools?).with(0).arguments
2103
+ end
2104
+
2105
+ it { expect(consumer.object_tools?).to be false }
2106
+
2107
+ context 'when the class includes a provider for the dependency' do
2108
+ let(:application_provider) do
2109
+ Plumbum::OneProvider.new(:application, value: Object.new.freeze)
2110
+ end
2111
+
2112
+ before(:example) do
2113
+ described_class.plumbum_provider application_provider
2114
+ end
2115
+
2116
+ it { expect(consumer.object_tools?).to be true }
2117
+ end
2118
+ end
2119
+ end
2120
+ end
2121
+ end
2122
+ end