const_conf 0.0.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,628 @@
1
+ require 'spec_helper'
2
+
3
+ describe ConstConf::Setting, protect_env: true do
4
+ let(:parent_namespace) do
5
+ Module.new do
6
+ include ConstConf
7
+ end
8
+ end
9
+
10
+ describe "#initialize" do
11
+ it "creates a setting with name and prefix" do
12
+ setting = parent_namespace.module_eval do
13
+ ConstConf::Setting.new(name: "DATABASE_URL", prefix: "APP") do
14
+ end
15
+ end
16
+ expect(setting.name).to eq "DATABASE_URL"
17
+ expect(setting.prefix).to eq "APP"
18
+ end
19
+
20
+ it "constructs env var name from prefix and name" do
21
+ setting = parent_namespace.module_eval do
22
+ ConstConf::Setting.new(name: ["APP", "DATABASE_URL"], prefix: "APP") do
23
+ end
24
+ end
25
+ expect(setting.env_var_name).to eq "APP_DATABASE_URL"
26
+ end
27
+ end
28
+
29
+ describe "#env_var_name" do
30
+ it "handles nested module names with double colons" do
31
+ setting = parent_namespace.module_eval do
32
+ ConstConf::Setting.new(name: ["APP", "EMAIL", "NOTIFY_USER"], prefix: "APP") do
33
+ end
34
+ end
35
+ expect(setting.env_var_name).to eq "APP_EMAIL_NOTIFY_USER"
36
+ end
37
+
38
+ it "uses empty prefix when none provided" do
39
+ setting = ConstConf::Setting.new(name: ["APP", "SERVICE", "DATABASE_URL"], prefix: '')
40
+ allow(setting).to receive(:parent_namespace).and_return('APP::SERVICE')
41
+ expect(setting.env_var_name).to eq "DATABASE_URL"
42
+ end
43
+ end
44
+
45
+ describe "#value" do
46
+ before do
47
+ ENV['APP_DATABASE_URL'] = "postgres://localhost/test"
48
+ ENV['APP_HOSTNAMES'] = nil
49
+ end
50
+
51
+ it "returns environment variable value when present" do
52
+ setting = ConstConf::Setting.new(name: ["APP", "DATABASE_URL"], prefix: "APP") do
53
+ description 'nope'
54
+ default "sqlite3://default.db"
55
+ end
56
+ expect(setting.value).to eq "postgres://localhost/test"
57
+ end
58
+
59
+ it "returns default value when env var is not set" do
60
+ ENV['APP_DATABASE_URL'] = nil
61
+
62
+ setting = ConstConf::Setting.new(name: ["APP", "DATABASE_URL"], prefix: "APP") do
63
+ default "sqlite3://default.db"
64
+ end
65
+ expect(setting.value).to eq "sqlite3://default.db"
66
+ end
67
+
68
+ it "applies decode logic when present" do
69
+ setting = ConstConf::Setting.new(name: ["APP", "HOSTNAMES"], prefix: "APP") do
70
+ default "foo,bar,baz"
71
+ decode ->(val) { val.split(",") }
72
+ end
73
+ expect(setting.value).to eq ["foo", "bar", "baz"]
74
+ end
75
+ end
76
+
77
+ describe "#active?" do
78
+ it "returns true when activated with non-nil value" do
79
+ setting = ConstConf::Setting.new(name: ["APP", "DATABASE_URL"], prefix: "APP") do
80
+ activated true
81
+ default "sqlite3://default.db"
82
+ end
83
+ expect(setting.active?).to be true
84
+ end
85
+
86
+ it "returns false when not activated" do
87
+ setting = ConstConf::Setting.new(name: ["APP", "DATABASE_URL"], prefix: "APP") do
88
+ activated false
89
+ end
90
+ expect(setting.active?).to be false
91
+ end
92
+
93
+ it "evaluates Proc with value when activated is a Proc (arity 1)" do
94
+ setting = ConstConf::Setting.new(name: ["APP", "DATABASE_URL"], prefix: "APP") do
95
+ activated ->(value) { value.present? }
96
+ default "test_value"
97
+ end
98
+ expect(setting.active?).to be true
99
+ end
100
+
101
+ it "evaluates Proc without arguments when activated is a Proc (arity 0)" do
102
+ setting = ConstConf::Setting.new(name: ["APP", "DATABASE_URL"], prefix: "APP") do
103
+ activated -> { true }
104
+ default "test_value"
105
+ end
106
+ expect(setting.active?).to be true
107
+ end
108
+
109
+ it "returns false when Proc evaluation returns false (arity 1)" do
110
+ setting = ConstConf::Setting.new(name: ["APP", "DATABASE_URL"], prefix: "APP") do
111
+ activated ->(value) { value.nil? }
112
+ default "test_value"
113
+ end
114
+ expect(setting.active?).to be false
115
+ end
116
+
117
+ it "returns false when Proc evaluation returns false (arity 0)" do
118
+ setting = ConstConf::Setting.new(name: ["APP", "DATABASE_URL"], prefix: "APP") do
119
+ activated -> { false }
120
+ default "test_value"
121
+ end
122
+ expect(setting.active?).to be false
123
+ end
124
+
125
+ it "calls method on value when activated is a Symbol" do
126
+ setting = ConstConf::Setting.new(name: ["APP", "DATABASE_URL"], prefix: "APP") do
127
+ activated :present?
128
+ default "test_value"
129
+ end
130
+ expect(setting.active?).to be true
131
+ end
132
+
133
+ it "returns false when Symbol method returns false on value" do
134
+ setting = ConstConf::Setting.new(name: ["APP", "DATABASE_URL"], prefix: "APP") do
135
+ activated :empty?
136
+ default "test_value"
137
+ end
138
+ expect(setting.active?).to be false
139
+ end
140
+
141
+ it "returns false when Symbol method returns false on nil value" do
142
+ ENV['APP_DATABASE_URL'] = nil
143
+
144
+ setting = ConstConf::Setting.new(name: ["APP", "DATABASE_URL"], prefix: "APP") do
145
+ activated :present?
146
+ default nil
147
+ end
148
+ expect(setting.active?).to be false
149
+ end
150
+ end
151
+
152
+ describe "#configured?" do
153
+ it "returns true when environment variable is set" do
154
+ ENV['APP_TEST_VAR'] = 'value'
155
+
156
+ setting = ConstConf::Setting.new(name: ["APP", "TEST_VAR"], prefix: "APP") do
157
+ default "default"
158
+ end
159
+
160
+ expect(setting.configured?).to be true
161
+ end
162
+
163
+ it "returns false when environment variable is not set" do
164
+ ENV['APP_TEST_VAR'] = nil
165
+
166
+ setting = ConstConf::Setting.new(name: ["APP", "TEST_VAR"], prefix: "APP") do
167
+ default "default"
168
+ end
169
+
170
+ expect(setting.configured?).to be false
171
+ end
172
+
173
+ it "returns false when ignored and environment variable is set" do
174
+ ENV['APP_TEST_VAR'] = 'value'
175
+
176
+ setting = ConstConf::Setting.new(name: ["APP", "TEST_VAR"], prefix: "APP") do
177
+ ignored true
178
+ default "default"
179
+ end
180
+
181
+ expect(setting.configured?).to be false
182
+ end
183
+
184
+ it "returns false when configured value is nil and no default" do
185
+ ENV['APP_TEST_VAR'] = nil
186
+
187
+ setting = ConstConf::Setting.new(name: ["APP", "TEST_VAR"], prefix: "APP") do
188
+ # No default specified
189
+ end
190
+
191
+ expect(setting.configured?).to be false
192
+ end
193
+
194
+ it "returns true when configured value is present" do
195
+ ENV['APP_TEST_VAR'] = 'some_value'
196
+
197
+ setting = ConstConf::Setting.new(name: ["APP", "TEST_VAR"], prefix: "APP") do
198
+ default nil
199
+ end
200
+
201
+ expect(setting.configured?).to be true
202
+ end
203
+
204
+ it "returns false when configured value is an empty string" do
205
+ ENV['APP_TEST_VAR'] = ''
206
+
207
+ setting = ConstConf::Setting.new(name: ["APP", "TEST_VAR"], prefix: "APP") do
208
+ default "default"
209
+ end
210
+
211
+ expect(setting.configured?).to be true
212
+ end
213
+ end
214
+
215
+ describe "#required?" do
216
+ it "returns true when required is set to true" do
217
+ setting = ConstConf::Setting.new(name: ["APP", "DATABASE_URL"], prefix: "APP") do
218
+ required true
219
+ end
220
+ expect(setting.required?).to be true
221
+ end
222
+
223
+ it "returns false when required is not set" do
224
+ setting = ConstConf::Setting.new(name: ["APP", "DATABASE_URL"], prefix: "APP")
225
+ expect(setting.required?).to be false
226
+ end
227
+
228
+ it "returns true when required is a proc that returns true (arity 1)" do
229
+ setting = ConstConf::Setting.new(name: ["APP", "DATABASE_URL"], prefix: "APP") do
230
+ required ->(value) { value.present? }
231
+ end
232
+ allow(setting).to receive(:configured_value_or_default_value).and_return("test_value")
233
+ expect(setting.required?).to be true
234
+ end
235
+
236
+ it "returns false when required is a proc that returns false (arity 1)" do
237
+ setting = ConstConf::Setting.new(name: ["APP", "DATABASE_URL"], prefix: "APP") do
238
+ required ->(value) { value.nil? }
239
+ end
240
+ allow(setting).to receive(:configured_value_or_default_value).and_return("test_value")
241
+ expect(setting.required?).to be false
242
+ end
243
+
244
+ it "returns true when required is a proc that returns true (arity 0)" do
245
+ setting = ConstConf::Setting.new(name: ["APP", "DATABASE_URL"], prefix: "APP") do
246
+ required -> { true }
247
+ end
248
+ expect(setting.required?).to be true
249
+ end
250
+
251
+ it "returns false when required is a proc that returns false (arity 0)" do
252
+ setting = ConstConf::Setting.new(name: ["APP", "DATABASE_URL"], prefix: "APP") do
253
+ required -> { false }
254
+ end
255
+ expect(setting.required?).to be false
256
+ end
257
+ end
258
+
259
+ describe "#confirm!" do
260
+ it "raises RequiredValueNotConfigured when required but not configured" do
261
+ setting = ConstConf::Setting.new(name: ["APP", "DATABASE_URL"], prefix: "APP") do
262
+ description 'nope'
263
+ required true
264
+ end
265
+ expect { setting.confirm! }.to raise_error(ConstConf::RequiredValueNotConfigured)
266
+ end
267
+
268
+ it "raises RequiredDescriptionNotConfigured if description is missing" do
269
+ setting = ConstConf::Setting.new(name: ["APP", "DATABASE_URL"], prefix: "APP") do
270
+ required true
271
+ activated true
272
+ end
273
+ expect { setting.confirm! }.to raise_error(ConstConf::RequiredDescriptionNotConfigured)
274
+ end
275
+
276
+ it "does not raise error when required and configured" do
277
+ setting = ConstConf::Setting.new(name: ["APP", "DATABASE_URL"], prefix: "APP") do
278
+ description 'nope'
279
+ required true
280
+ activated true
281
+ end
282
+ allow(setting).to receive(:configured_value).and_return('was set')
283
+ expect { setting.confirm! }.not_to raise_error
284
+ end
285
+
286
+ context "when check fails" do
287
+ it "raises SettingCheckFailed error" do
288
+ setting = ConstConf::Setting.new(name: ["APP", "TEST_VAR"], prefix: "APP") do
289
+ description 'Test setting'
290
+ check ->(setting) { false } # Always fail
291
+ end
292
+ expect { setting.confirm! }.to raise_error(ConstConf::SettingCheckFailed)
293
+ end
294
+ end
295
+
296
+ context "when check passes" do
297
+ it "does not raise error" do
298
+ setting = ConstConf::Setting.new(name: ["APP", "TEST_VAR"], prefix: "APP") do
299
+ description 'Test setting'
300
+ check ->(setting) { true } # Always pass
301
+ end
302
+ expect { setting.confirm! }.not_to raise_error
303
+ end
304
+ end
305
+ end
306
+
307
+ describe "#ignored?" do
308
+ it "returns true when ignored is set to true" do
309
+ setting = ConstConf::Setting.new(name: ["APP", "DATABASE_URL"], prefix: "APP") do
310
+ description 'nope'
311
+ ignored true
312
+ end
313
+ expect(setting.ignored?).to be true
314
+ end
315
+
316
+ it "returns false when ignored is not set" do
317
+ setting = ConstConf::Setting.new(name: ["APP", "DATABASE_URL"], prefix: "APP")
318
+ expect(setting.ignored?).to be false
319
+ end
320
+ end
321
+
322
+ describe "#value_provided?" do
323
+ it "returns true when configured value exists" do
324
+ setting = ConstConf::Setting.new(name: ["APP", "TEST_VAR"], prefix: "APP") do
325
+ default "default"
326
+ end
327
+
328
+ allow(setting).to receive(:configured_value_or_default_value).and_return("value")
329
+
330
+ expect(setting.value_provided?).to be true
331
+ end
332
+
333
+ it "returns false when no configured value or default exists" do
334
+ setting = ConstConf::Setting.new(name: ["APP", "TEST_VAR"], prefix: "APP") do
335
+ default nil
336
+ end
337
+
338
+ allow(setting).to receive(:configured_value_or_default_value).and_return(nil)
339
+
340
+ expect(setting.value_provided?).to be false
341
+ end
342
+ end
343
+
344
+ describe "#default_value" do
345
+ it "returns the default value when it's not a proc" do
346
+ setting = ConstConf::Setting.new(name: ["APP", "TEST_VAR"], prefix: "APP") do
347
+ default "test_default"
348
+ end
349
+
350
+ expect(setting.default_value).to eq "test_default"
351
+ end
352
+
353
+ it "evaluates the default when it's a proc" do
354
+ setting = ConstConf::Setting.new(name: ["APP", "TEST_VAR"], prefix: "APP") do
355
+ default -> { "computed_default" }
356
+ end
357
+
358
+ expect(setting.default_value).to eq "computed_default"
359
+ end
360
+
361
+ it "handles nil defaults properly" do
362
+ setting = ConstConf::Setting.new(name: ["APP", "TEST_VAR"], prefix: "APP") do
363
+ default nil
364
+ end
365
+
366
+ expect(setting.default_value).to be_nil
367
+ end
368
+ end
369
+
370
+ describe "#configured_value_or_default_value" do
371
+ it "returns configured value when present" do
372
+ ENV['APP_TEST_VAR'] = 'configured_value'
373
+
374
+ setting = ConstConf::Setting.new(name: ["APP", "TEST_VAR"], prefix: "APP") do
375
+ default "default_value"
376
+ end
377
+
378
+ expect(setting.configured_value_or_default_value).to eq "configured_value"
379
+ end
380
+
381
+ it "returns default value when configured value is nil" do
382
+ ENV['APP_TEST_VAR'] = nil
383
+
384
+ setting = ConstConf::Setting.new(name: ["APP", "TEST_VAR"], prefix: "APP") do
385
+ default "default_value"
386
+ end
387
+
388
+ expect(setting.configured_value_or_default_value).to eq "default_value"
389
+ end
390
+
391
+ it "returns nil when both configured and default values are nil" do
392
+ ENV['APP_TEST_VAR'] = nil
393
+
394
+ setting = ConstConf::Setting.new(name: ["APP", "TEST_VAR"], prefix: "APP") do
395
+ default nil
396
+ end
397
+
398
+ expect(setting.configured_value_or_default_value).to be_nil
399
+ end
400
+ end
401
+
402
+ describe "#configured_value" do
403
+ it "returns env var value when set and not ignored" do
404
+ ENV['APP_TEST_VAR'] = 'value'
405
+
406
+ setting = ConstConf::Setting.new(name: ["APP", "TEST_VAR"], prefix: "APP") do
407
+ default "default"
408
+ end
409
+
410
+ expect(setting.configured_value).to eq "value"
411
+ end
412
+
413
+ it "returns nil when env var is not set" do
414
+ ENV['APP_TEST_VAR'] = nil
415
+
416
+ setting = ConstConf::Setting.new(name: ["APP", "TEST_VAR"], prefix: "APP") do
417
+ default "default"
418
+ end
419
+
420
+ expect(setting.configured_value).to be_nil
421
+ end
422
+
423
+ it "returns nil when ignored" do
424
+ ENV['APP_TEST_VAR'] = 'value'
425
+
426
+ setting = ConstConf::Setting.new(name: ["APP", "TEST_VAR"], prefix: "APP") do
427
+ ignored true
428
+ default "default"
429
+ end
430
+
431
+ expect(setting.configured_value).to be_nil
432
+ end
433
+ end
434
+
435
+ describe "#checked?" do
436
+ context "with default check (always passes)" do
437
+ it "returns :unchecked_true by default" do
438
+ setting = ConstConf::Setting.new(name: ["APP", "TEST_VAR"], prefix: "APP") do
439
+ description 'Test setting'
440
+ end
441
+ expect(setting.checked?).to be :unchecked_true
442
+ end
443
+ end
444
+
445
+ context "with custom check that passes" do
446
+ it "returns true when custom check passes" do
447
+ setting = ConstConf::Setting.new(name: ["APP", "TEST_VAR"], prefix: "APP") do
448
+ description 'Test setting'
449
+ check ->(setting) { true } # Always pass
450
+ end
451
+ expect(setting.checked?).to be true
452
+ end
453
+ end
454
+
455
+ context "with custom check that fails" do
456
+ it "returns false when custom check fails" do
457
+ setting = ConstConf::Setting.new(name: ["APP", "TEST_VAR"], prefix: "APP") do
458
+ description 'Test setting'
459
+ check ->(setting) { false } # Always fail
460
+ end
461
+ expect(setting.checked?).to be false
462
+ end
463
+ end
464
+
465
+ context "with custom check that uses setting value" do
466
+ it "returns true when check validates value properly" do
467
+ ENV['APP_TEST_VAR'] = 'valid_value'
468
+
469
+ setting = ConstConf::Setting.new(name: ["APP", "TEST_VAR"], prefix: "APP") do
470
+ description 'Test setting'
471
+ check ->(setting) {
472
+ value = setting.value
473
+ !value.nil? && value.length > 5 # Only pass if value length > 5
474
+ }
475
+ end
476
+ expect(setting.checked?).to be true
477
+ end
478
+
479
+ it "returns false when check validates value improperly" do
480
+ ENV['APP_TEST_VAR'] = 'short'
481
+
482
+ setting = ConstConf::Setting.new(name: ["APP", "TEST_VAR"], prefix: "APP") do
483
+ description 'Test setting'
484
+ check ->(setting) {
485
+ value = setting.value
486
+ !value.nil? && value.length > 5 # Only pass if value length > 5
487
+ }
488
+ end
489
+ expect(setting.checked?).to be false
490
+ end
491
+ end
492
+ end
493
+
494
+ describe "#decoded_value" do
495
+ it "applies decoding when proc is present" do
496
+ setting = ConstConf::Setting.new(name: ["APP", "TEST_VAR"], prefix: "APP") do
497
+ decode ->(val) { val.upcase }
498
+ end
499
+ expect(setting).to be_decoding
500
+ expect(setting.send(:decoded_value, "test")).to eq "TEST"
501
+ end
502
+
503
+ it "returns value unchanged when no decoding is present" do
504
+ setting = ConstConf::Setting.new(name: ["APP", "TEST_VAR"], prefix: "APP") do
505
+ decode nil
506
+ end
507
+
508
+ expect(setting.send(:decoded_value, "test")).to eq "test"
509
+ end
510
+ end
511
+
512
+ context 'with parent namespace' do
513
+ module TestParentNamespace
514
+ include ConstConf
515
+ end
516
+
517
+ describe "#view" do
518
+ it "generates tree structure for setting" do
519
+ setting = TestParentNamespace.module_eval do
520
+ ConstConf::Setting.new(name: ["APP", "VIEW_TEST"], prefix: "APP") do
521
+ description 'View test variable'
522
+ default 'view_default'
523
+ required true
524
+ sensitive true
525
+ end
526
+ end
527
+
528
+ # Test that view works with StringIO
529
+ io = StringIO.new
530
+ setting.view(io: io)
531
+ output = io.string
532
+
533
+ expect(output).to include('VIEW_TEST')
534
+ expect(output).to include('View test variable')
535
+ expect(output).to include('APP_VIEW_TEST')
536
+ expect(output).not_to include('view_default')
537
+ expect(output).to include('required 🔴')
538
+ expect(output).to include('sensitive 🔒')
539
+ end
540
+
541
+ it "shows check status in view output" do
542
+ setting = TestParentNamespace.module_eval do
543
+ ConstConf::Setting.new(name: ["APP", "CHECK_VAR"], prefix: "APP") do
544
+ description 'Check test variable'
545
+ check ->(setting) { true }
546
+ end
547
+ end
548
+
549
+ io = StringIO.new
550
+ setting.view(io: io)
551
+ output = io.string
552
+
553
+ expect(output).to include('checked ✅')
554
+ end
555
+
556
+ it "shows failed check in view output" do
557
+ setting = TestParentNamespace.module_eval do
558
+ ConstConf::Setting.new(name: ["APP", "FAILED_VAR"], prefix: "APP") do
559
+ description 'Failed check variable'
560
+ check ->(setting) { false }
561
+ end
562
+ end
563
+
564
+ io = StringIO.new
565
+ setting.view(io: io)
566
+ output = io.string
567
+
568
+ expect(output).to include('checked ❌')
569
+ end
570
+ end
571
+
572
+ describe "#to_s" do
573
+ it "returns string representation with all metadata" do
574
+ setting = TestParentNamespace.module_eval do
575
+ ConstConf::Setting.new(name: ["APP", "TO_S_TEST"], prefix: "APP") do
576
+ description 'To string test'
577
+ default 'to_string_default'
578
+ required true
579
+ end
580
+ end
581
+
582
+ output = setting.to_s
583
+ expect(output).to be_a(String)
584
+ expect(output).to include('TO_S_TEST')
585
+ expect(output).to include('To string test')
586
+ expect(output).to include('default "to_string_default"')
587
+ expect(output).to include('required 🔴')
588
+ end
589
+ end
590
+
591
+ describe "#inspect" do
592
+ it "returns same format as to_s" do
593
+ setting = TestParentNamespace.module_eval do
594
+ ConstConf::Setting.new(name: ["APP", "INSPECT_TEST"], prefix: "APP") do
595
+ description 'Inspect test'
596
+ default 'inspect_default'
597
+ end
598
+ end
599
+
600
+ inspect_output = setting.inspect
601
+ to_s_output = setting.to_s
602
+
603
+ expect(inspect_output).to be_a(String)
604
+ expect(to_s_output).to be_a(String)
605
+ expect(inspect_output).to eq to_s_output
606
+ end
607
+
608
+ it "returns same format as to_s without colors" do
609
+ require 'irb'
610
+ allow(IRB.conf).to receive(:[]).with(:USE_COLORIZE).and_return true
611
+
612
+ setting = TestParentNamespace.module_eval do
613
+ ConstConf::Setting.new(name: ["APP", "INSPECT_TEST"], prefix: "APP") do
614
+ description 'Inspect test'
615
+ default 'inspect_default'
616
+ end
617
+ end
618
+
619
+ inspect_output = setting.inspect
620
+ to_s_output = setting.to_s
621
+
622
+ expect(inspect_output).to be_a(String)
623
+ expect(to_s_output).to be_a(String)
624
+ expect(inspect_output).to eq Term::ANSIColor.uncolor(to_s_output)
625
+ end
626
+ end
627
+ end
628
+ end