functional-light-service 0.5.4 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/project-build.yml +35 -11
  3. data/.rubocop.yml +101 -160
  4. data/AUDIT-functional-light-service.md +352 -0
  5. data/CHANGELOG.md +45 -0
  6. data/README.md +88 -2
  7. data/audit/bench.rb +99 -0
  8. data/audit/verify_findings.rb +172 -0
  9. data/functional-light-service.gemspec +15 -21
  10. data/lib/functional-light-service/action.rb +97 -101
  11. data/lib/functional-light-service/configuration.rb +26 -24
  12. data/lib/functional-light-service/context/key_verifier.rb +124 -118
  13. data/lib/functional-light-service/context.rb +63 -20
  14. data/lib/functional-light-service/deprecations.rb +26 -0
  15. data/lib/functional-light-service/errors.rb +8 -6
  16. data/lib/functional-light-service/functional/enum.rb +286 -250
  17. data/lib/functional-light-service/functional/maybe.rb +21 -15
  18. data/lib/functional-light-service/functional/monad.rb +77 -66
  19. data/lib/functional-light-service/functional/null.rb +88 -74
  20. data/lib/functional-light-service/functional/option.rb +100 -97
  21. data/lib/functional-light-service/functional/result.rb +129 -116
  22. data/lib/functional-light-service/functional/sequencer.rb +144 -0
  23. data/lib/functional-light-service/localization_adapter.rb +48 -47
  24. data/lib/functional-light-service/organizer/execute.rb +16 -14
  25. data/lib/functional-light-service/organizer/iterate.rb +30 -25
  26. data/lib/functional-light-service/organizer/reduce_if.rb +19 -17
  27. data/lib/functional-light-service/organizer/reduce_until.rb +22 -20
  28. data/lib/functional-light-service/organizer/scoped_reducable.rb +15 -13
  29. data/lib/functional-light-service/organizer/with_callback.rb +28 -26
  30. data/lib/functional-light-service/organizer/with_reducer.rb +81 -77
  31. data/lib/functional-light-service/organizer/with_reducer_factory.rb +20 -18
  32. data/lib/functional-light-service/organizer/with_reducer_log_decorator.rb +110 -108
  33. data/lib/functional-light-service/organizer.rb +114 -114
  34. data/lib/functional-light-service/testing/context_factory.rb +48 -42
  35. data/lib/functional-light-service/testing.rb +3 -1
  36. data/lib/functional-light-service/version.rb +5 -3
  37. data/lib/functional-light-service.rb +31 -28
  38. data/spec/acceptance/after_actions_spec.rb +87 -71
  39. data/spec/acceptance/before_actions_spec.rb +115 -98
  40. data/spec/acceptance/custom_log_from_organizer_spec.rb +61 -60
  41. data/spec/acceptance/deprecation_warnings_spec.rb +82 -0
  42. data/spec/acceptance/fail_spec.rb +52 -50
  43. data/spec/acceptance/message_localization_spec.rb +119 -118
  44. data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +68 -65
  45. data/spec/acceptance/organizer/reduce_if_spec.rb +89 -89
  46. data/spec/acceptance/organizer/with_callback_spec.rb +113 -110
  47. data/spec/acceptance/{not_having_call_method_warning_spec.rb → organizer_entry_point_spec.rb} +10 -7
  48. data/spec/acceptance/rollback_spec.rb +183 -132
  49. data/spec/action_expects_and_promises_spec.rb +97 -93
  50. data/spec/action_promised_keys_spec.rb +126 -122
  51. data/spec/context_spec.rb +289 -197
  52. data/spec/examples/controller_spec.rb +63 -63
  53. data/spec/examples/validate_address_spec.rb +38 -37
  54. data/spec/lib/deterministic/currify_spec.rb +90 -88
  55. data/spec/lib/deterministic/null_spec.rb +6 -1
  56. data/spec/lib/deterministic/option_spec.rb +140 -137
  57. data/spec/lib/deterministic/result/result_map_spec.rb +155 -154
  58. data/spec/lib/deterministic/result/result_shared.rb +3 -2
  59. data/spec/lib/deterministic/result_spec.rb +2 -2
  60. data/spec/lib/deterministic/sequencer_spec.rb +506 -0
  61. data/spec/lib/edge_cases_spec.rb +156 -0
  62. data/spec/lib/enum_spec.rb +1 -1
  63. data/spec/lib/native_pattern_matching_spec.rb +74 -0
  64. data/spec/organizer_spec.rb +115 -114
  65. data/spec/readme_spec.rb +45 -47
  66. data/spec/sample/calculates_order_tax_action_spec.rb +16 -16
  67. data/spec/sample/calculates_tax_spec.rb +1 -1
  68. data/spec/sample/looks_up_tax_percentage_action_spec.rb +55 -55
  69. data/spec/sample/tax/calculates_order_tax_action.rb +10 -9
  70. data/spec/sample/tax/looks_up_tax_percentage_action.rb +28 -27
  71. data/spec/sample/tax/provides_free_shipping_action.rb +11 -10
  72. data/spec/spec_helper.rb +6 -0
  73. data/spec/test_doubles.rb +628 -599
  74. data/spec/testing/context_factory_spec.rb +21 -0
  75. metadata +48 -162
  76. data/lib/functional-light-service/organizer/verify_call_method_exists.rb +0 -29
  77. data/spec/acceptance/include_warning_spec.rb +0 -29
