contracted_value 0.1.0.alpha.1

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,680 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ ::RSpec.describe ::ContractedValue::Value do
6
+
7
+ shared_examples_for "attribute declaration" do
8
+ let(:value_class) do
9
+ raise "`value_class` absent"
10
+ end
11
+
12
+ example "does not raise error when NOT declaring any attribute" do
13
+ expect(->{ value_class }).to_not raise_error
14
+ end
15
+
16
+ example "does not raise error when declaring 1 attribute" do
17
+ expect(
18
+ ->{
19
+ value_class.class_eval do
20
+ attribute(:attribute_1)
21
+ end
22
+ },
23
+ ).to_not raise_error
24
+ end
25
+
26
+ example "does not raise error when declaring N attributes with different names" do
27
+ expect(
28
+ ->{
29
+ value_class.class_eval do
30
+ attribute(:attribute_1)
31
+ attribute(:attribute_2)
32
+ end
33
+ },
34
+ ).to_not raise_error
35
+ end
36
+
37
+ example "does raise error when declaring N attributes with the same name" do
38
+ expect(
39
+ ->{
40
+ value_class.class_eval do
41
+ attribute(:attribute_1)
42
+ attribute(:attribute_2)
43
+ attribute(:attribute_1)
44
+ end
45
+ },
46
+ ).to raise_error(::ContractedValue::Errors::DuplicateAttributeDeclaration)
47
+ end
48
+ end
49
+
50
+
51
+ describe "attribute declaration" do
52
+ it_behaves_like "attribute declaration" do
53
+ let(:value_class) do
54
+ Class.new(described_class)
55
+ end
56
+ end
57
+ end
58
+
59
+ describe "attribute assignment" do
60
+ context "on object initialization" do
61
+ context "with class with no attribute declared with contract" do
62
+ let(:value_class) do
63
+ Class.new(described_class) do
64
+ attribute(:attribute_1)
65
+ attribute(:attribute_2)
66
+ end
67
+ end
68
+
69
+ let(:default_inputs) do
70
+ {
71
+ attribute_1: 1,
72
+ attribute_2: 1,
73
+ }
74
+ end
75
+
76
+ let(:non_hash) do
77
+ []
78
+ end
79
+
80
+ it "does raise error when input is not a hash" do
81
+ aggregate_failures do
82
+ expect(
83
+ ->{
84
+ value_class.new(
85
+ non_hash,
86
+ )
87
+ },
88
+ ).to raise_error(::ContractedValue::Errors::InvalidInputType)
89
+ end
90
+ end
91
+
92
+ it "does not raise error when input is a hash" do
93
+ aggregate_failures do
94
+ expect(
95
+ ->{
96
+ value_class.new(
97
+ default_inputs,
98
+ )
99
+ },
100
+ ).to_not raise_error
101
+ end
102
+ end
103
+
104
+ it "does not raise error when input is a value" do
105
+ aggregate_failures do
106
+ expect(
107
+ ->{
108
+ value_class.new(
109
+ value_class.new(
110
+ default_inputs,
111
+ ),
112
+ )
113
+ },
114
+ ).to_not raise_error
115
+ end
116
+ end
117
+
118
+ it "does raise error when value of any declared attribute missing from input" do
119
+ aggregate_failures do
120
+ [
121
+ :attribute_1,
122
+ :attribute_2,
123
+ ].each do |attr_name|
124
+ expect(
125
+ ->{
126
+ value_class.new(
127
+ default_inputs.dup.tap{|h| h.delete(attr_name)},
128
+ )
129
+ },
130
+ ).to raise_error(::ContractedValue::Errors::MissingAttributeInput)
131
+ end
132
+ end
133
+ end
134
+
135
+ it "does not raise error when values of all declared attributes are present, even they are all nil" do
136
+ aggregate_failures do
137
+ [
138
+ :attribute_1,
139
+ :attribute_2,
140
+ ].each do |attr_name|
141
+ expect(
142
+ ->{
143
+ value_class.new(
144
+ default_inputs.each_with_object({}) do |(k, _v), h|
145
+ h[k] = nil
146
+ end
147
+ )
148
+ },
149
+ ).to_not raise_error
150
+ end
151
+ end
152
+ end
153
+ end
154
+
155
+ context "with class with some attributes declared with contract" do
156
+ let(:value_class) do
157
+ Class.new(described_class) do
158
+ attribute(
159
+ :attribute_with_contract_1,
160
+ contract: ::Contracts::Builtin::And[::String, ::Contracts::Builtin::Not[::Contracts::Builtin::Send[:empty?]]],
161
+ )
162
+ attribute(
163
+ :attribute_with_contract_2,
164
+ contract: ::Contracts::Builtin::NatPos,
165
+ )
166
+ end
167
+ end
168
+
169
+ let(:default_inputs) do
170
+ {
171
+ attribute_with_contract_1: "yo",
172
+ attribute_with_contract_2: 1,
173
+ }
174
+ end
175
+
176
+
177
+ it "does not raise error when all values are valid according to contracts" do
178
+ expect(
179
+ ->{
180
+ value_class.new(
181
+ default_inputs
182
+ )
183
+ },
184
+ ).to_not raise_error
185
+ end
186
+
187
+ it "does raise error when any value is invalid according to contracts" do
188
+ aggregate_failures do
189
+ expect(
190
+ ->{
191
+ value_class.new(
192
+ default_inputs.merge(
193
+ attribute_with_contract_1: "",
194
+ ),
195
+ )
196
+ },
197
+ ).to raise_error(::ContractedValue::Errors::InvalidAttributeValue)
198
+
199
+ expect(
200
+ ->{
201
+ value_class.new(
202
+ default_inputs.merge(
203
+ attribute_with_contract_2: 0,
204
+ ),
205
+ )
206
+ },
207
+ ).to raise_error(::ContractedValue::Errors::InvalidAttributeValue)
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end
213
+
214
+ describe "attribute input value freezing" do
215
+ let(:value_object) do
216
+ value_class.new(
217
+ a_hash: hash_as_input,
218
+ )
219
+ end
220
+
221
+ let(:hash_as_input) do
222
+ {
223
+ hash: hash_as_deep_nested_content,
224
+ }
225
+ end
226
+
227
+ let(:hash_as_deep_nested_content) do
228
+ ::Hash.new
229
+ end
230
+
231
+ shared_examples "attribute deeply frozen" do
232
+ it "does freeze the new object" do
233
+ expect(value_object).to be_frozen
234
+ end
235
+
236
+ it "does freeze the inputs" do
237
+ # Create it just before expectation
238
+ value_object
239
+
240
+ expect(
241
+ ->{
242
+ hash_as_input[:a] = nil
243
+ },
244
+ ).to raise_error(::RuntimeError, /can't modify frozen/)
245
+ end
246
+
247
+ it "does deeply freeze the inputs" do
248
+ # Create it just before expectation
249
+ value_object
250
+
251
+ expect(
252
+ ->{
253
+ hash_as_deep_nested_content[:a] = nil
254
+ },
255
+ ).to raise_error(::RuntimeError, /can't modify frozen/)
256
+ end
257
+ end
258
+
259
+ shared_examples "attribute shallowly frozen" do
260
+ it "does freeze the new object" do
261
+ expect(value_object).to be_frozen
262
+ end
263
+
264
+ it "does freeze the inputs" do
265
+ # Create it just before expectation
266
+ value_object
267
+
268
+ expect(
269
+ ->{
270
+ hash_as_input[:a] = nil
271
+ },
272
+ ).to raise_error(::RuntimeError, /can't modify frozen/)
273
+ end
274
+
275
+ it "does not deeply freeze the inputs" do
276
+ # Create it just before expectation
277
+ value_object
278
+
279
+ expect(
280
+ ->{
281
+ hash_as_deep_nested_content[:a] = nil
282
+ },
283
+ ).to_not raise_error
284
+ end
285
+ end
286
+
287
+ shared_examples "attribute not frozen" do
288
+ it "does freeze the new object" do
289
+ expect(value_object).to be_frozen
290
+ end
291
+
292
+ it "does not freeze the inputs" do
293
+ # Create it just before expectation
294
+ value_object
295
+
296
+ expect(
297
+ ->{
298
+ hash_as_input[:a] = nil
299
+ },
300
+ ).to_not raise_error
301
+ end
302
+
303
+ it "does not deeply freeze the inputs" do
304
+ # Create it just before expectation
305
+ value_object
306
+
307
+ expect(
308
+ ->{
309
+ hash_as_deep_nested_content[:a] = nil
310
+ },
311
+ ).to_not raise_error
312
+ end
313
+ end
314
+
315
+ context "when an attribute is declared without `refrigeration_mode` option" do
316
+ let(:value_class) do
317
+ Class.new(described_class) do
318
+ attribute(:a_hash)
319
+ end
320
+ end
321
+
322
+ it_behaves_like "attribute deeply frozen"
323
+ end
324
+
325
+ context "when an attribute is declared with `refrigeration_mode` option (:deep)" do
326
+ let(:value_class) do
327
+ Class.new(described_class) do
328
+ attribute(
329
+ :a_hash,
330
+ refrigeration_mode: :deep,
331
+ )
332
+ end
333
+ end
334
+
335
+ it_behaves_like "attribute deeply frozen"
336
+ end
337
+
338
+ context "when an attribute is declared with `refrigeration_mode` option (:shallow)" do
339
+ let(:value_class) do
340
+ Class.new(described_class) do
341
+ attribute(
342
+ :a_hash,
343
+ refrigeration_mode: :shallow,
344
+ )
345
+ end
346
+ end
347
+
348
+ it_behaves_like "attribute shallowly frozen"
349
+ end
350
+
351
+ context "when an attribute is declared with `refrigeration_mode` option (:none)" do
352
+ let(:value_class) do
353
+ Class.new(described_class) do
354
+ attribute(
355
+ :a_hash,
356
+ refrigeration_mode: :none,
357
+ )
358
+ end
359
+ end
360
+
361
+ it_behaves_like "attribute not frozen"
362
+ end
363
+
364
+ context "when an attribute is declared with invalid `refrigeration_mode` option" do
365
+ let(:value_class) do
366
+ Class.new(described_class) do
367
+ attribute(
368
+ :a_hash,
369
+ refrigeration_mode: :meow,
370
+ )
371
+ end
372
+ end
373
+
374
+ it "raises error on attribute declaration" do
375
+ expect do
376
+ value_class
377
+ end.to raise_error(::ContractedValue::Errors::InvalidRefrigerationMode)
378
+ end
379
+ end
380
+ end
381
+
382
+ describe "attribute default value" do
383
+ context "when with option :default_value" do
384
+ let(:value_class) do
385
+ # Workaround strange issue that
386
+ # `let` cannot be used in class block
387
+ default_val = default_value
388
+ attr_contract = attribute_contract
389
+
390
+ Class.new(described_class) do
391
+ attribute(
392
+ :attr_with_default,
393
+ default_value: default_val,
394
+ contract: attr_contract,
395
+ )
396
+ end
397
+ end
398
+
399
+ let(:default_value) do
400
+ 1
401
+ end
402
+
403
+ let(:attribute_contract) do
404
+ ::Contracts::Builtin::Any
405
+ end
406
+
407
+ shared_examples "attribute with default value" do
408
+ it "returns default value when nothing provided" do
409
+ expect(
410
+ value_class.new.attr_with_default,
411
+ ).to eq(default_value)
412
+ end
413
+
414
+ it "returns provided value when something provided" do
415
+ [
416
+ nil,
417
+ :wut,
418
+ -> { },
419
+ ].each do |provided_value|
420
+ aggregate_failures do
421
+ expect(
422
+ value_class.new(
423
+ attr_with_default: provided_value,
424
+ ).attr_with_default,
425
+ ).to eq(provided_value)
426
+ end
427
+ end
428
+ end
429
+ end
430
+
431
+ context "and default value provided is `nil`" do
432
+ let(:default_value) do
433
+ nil
434
+ end
435
+
436
+ it_behaves_like "attribute with default value"
437
+
438
+ context "when default value violates contract" do
439
+ let(:attribute_contract) do
440
+ ::Contracts::Builtin::Not[nil]
441
+ end
442
+
443
+ it "does raise error on attribute declaration" do
444
+ expect do
445
+ value_class
446
+ end.to raise_error(::ContractedValue::Errors::InvalidAttributeDefaultValue)
447
+ end
448
+ end
449
+ end
450
+
451
+ context "and default value provided is not `nil`" do
452
+ let(:default_value) do
453
+ 123
454
+ end
455
+
456
+ it_behaves_like "attribute with default value"
457
+
458
+ context "when default value violates contract" do
459
+ let(:attribute_contract) do
460
+ ::Symbol
461
+ end
462
+
463
+ it "does raise error on attribute declaration" do
464
+ expect do
465
+ value_class
466
+ end.to raise_error(::ContractedValue::Errors::InvalidAttributeDefaultValue)
467
+ end
468
+ end
469
+ end
470
+
471
+ context "and default value provided is mutable" do
472
+
473
+ context "as a string" do
474
+ let(:default_value) do
475
+ "PikaPika"
476
+ end
477
+
478
+ it_behaves_like "attribute with default value"
479
+
480
+ it "returns default value as frozen" do
481
+ expect(
482
+ value_class.new.attr_with_default,
483
+ ).to be_frozen
484
+ end
485
+ end
486
+
487
+ context "as a hash" do
488
+ let(:default_value) do
489
+ {a: {b: :c}}
490
+ end
491
+
492
+ it_behaves_like "attribute with default value"
493
+
494
+ it "returns default value as frozen" do
495
+ aggregate_failures do
496
+ val = value_class.new.attr_with_default.fetch(:a)
497
+
498
+ expect(val).to eq({b: :c})
499
+ expect(val).to be_frozen
500
+ end
501
+ end
502
+ end
503
+
504
+ end
505
+ end
506
+ end
507
+
508
+ describe "sub-classes of a value class" do
509
+
510
+ describe "attribute declaration" do
511
+ let(:parent_value_class) do
512
+ Class.new(described_class)
513
+ end
514
+
515
+ it_behaves_like "attribute declaration" do
516
+ let(:value_class) do
517
+ Class.new(parent_value_class)
518
+ end
519
+ end
520
+
521
+ describe "usage of parent class attribute" do
522
+
523
+ let(:parent_value_class) do
524
+ Class.new(described_class).tap do |klass|
525
+ klass.class_eval do
526
+ # Too lazy to include parent attributes in all examples
527
+ attribute(:attribute_1)
528
+ end
529
+ end
530
+ end
531
+ let(:child_value_class) do
532
+ Class.new(parent_value_class)
533
+ end
534
+
535
+ example "does not raise error" do
536
+ expect(
537
+ ->{
538
+ child_value_class.new(attribute_1: "wut")
539
+ },
540
+ ).to_not raise_error
541
+ end
542
+
543
+ end
544
+
545
+ describe "for new attributes absent in parent class" do
546
+
547
+ let(:parent_value_class) do
548
+ Class.new(described_class).tap do |klass|
549
+ klass.class_eval do
550
+ # Too lazy to include parent attributes in all examples
551
+ attribute(:attribute_1, default_value: nil)
552
+ attribute(:attribute_2, default_value: nil)
553
+ end
554
+ end
555
+ end
556
+
557
+ let(:child_value_class) do
558
+ Class.new(parent_value_class)
559
+ end
560
+
561
+ example "does not raise error when declaring 1 new attribute" do
562
+ expect(
563
+ ->{
564
+ child_value_class.class_eval do
565
+ attribute(:attribute_3)
566
+ end
567
+ },
568
+ ).to_not raise_error
569
+ end
570
+
571
+ example "does not raise error when declaring N attributes with different names" do
572
+ expect(
573
+ ->{
574
+ child_value_class.class_eval do
575
+ attribute(:attribute_3)
576
+ attribute(:attribute_4)
577
+ end
578
+ },
579
+ ).to_not raise_error
580
+ end
581
+
582
+ example "does raise error when declaring N attributes with the same name" do
583
+ expect(
584
+ ->{
585
+ child_value_class.class_eval do
586
+ attribute(:attribute_3)
587
+ attribute(:attribute_4)
588
+ attribute(:attribute_3)
589
+ end
590
+ },
591
+ ).to raise_error(::ContractedValue::Errors::DuplicateAttributeDeclaration)
592
+ end
593
+
594
+ end
595
+ end
596
+
597
+ describe "attribute redeclaration" do
598
+
599
+ let(:child_value_class) do
600
+ Class.new(parent_value_class)
601
+ end
602
+
603
+ describe "for existing attributes from parent class" do
604
+ let(:parent_value_class) do
605
+ Class.new(described_class).tap do |klass|
606
+ klass.class_eval do
607
+ attribute(:attribute_1)
608
+ attribute(:attribute_2)
609
+ end
610
+ end
611
+ end
612
+ it_behaves_like "attribute declaration" do
613
+ let(:value_class) do
614
+ child_value_class
615
+ end
616
+ end
617
+ end
618
+
619
+ describe "for existing attributes from parent class" do
620
+
621
+ let(:parent_value_class) do
622
+ Class.new(described_class).tap do |klass|
623
+ klass.class_eval do
624
+ attribute(
625
+ :attribute_1,
626
+ contract: ::String,
627
+ refrigeration_mode: :deep,
628
+ # `default_value` not specified on purpose
629
+ )
630
+ end
631
+ end
632
+ end
633
+
634
+ example "does not raise error when declaring existing attribute with different contract" do
635
+ expect(
636
+ ->{
637
+ child_value_class.class_eval do
638
+ attribute(
639
+ :attribute_1,
640
+ contract: ::Contracts::Builtin::NatPos
641
+ )
642
+ end
643
+ child_value_class.new(attribute_1: "")
644
+ },
645
+ ).to raise_error(::ContractedValue::Errors::InvalidAttributeValue)
646
+ end
647
+
648
+ example "does not raise error when declaring existing attribute with different default_value" do
649
+ expect(
650
+ ->{
651
+ child_value_class.class_eval do
652
+ attribute(
653
+ :attribute_1,
654
+ default_value: nil,
655
+ )
656
+ end
657
+ child_value_class.new
658
+ },
659
+ ).to_not raise_error
660
+ end
661
+
662
+ example "does not raise error when declaring existing attribute with different refrigeration_mode" do
663
+ child_value_class.class_eval do
664
+ attribute(
665
+ :attribute_1,
666
+ refrigeration_mode: :none,
667
+ )
668
+ end
669
+ value_object = child_value_class.new(attribute_1: String.new)
670
+ expect(value_object).to be_frozen
671
+ expect(value_object.attribute_1).to_not be_frozen
672
+ end
673
+
674
+ end
675
+
676
+ end
677
+
678
+ end
679
+
680
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ ::RSpec.describe ::ContractedValue do
6
+
7
+ it "has a version number" do
8
+ expect(described_class::VERSION).not_to eq(nil)
9
+ end
10
+
11
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift(File.expand_path("../../lib", __FILE__))
4
+ require "contracted_value"
5
+
6
+ if ENV["TRAVIS"]
7
+ require "simplecov"
8
+ SimpleCov.start
9
+
10
+ require "codecov"
11
+ SimpleCov.formatter = SimpleCov::Formatter::Codecov
12
+ end
13
+
14
+ require "rspec"
15
+ require "rspec/its"