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.
- checksums.yaml +4 -4
- data/.github/workflows/project-build.yml +35 -11
- data/.rubocop.yml +101 -160
- data/AUDIT-functional-light-service.md +352 -0
- data/CHANGELOG.md +45 -0
- data/README.md +88 -2
- data/audit/bench.rb +99 -0
- data/audit/verify_findings.rb +172 -0
- data/functional-light-service.gemspec +15 -21
- data/lib/functional-light-service/action.rb +97 -101
- data/lib/functional-light-service/configuration.rb +26 -24
- data/lib/functional-light-service/context/key_verifier.rb +124 -118
- data/lib/functional-light-service/context.rb +63 -20
- data/lib/functional-light-service/deprecations.rb +26 -0
- data/lib/functional-light-service/errors.rb +8 -6
- data/lib/functional-light-service/functional/enum.rb +286 -250
- data/lib/functional-light-service/functional/maybe.rb +21 -15
- data/lib/functional-light-service/functional/monad.rb +77 -66
- data/lib/functional-light-service/functional/null.rb +88 -74
- data/lib/functional-light-service/functional/option.rb +100 -97
- data/lib/functional-light-service/functional/result.rb +129 -116
- data/lib/functional-light-service/functional/sequencer.rb +144 -0
- data/lib/functional-light-service/localization_adapter.rb +48 -47
- data/lib/functional-light-service/organizer/execute.rb +16 -14
- data/lib/functional-light-service/organizer/iterate.rb +30 -25
- data/lib/functional-light-service/organizer/reduce_if.rb +19 -17
- data/lib/functional-light-service/organizer/reduce_until.rb +22 -20
- data/lib/functional-light-service/organizer/scoped_reducable.rb +15 -13
- data/lib/functional-light-service/organizer/with_callback.rb +28 -26
- data/lib/functional-light-service/organizer/with_reducer.rb +81 -77
- data/lib/functional-light-service/organizer/with_reducer_factory.rb +20 -18
- data/lib/functional-light-service/organizer/with_reducer_log_decorator.rb +110 -108
- data/lib/functional-light-service/organizer.rb +114 -114
- data/lib/functional-light-service/testing/context_factory.rb +48 -42
- data/lib/functional-light-service/testing.rb +3 -1
- data/lib/functional-light-service/version.rb +5 -3
- data/lib/functional-light-service.rb +31 -28
- data/spec/acceptance/after_actions_spec.rb +87 -71
- data/spec/acceptance/before_actions_spec.rb +115 -98
- data/spec/acceptance/custom_log_from_organizer_spec.rb +61 -60
- data/spec/acceptance/deprecation_warnings_spec.rb +82 -0
- data/spec/acceptance/fail_spec.rb +52 -50
- data/spec/acceptance/message_localization_spec.rb +119 -118
- data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +68 -65
- data/spec/acceptance/organizer/reduce_if_spec.rb +89 -89
- data/spec/acceptance/organizer/with_callback_spec.rb +113 -110
- data/spec/acceptance/{not_having_call_method_warning_spec.rb → organizer_entry_point_spec.rb} +10 -7
- data/spec/acceptance/rollback_spec.rb +183 -132
- data/spec/action_expects_and_promises_spec.rb +97 -93
- data/spec/action_promised_keys_spec.rb +126 -122
- data/spec/context_spec.rb +289 -197
- data/spec/examples/controller_spec.rb +63 -63
- data/spec/examples/validate_address_spec.rb +38 -37
- data/spec/lib/deterministic/currify_spec.rb +90 -88
- data/spec/lib/deterministic/null_spec.rb +6 -1
- data/spec/lib/deterministic/option_spec.rb +140 -137
- data/spec/lib/deterministic/result/result_map_spec.rb +155 -154
- data/spec/lib/deterministic/result/result_shared.rb +3 -2
- data/spec/lib/deterministic/result_spec.rb +2 -2
- data/spec/lib/deterministic/sequencer_spec.rb +506 -0
- data/spec/lib/edge_cases_spec.rb +156 -0
- data/spec/lib/enum_spec.rb +1 -1
- data/spec/lib/native_pattern_matching_spec.rb +74 -0
- data/spec/organizer_spec.rb +115 -114
- data/spec/readme_spec.rb +45 -47
- data/spec/sample/calculates_order_tax_action_spec.rb +16 -16
- data/spec/sample/calculates_tax_spec.rb +1 -1
- data/spec/sample/looks_up_tax_percentage_action_spec.rb +55 -55
- data/spec/sample/tax/calculates_order_tax_action.rb +10 -9
- data/spec/sample/tax/looks_up_tax_percentage_action.rb +28 -27
- data/spec/sample/tax/provides_free_shipping_action.rb +11 -10
- data/spec/spec_helper.rb +6 -0
- data/spec/test_doubles.rb +628 -599
- data/spec/testing/context_factory_spec.rb +21 -0
- metadata +48 -162
- data/lib/functional-light-service/organizer/verify_call_method_exists.rb +0 -29
- 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
|
data/spec/lib/enum_spec.rb
CHANGED
|
@@ -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,
|
|
23
|
+
raise_error NoMethodError, /private method [`']new'/
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
it "Nullary" do
|