@@ -0,0 +1,506 @@
1
+ require 'spec_helper'
2
+
3
+ describe FunctionalLightService::Sequencer do
4
+ include FunctionalLightService::Prelude::Result
5
+
6
+ let(:test_class) { Class.new { include FunctionalLightService::Sequencer } }
7
+ let(:test_instance) { test_class.new }
8
+ let(:arbitrary_success) { Success(double) }
9
+
10
+ # This mock method makes #arbitrary_success available within the operations
11
+ # in the test sequences
12
+ before { allow(test_instance).to receive(:arbitrary_success).and_return(arbitrary_success) }
13
+
14
+ it 'requires #and_yield to be specified' do
15
+ expect do
16
+ in_sequence do
17
+ get(:_) { arbitrary_success }
18
+ end
19
+ end.to raise_error(described_class::InvalidSequenceError, 'and_yield not called')
20
+ end
21
+
22
+ it 'does not allow calling #and_yield multiple times' do
23
+ expect do
24
+ in_sequence do
25
+ and_yield { arbitrary_success }
26
+ and_yield { arbitrary_success }
27
+ end
28
+ end.to raise_error(described_class::InvalidSequenceError, 'and_yield already called')
29
+ end
30
+
31
+ it 'does not allow calling #get after #and_yield' do
32
+ expect do
33
+ in_sequence do
34
+ and_yield { arbitrary_success }
35
+ get(:_) { arbitrary_success }
36
+ end
37
+ end.to raise_error(described_class::InvalidSequenceError, 'and_yield already called')
38
+ end
39
+
40
+ it 'does not allow calling #let after #and_yield' do
41
+ expect do
42
+ in_sequence do
43
+ and_yield { arbitrary_success }
44
+ let(:_) { arbitrary_success }
45
+ end
46
+ end.to raise_error(described_class::InvalidSequenceError, 'and_yield already called')
47
+ end
48
+
49
+ it 'does not allow calling #and_then after #and_yield' do
50
+ expect do
51
+ in_sequence do
52
+ and_yield { arbitrary_success }
53
+ and_then { arbitrary_success }
54
+ end
55
+ end.to raise_error(described_class::InvalidSequenceError, 'and_yield already called')
56
+ end
57
+
58
+ it 'does not allow calling #observe after #and_yield' do
59
+ expect do
60
+ in_sequence do
61
+ and_yield { arbitrary_success }
62
+ observe { arbitrary_success }
63
+ end
64
+ end.to raise_error(described_class::InvalidSequenceError, 'and_yield already called')
65
+ end
66
+
67
+ it 'requires a block for every operation' do
68
+ expect { in_sequence { get(:_) } }.to raise_error(ArgumentError, 'no block given')
69
+ expect { in_sequence { let(:_) } }.to raise_error(ArgumentError, 'no block given')
70
+ expect { in_sequence { and_then } }.to raise_error(ArgumentError, 'no block given')
71
+ expect { in_sequence { observe } }.to raise_error(ArgumentError, 'no block given')
72
+ expect { in_sequence { and_yield } }.to raise_error(ArgumentError, 'no block given')
73
+ end
74
+
75
+ context 'when #and_yield succeeds' do
76
+ let(:yielder_result) { Success('yield') }
77
+ before { allow(test_instance).to receive(:yielder).and_return(yielder_result) }
78
+
79
+ it "returns and_yield's result" do
80
+ result = in_sequence do
81
+ and_yield { yielder }
82
+ end
83
+ expect(result).to eq(yielder_result)
84
+ end
85
+
86
+ it 'ignores the return value of the #in_sequence block' do
87
+ result = in_sequence do
88
+ and_yield { yielder }
89
+ 'a different return value'
90
+ end
91
+ expect(result).to eq(yielder_result)
92
+ end
93
+ end
94
+
95
+ context 'when #and_yield fails' do
96
+ let(:yielder_result) { Failure('yield') }
97
+ before { allow(test_instance).to receive(:yielder).and_return(yielder_result) }
98
+
99
+ it "returns and_yield's result" do
100
+ result = in_sequence do
101
+ and_yield { yielder }
102
+ end
103
+ expect(result).to eq(yielder_result)
104
+ end
105
+ end
106
+
107
+ context 'when #get succeeds' do
108
+ let(:getter_result) { Success('get') }
109
+ before { allow(test_instance).to receive(:getter).and_return(getter_result) }
110
+
111
+ it 'its result is available in a subsequent #get' do
112
+ allow(test_instance).to receive(:second_getter).and_return(arbitrary_success)
113
+
114
+ in_sequence do
115
+ get(:get_result) { getter }
116
+ get(:_) { second_getter(get_result) }
117
+ and_yield { arbitrary_success }
118
+ end
119
+
120
+ expect(test_instance).to have_received(:second_getter).with(getter_result.value)
121
+ end
122
+
123
+ it 'its result is not available in a previous #get' do
124
+ allow(test_instance).to receive(:second_getter)
125
+
126
+ expect do
127
+ in_sequence do
128
+ get(:_) { second_getter(get_result) }
129
+ get(:get_result) { getter }
130
+ and_yield { arbitrary_success }
131
+ end
132
+ end.to raise_error(NameError)
133
+ end
134
+
135
+ it 'its result is available in a subsequent #and_then' do
136
+ allow(test_instance).to receive(:and_then_function).and_return(arbitrary_success)
137
+
138
+ in_sequence do
139
+ get(:get_result) { getter }
140
+ and_then { and_then_function(get_result) }
141
+ and_yield { arbitrary_success }
142
+ end
143
+
144
+ expect(test_instance).to have_received(:and_then_function).with(getter_result.value)
145
+ end
146
+
147
+ it 'its result is available in a subsequent #observe' do
148
+ allow(test_instance).to receive(:observer)
149
+
150
+ in_sequence do
151
+ get(:get_result) { getter }
152
+ observe { observer(get_result) }
153
+ and_yield { arbitrary_success }
154
+ end
155
+
156
+ expect(test_instance).to have_received(:observer).with(getter_result.value)
157
+ end
158
+
159
+ it 'its result is available in #and_yield' do
160
+ allow(test_instance).to receive(:yielder).and_return(arbitrary_success)
161
+
162
+ in_sequence do
163
+ get(:get_result) { getter }
164
+ and_yield { yielder(get_result) }
165
+ end
166
+
167
+ expect(test_instance).to have_received(:yielder).with(getter_result.value)
168
+ end
169
+ end
170
+
171
+ context 'when multiple #gets succeed' do
172
+ let(:first_getter_result) { Success('get1') }
173
+ let(:second_getter_result) { Success('get2') }
174
+
175
+ before do
176
+ allow(test_instance).to receive(:first_getter).and_return(first_getter_result)
177
+ allow(test_instance).to receive(:second_getter).and_return(second_getter_result)
178
+ end
179
+
180
+ it 'both results are available in subsequent operations' do
181
+ allow(test_instance).to receive(:yielder).and_return(arbitrary_success)
182
+
183
+ in_sequence do
184
+ get(:first_get_result) { first_getter }
185
+ get(:second_get_result) { second_getter }
186
+ and_yield { yielder(first_get_result, second_get_result) }
187
+ end
188
+
189
+ expect(test_instance).to have_received(:yielder)
190
+ .with(first_getter_result.value, second_getter_result.value)
191
+ end
192
+ end
193
+
194
+ context 'when #get fails' do
195
+ let(:getter_result) { Failure('get') }
196
+ before { allow(test_instance).to receive(:getter).and_return(getter_result) }
197
+
198
+ it 'does not invoke subsequent #gets' do
199
+ allow(test_instance).to receive(:second_getter).and_return(arbitrary_success)
200
+
201
+ in_sequence do
202
+ get(:get_result) { getter }
203
+ get(:_) { second_getter(get_result) }
204
+ and_yield { arbitrary_success }
205
+ end
206
+
207
+ expect(test_instance).not_to have_received(:second_getter)
208
+ end
209
+
210
+ it 'does not invoke subsequent #and_thens' do
211
+ allow(test_instance).to receive(:and_then_function).and_return(arbitrary_success)
212
+
213
+ in_sequence do
214
+ get(:get_result) { getter }
215
+ and_then { and_then_function(get_result) }
216
+ and_yield { arbitrary_success }
217
+ end
218
+
219
+ expect(test_instance).not_to have_received(:and_then_function)
220
+ end
221
+
222
+ it 'does not invoke subsequent #observes' do
223
+ allow(test_instance).to receive(:observer)
224
+
225
+ in_sequence do
226
+ get(:get_result) { getter }
227
+ observe { observer(get_result) }
228
+ and_yield { arbitrary_success }
229
+ end
230
+
231
+ expect(test_instance).not_to have_received(:observer)
232
+ end
233
+
234
+ it 'does not invoke #and_yield' do
235
+ allow(test_instance).to receive(:yielder).and_return(arbitrary_success)
236
+
237
+ in_sequence do
238
+ get(:get_result) { getter }
239
+ and_yield { yielder }
240
+ end
241
+
242
+ expect(test_instance).not_to have_received(:yielder)
243
+ end
244
+
245
+ it 'returns the failure' do
246
+ result = in_sequence do
247
+ get(:get_result) { getter }
248
+ and_yield { arbitrary_success }
249
+ end
250
+
251
+ expect(result).to eq(getter_result)
252
+ end
253
+ end
254
+
255
+ context 'when #let succeeds' do
256
+ let(:object) { { :a => 'a' } }
257
+ before { allow(test_instance).to receive(:object).and_return(object) }
258
+
259
+ it 'its result is available in a subsequent #let' do
260
+ allow(test_instance).to receive(:test_function)
261
+
262
+ in_sequence do
263
+ let(:a) { object.fetch(:a) }
264
+ let(:_) { test_function(a) }
265
+ and_yield { arbitrary_success }
266
+ end
267
+
268
+ expect(test_instance).to have_received(:test_function).with(object.fetch(:a))
269
+ end
270
+
271
+ it 'its result is available in a subsequent #and_then' do
272
+ allow(test_instance).to receive(:and_then_function).and_return(arbitrary_success)
273
+
274
+ in_sequence do
275
+ let(:a) { object.fetch(:a) }
276
+ and_then { and_then_function(a) }
277
+ and_yield { arbitrary_success }
278
+ end
279
+
280
+ expect(test_instance).to have_received(:and_then_function).with(object.fetch(:a))
281
+ end
282
+
283
+ it 'its result is available in a subsequent #observe' do
284
+ allow(test_instance).to receive(:observer)
285
+
286
+ in_sequence do
287
+ let(:a) { object.fetch(:a) }
288
+ observe { observer(a) }
289
+ and_yield { arbitrary_success }
290
+ end
291
+
292
+ expect(test_instance).to have_received(:observer).with(object.fetch(:a))
293
+ end
294
+
295
+ it 'its result is available in #and_yield' do
296
+ allow(test_instance).to receive(:yielder).and_return(arbitrary_success)
297
+
298
+ in_sequence do
299
+ let(:a) { object.fetch(:a) }
300
+ and_yield { yielder(a) }
301
+ end
302
+
303
+ expect(test_instance).to have_received(:yielder).with(object.fetch(:a))
304
+ end
305
+ end
306
+
307
+ context 'when #let raises an error' do
308
+ let(:object) { { :a => 'a' } }
309
+ before { allow(test_instance).to receive(:object).and_return(object) }
310
+
311
+ it 'bubbles the raised error' do
312
+ expect do
313
+ in_sequence do
314
+ let(:b) { object.fetch(:b) }
315
+ and_yield { arbitrary_success }
316
+ end
317
+ end.to raise_error(KeyError)
318
+ end
319
+
320
+ it 'does not invoke #and_yield' do
321
+ allow(test_instance).to receive(:yielder).and_return(arbitrary_success)
322
+
323
+ begin
324
+ in_sequence do
325
+ let(:b) { object.fetch(:b) }
326
+ and_yield { yielder }
327
+ end
328
+ rescue KeyError
329
+ # Ignore
330
+ end
331
+
332
+ expect(test_instance).not_to have_received(:yielder)
333
+ end
334
+ end
335
+
336
+ context 'when #and_then succeeds' do
337
+ let(:and_then_result) { Success('and_then') }
338
+ before { allow(test_instance).to receive(:and_then_function).and_return(and_then_result) }
339
+
340
+ it 'continues the sequence' do
341
+ allow(test_instance).to receive(:another_step).and_return(arbitrary_success)
342
+
343
+ in_sequence do
344
+ and_then { and_then_function }
345
+ and_then { another_step }
346
+ and_yield { arbitrary_success }
347
+ end
348
+
349
+ expect(test_instance).to have_received(:another_step)
350
+ end
351
+ end
352
+
353
+ context 'when #and_then fails' do
354
+ let(:and_then_result) { Failure('and_then') }
355
+ before { allow(test_instance).to receive(:and_then_function).and_return(and_then_result) }
356
+
357
+ it 'does not continue the sequence' do
358
+ allow(test_instance).to receive(:another_step).and_return(arbitrary_success)
359
+
360
+ in_sequence do
361
+ and_then { and_then_function }
362
+ and_then { another_step }
363
+ and_yield { arbitrary_success }
364
+ end
365
+
366
+ expect(test_instance).not_to have_received(:another_step)
367
+ end
368
+
369
+ it 'returns the failure' do
370
+ result = in_sequence do
371
+ and_then { and_then_function }
372
+ and_yield { arbitrary_success }
373
+ end
374
+
375
+ expect(result).to eq(and_then_result)
376
+ end
377
+ end
378
+
379
+ context 'when #observe returns a failure' do
380
+ let(:observe_result) { Failure('observe') }
381
+ before { allow(test_instance).to receive(:observer).and_return(observe_result) }
382
+
383
+ it 'its return value is ignored and the sequence continues' do
384
+ allow(test_instance).to receive(:another_step).and_return(arbitrary_success)
385
+
386
+ in_sequence do
387
+ observe { observer }
388
+ and_then { another_step }
389
+ and_yield { arbitrary_success }
390
+ end
391
+
392
+ expect(test_instance).to have_received(:another_step)
393
+ end
394
+ end
395
+
396
+ it 'does not allow calling methods outside of the wrapped instance' do
397
+ expect do
398
+ in_sequence do
399
+ and_yield { top_level_test_method }
400
+ end
401
+ end.to raise_error(NameError)
402
+ end
403
+
404
+ context 'when including FunctionalLightService::Prelude' do
405
+ let(:test_class) { Class.new { include FunctionalLightService::Prelude } }
406
+
407
+ it '#in_sequence is available' do
408
+ expect do
409
+ in_sequence do
410
+ and_yield { arbitrary_success }
411
+ end
412
+ end.not_to raise_error
413
+ end
414
+ end
415
+
416
+ context 'readme example' do
417
+ let(:test_class) do
418
+ Class.new do
419
+ include FunctionalLightService::Prelude
420
+
421
+ # rubocop:disable Metrics/AbcSize
422
+ def call(input)
423
+ in_sequence do
424
+ get(:sanitized_input) { sanitize(input) }
425
+ and_then { validate(sanitized_input) }
426
+ get(:user) { get_user_from_db(sanitized_input) }
427
+ let(:name) { user.fetch(:name) }
428
+ observe { log('user name', name) }
429
+ get(:request) { build_request(sanitized_input, user) }
430
+ observe { log('sending request', request) }
431
+ get(:response) { send_request(request) }
432
+ observe { log('got response', response) }
433
+ and_yield { format_response(response) }
434
+ end
435
+ end
436
+ # rubocop:enable Metrics/AbcSize
437
+
438
+ def sanitize(input)
439
+ sanitized_input = input
440
+ Success(sanitized_input)
441
+ end
442
+
443
+ def validate(sanitized_input)
444
+ Success(sanitized_input)
445
+ end
446
+
447
+ def get_user_from_db(sanitized_input)
448
+ Success(:type => :admin, :id => sanitized_input.fetch(:id), :name => 'John')
449
+ end
450
+
451
+ def build_request(sanitized_input, user)
452
+ Success(:input => sanitized_input, :user => user)
453
+ end
454
+
455
+ def log(message, data)
456
+ # logger.info(message, data)
457
+ end
458
+
459
+ def send_request(_request)
460
+ Success(:status => 200)
461
+ end
462
+
463
+ def format_response(response)
464
+ Success(:response => response, :message => 'it worked')
465
+ end
466
+ end
467
+ end
468
+
469
+ it 'returns expected result' do
470
+ result = test_instance.call(:id => 1)
471
+
472
+ expect(result).to eq(Success(
473
+ :response => { :status => 200 },
474
+ :message => 'it worked'
475
+ ))
476
+ end
477
+
478
+ it 'logs expected values' do
479
+ allow(test_instance).to receive(:log).and_call_original
480
+
481
+ test_instance.call(:id => 1)
482
+
483
+ expect(test_instance).to have_received(:log)
484
+ .with('user name', 'John')
485
+ .ordered
486
+ expect(test_instance).to have_received(:log)
487
+ .with('sending request',
488
+ :input => { :id => 1 },
489
+ :user => { :type => :admin, :id => 1, :name => 'John' })
490
+ .ordered
491
+ expect(test_instance).to have_received(:log)
492
+ .with('got response', :status => 200)
493
+ .ordered
494
+ end
495
+ end
496
+
497
+ def in_sequence(&block)
498
+ test_instance.instance_eval do
499
+ in_sequence(&block)
500
+ end
501
+ end
502
+ end
503
+
504
+ def top_level_test_method
505
+ :empty
506
+ end
@@ -0,0 +1,156 @@
1
+ require 'spec_helper'
2
+ require 'functional-light-service/functional/maybe'
3
+
4
+ # Copertura dei path d'errore e delle API minori emersi dall'audit
5
+ describe "edge cases and error paths" do
6
+ include FunctionalLightService::Prelude::Result
7
+ include FunctionalLightService::Prelude::Option
8
+
9
+ describe FunctionalLightService::Context do
10
+ it "#add_to_context merges values" do
11
+ ctx = FunctionalLightService::Context.make(:a => 1)
12
+ ctx.add_to_context(:b => 2)
13
+
14
+ expect(ctx[:b]).to eq(2)
15
+ end
16
+ end
17
+
18
+ describe FunctionalLightService::Context::KeyVerifier do
19
+ it "the base verifier requires throw_error_predicate to be overridden" do
20
+ ctx = FunctionalLightService::Context.make
21
+ verifier = described_class.new(ctx, nil)
22
+
23
+ expect { verifier.throw_error_predicate([]) }.to raise_error(NotImplementedError)
24
+ end
25
+ end
26
+
27
+ describe "Object monkey patch (opt-in via functional/maybe)" do
28
+ it "defines null? and some? on every object" do
29
+ expect("anything".null?).to be(false)
30
+ expect("anything".some?).to be(true)
31
+ end
32
+ end
33
+
34
+ describe Null do
35
+ it "class-level unknown messages return the singleton" do
36
+ expect(Null.anything_at_all).to eq(Null.instance)
37
+ end
38
+
39
+ it "class-level respond_to? is permissive" do
40
+ expect(Null.respond_to?(:anything_at_all)).to be(true)
41
+ end
42
+
43
+ it "compares equal to another Null" do
44
+ expect(Null.instance == Null.instance).to be(true) # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands
45
+ expect(Null.instance == "not null").to be(false)
46
+ end
47
+
48
+ it "a mimic only responds to the mimicked interface" do
49
+ klass = Class.new { def foo; end }
50
+ mimic = Null.mimic(klass)
51
+
52
+ expect(mimic.respond_to?(:foo)).to be(true)
53
+ expect(mimic.respond_to?(:bar)).to be(false)
54
+ expect { mimic.bar }.to raise_error(NoMethodError)
55
+ end
56
+ end
57
+
58
+ describe "enum error paths" do
59
+ it "raises when a variant is defined twice" do
60
+ expect do
61
+ FunctionalLightService.enum do
62
+ Dup()
63
+ Dup()
64
+ end
65
+ end.to raise_error(ArgumentError, /already defined/)
66
+ end
67
+
68
+ it "the enum builder responds to any variant name" do
69
+ builder = FunctionalLightService::EnumBuilder.new(Class.new)
70
+
71
+ expect(builder.respond_to?(:AnyVariantName)).to be(true)
72
+ end
73
+
74
+ it "raises MatchError when the match is not exhaustive" do
75
+ expect do
76
+ Some(1).match do
77
+ Some() { |s| s }
78
+ end
79
+ end.to raise_error(FunctionalLightService::Enum::MatchError, /non-exhaustive/)
80
+ end
81
+
82
+ it "raises MatchError when the block arity does not match the variant" do
83
+ expect do
84
+ Some(1).match do
85
+ Some() { |a, b| [a, b] }
86
+ None() { nil }
87
+ end
88
+ end.to raise_error(FunctionalLightService::Enum::MatchError, /must match/)
89
+ end
90
+
91
+ it "raises MatchError when no guard matches" do
92
+ expect do
93
+ Some(1).match do
94
+ Some(where { s > 100 }) { |s| s }
95
+ None() { nil }
96
+ end
97
+ end.to raise_error(FunctionalLightService::Enum::MatchError, /No match could be made/)
98
+ end
99
+
100
+ it "rejects unnamed/rest block parameters" do
101
+ # su Ruby 3.1 |*| appare come [[:rest]] (ramo "Unnamed param"),
102
+ # da Ruby 3.2 come [[:rest, :*]] (ramo "Only :req & :opt")
103
+ expect do
104
+ Some(1).match do
105
+ Some() { |*| nil }
106
+ None() { nil }
107
+ end
108
+ end.to raise_error(ArgumentError, /Unnamed param|Only :req & :opt/)
109
+ end
110
+
111
+ it "rejects keyword block parameters" do
112
+ expect do
113
+ Some(1).match do
114
+ Some() { |**opts| opts }
115
+ None() { nil }
116
+ end
117
+ end.to raise_error(ArgumentError, /Only :req & :opt params allowed/)
118
+ end
119
+
120
+ it "None deconstructs to an empty hash and array" do
121
+ none = FunctionalLightService::Option::None.new
122
+
123
+ expect(none.deconstruct).to eq([])
124
+ expect(none.deconstruct_keys(nil)).to eq({})
125
+ end
126
+ end
127
+
128
+ describe "Option minor APIs" do
129
+ it "#value_to_a returns the raw value" do
130
+ expect(Some(1).value_to_a).to eq(1)
131
+ expect(FunctionalLightService::Option::None.new.value_to_a).to be_nil
132
+ end
133
+
134
+ it "Prelude None() builds the shared None" do
135
+ expect(None().none?).to be(true)
136
+ end
137
+
138
+ it "Prelude Option() returns the Option enum" do
139
+ expect(Option()).to eq(FunctionalLightService::Option)
140
+ end
141
+ end
142
+
143
+ describe "Result minor APIs" do
144
+ it "#+ raises NotMonadError for non-Result operands" do
145
+ expect { Success(1) + "not a result" } # rubocop:disable Style/StringConcatenation
146
+ .to raise_error(FunctionalLightService::Monad::NotMonadError)
147
+ end
148
+
149
+ it "Prelude try! wraps exceptions in Failure" do
150
+ result = try! { raise "boom" }
151
+
152
+ expect(result).to be_failure
153
+ expect(result.value).to be_a(RuntimeError)
154
+ end
155
+ end
156
+ end
@@ -20,7 +20,7 @@ describe FunctionalLightService::Enum do
20
20
 
21
21
  it "can't instantiate parent" do
22
22
  expect { MyEnym.new }.to \
23
- raise_error NoMethodError, "private method `new' called for MyEnym:Class"
23
+ raise_error NoMethodError, /private method [`']new'/
24
24
  end
25
25
 
26
26
  it "Nullary" do