functional-light-service 0.2.4
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 +7 -0
- data/.gitignore +22 -0
- data/.rspec +3 -0
- data/.rubocop.yml +72 -0
- data/.travis.yml +22 -0
- data/Appraisals +3 -0
- data/CHANGELOG.md +37 -0
- data/CODE_OF_CONDUCT.md +22 -0
- data/Gemfile +6 -0
- data/LICENSE +22 -0
- data/README.md +1424 -0
- data/Rakefile +12 -0
- data/VERSION +1 -0
- data/functional-light-service.gemspec +26 -0
- data/gemfiles/activesupport_5.gemfile +8 -0
- data/gemfiles/activesupport_5.gemfile.lock +82 -0
- data/lib/functional-light-service/action.rb +102 -0
- data/lib/functional-light-service/configuration.rb +24 -0
- data/lib/functional-light-service/context/key_verifier.rb +118 -0
- data/lib/functional-light-service/context.rb +165 -0
- data/lib/functional-light-service/errors.rb +6 -0
- data/lib/functional-light-service/functional/enum.rb +254 -0
- data/lib/functional-light-service/functional/maybe.rb +14 -0
- data/lib/functional-light-service/functional/monad.rb +66 -0
- data/lib/functional-light-service/functional/null.rb +74 -0
- data/lib/functional-light-service/functional/option.rb +99 -0
- data/lib/functional-light-service/functional/result.rb +122 -0
- data/lib/functional-light-service/localization_adapter.rb +44 -0
- data/lib/functional-light-service/organizer/execute.rb +14 -0
- data/lib/functional-light-service/organizer/iterate.rb +22 -0
- data/lib/functional-light-service/organizer/reduce_if.rb +17 -0
- data/lib/functional-light-service/organizer/reduce_until.rb +20 -0
- data/lib/functional-light-service/organizer/scoped_reducable.rb +13 -0
- data/lib/functional-light-service/organizer/verify_call_method_exists.rb +28 -0
- data/lib/functional-light-service/organizer/with_callback.rb +26 -0
- data/lib/functional-light-service/organizer/with_reducer.rb +71 -0
- data/lib/functional-light-service/organizer/with_reducer_factory.rb +18 -0
- data/lib/functional-light-service/organizer/with_reducer_log_decorator.rb +105 -0
- data/lib/functional-light-service/organizer.rb +105 -0
- data/lib/functional-light-service/testing/context_factory.rb +40 -0
- data/lib/functional-light-service/testing.rb +1 -0
- data/lib/functional-light-service/version.rb +3 -0
- data/lib/functional-light-service.rb +29 -0
- data/resources/fail_actions.png +0 -0
- data/resources/light-service.png +0 -0
- data/resources/organizer_and_actions.png +0 -0
- data/resources/skip_actions.png +0 -0
- data/spec/acceptance/add_numbers_spec.rb +11 -0
- data/spec/acceptance/after_actions_spec.rb +71 -0
- data/spec/acceptance/around_each_spec.rb +19 -0
- data/spec/acceptance/before_actions_spec.rb +98 -0
- data/spec/acceptance/custom_log_from_organizer_spec.rb +60 -0
- data/spec/acceptance/fail_spec.rb +24 -0
- data/spec/acceptance/include_warning_spec.rb +29 -0
- data/spec/acceptance/log_from_organizer_spec.rb +154 -0
- data/spec/acceptance/message_localization_spec.rb +118 -0
- data/spec/acceptance/not_having_call_method_warning_spec.rb +39 -0
- data/spec/acceptance/organizer/around_each_with_reduce_if_spec.rb +42 -0
- data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +65 -0
- data/spec/acceptance/organizer/execute_spec.rb +46 -0
- data/spec/acceptance/organizer/iterate_spec.rb +37 -0
- data/spec/acceptance/organizer/reduce_if_spec.rb +51 -0
- data/spec/acceptance/organizer/reduce_until_spec.rb +43 -0
- data/spec/acceptance/organizer/with_callback_spec.rb +110 -0
- data/spec/acceptance/rollback_spec.rb +132 -0
- data/spec/acceptance/skip_all_warning_spec.rb +20 -0
- data/spec/acceptance/testing/context_factory_spec.rb +54 -0
- data/spec/action_expected_keys_spec.rb +63 -0
- data/spec/action_expects_and_promises_spec.rb +93 -0
- data/spec/action_promised_keys_spec.rb +122 -0
- data/spec/action_spec.rb +89 -0
- data/spec/context/inspect_spec.rb +57 -0
- data/spec/context_spec.rb +197 -0
- data/spec/examples/amount_spec.rb +77 -0
- data/spec/examples/controller_spec.rb +63 -0
- data/spec/examples/validate_address_spec.rb +37 -0
- data/spec/lib/deterministic/class_mixin_spec.rb +24 -0
- data/spec/lib/deterministic/currify_spec.rb +88 -0
- data/spec/lib/deterministic/monad_axioms.rb +44 -0
- data/spec/lib/deterministic/monad_spec.rb +45 -0
- data/spec/lib/deterministic/null_spec.rb +58 -0
- data/spec/lib/deterministic/option_spec.rb +133 -0
- data/spec/lib/deterministic/result/failure_spec.rb +65 -0
- data/spec/lib/deterministic/result/result_map_spec.rb +154 -0
- data/spec/lib/deterministic/result/result_shared.rb +24 -0
- data/spec/lib/deterministic/result/success_spec.rb +41 -0
- data/spec/lib/deterministic/result_spec.rb +63 -0
- data/spec/lib/enum_spec.rb +112 -0
- data/spec/localization_adapter_spec.rb +83 -0
- data/spec/organizer/with_reducer_spec.rb +56 -0
- data/spec/organizer_key_aliases_spec.rb +29 -0
- data/spec/organizer_spec.rb +93 -0
- data/spec/readme_spec.rb +47 -0
- data/spec/sample/calculates_order_tax_action_spec.rb +16 -0
- data/spec/sample/calculates_tax_spec.rb +30 -0
- data/spec/sample/looks_up_tax_percentage_action_spec.rb +53 -0
- data/spec/sample/provides_free_shipping_action_spec.rb +25 -0
- data/spec/sample/tax/calculates_order_tax_action.rb +9 -0
- data/spec/sample/tax/calculates_tax.rb +11 -0
- data/spec/sample/tax/looks_up_tax_percentage_action.rb +27 -0
- data/spec/sample/tax/provides_free_shipping_action.rb +10 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/support.rb +1 -0
- data/spec/test_doubles.rb +552 -0
- data/spec/testing/context_factory/iterate_spec.rb +39 -0
- data/spec/testing/context_factory/reduce_if_spec.rb +40 -0
- data/spec/testing/context_factory/reduce_until_spec.rb +40 -0
- data/spec/testing/context_factory/with_callback_spec.rb +38 -0
- data/spec/testing/context_factory_spec.rb +55 -0
- metadata +285 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'test_doubles'
|
|
3
|
+
|
|
4
|
+
describe ":promises macro" do
|
|
5
|
+
context "when the promised key is not in the context" do
|
|
6
|
+
it "raises an ArgumentError" do
|
|
7
|
+
module TestDoubles
|
|
8
|
+
class MakesCappuccinoAction1
|
|
9
|
+
extend FunctionalLightService::Action
|
|
10
|
+
expects :coffee, :milk
|
|
11
|
+
promises :cappuccino
|
|
12
|
+
executed do |context|
|
|
13
|
+
context[:macchiato] = "#{context.coffee} - #{context.milk}"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
exception_msg = "promised :cappuccino to be in the context during " \
|
|
19
|
+
"TestDoubles::MakesCappuccinoAction1"
|
|
20
|
+
expect do
|
|
21
|
+
TestDoubles::MakesCappuccinoAction1.execute(:coffee => "espresso",
|
|
22
|
+
:milk => "2%")
|
|
23
|
+
end.to \
|
|
24
|
+
raise_error(FunctionalLightService::PromisedKeysNotInContextError, exception_msg)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "can fail the context without fulfilling its promise" do
|
|
28
|
+
module TestDoubles
|
|
29
|
+
class MakesCappuccinoAction2
|
|
30
|
+
extend FunctionalLightService::Action
|
|
31
|
+
expects :coffee, :milk
|
|
32
|
+
promises :cappuccino
|
|
33
|
+
executed do |context|
|
|
34
|
+
context.fail!("Sorry, something bad has happened.")
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
result_context = TestDoubles::MakesCappuccinoAction2
|
|
40
|
+
.execute(:coffee => "espresso",
|
|
41
|
+
:milk => "2%")
|
|
42
|
+
|
|
43
|
+
expect(result_context).to be_failure
|
|
44
|
+
expect(result_context.keys).not_to include(:cappuccino)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
context "when the promised key is in the context" do
|
|
49
|
+
it "can be set with an actual value" do
|
|
50
|
+
module TestDoubles
|
|
51
|
+
class MakesCappuccinoAction3
|
|
52
|
+
extend FunctionalLightService::Action
|
|
53
|
+
expects :coffee, :milk
|
|
54
|
+
promises :cappuccino
|
|
55
|
+
executed do |context|
|
|
56
|
+
context.cappuccino = "#{context.coffee} - with #{context.milk} milk"
|
|
57
|
+
context.cappuccino += " hot"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
result_context = TestDoubles::MakesCappuccinoAction3
|
|
63
|
+
.execute(:coffee => "espresso",
|
|
64
|
+
:milk => "2%")
|
|
65
|
+
|
|
66
|
+
expect(result_context).to be_success
|
|
67
|
+
expect(result_context.cappuccino).to eq("espresso - with 2% milk hot")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "can be set with nil" do
|
|
71
|
+
module TestDoubles
|
|
72
|
+
class MakesCappuccinoAction4
|
|
73
|
+
extend FunctionalLightService::Action
|
|
74
|
+
expects :coffee, :milk
|
|
75
|
+
promises :cappuccino
|
|
76
|
+
executed do |context|
|
|
77
|
+
context.cappuccino = nil
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
result_context = TestDoubles::MakesCappuccinoAction4
|
|
82
|
+
.execute(:coffee => "espresso",
|
|
83
|
+
:milk => "2%")
|
|
84
|
+
|
|
85
|
+
expect(result_context).to be_success
|
|
86
|
+
expect(result_context[:cappuccino]).to be_nil
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
context "when a reserved key is listed as a promised key" do
|
|
91
|
+
it "raises error indicating a reserved key has been promised" do
|
|
92
|
+
exception_msg = "promised or expected keys cannot be a reserved key: "\
|
|
93
|
+
"[:message]"
|
|
94
|
+
expect do
|
|
95
|
+
TestDoubles::MakesTeaPromisingReservedKey.execute(:tea => "black")
|
|
96
|
+
end.to \
|
|
97
|
+
raise_error(FunctionalLightService::ReservedKeysInContextError, exception_msg)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it "raises error indicating multiple reserved keys have been promised" do
|
|
101
|
+
exception_msg = "promised or expected keys cannot be a reserved key: " \
|
|
102
|
+
"[:message, :error_code, :current_action]"
|
|
103
|
+
expect do
|
|
104
|
+
ctx = { :tea => "black" }
|
|
105
|
+
TestDoubles::MakesTeaPromisingMultipleReservedKeys.execute(ctx)
|
|
106
|
+
end.to \
|
|
107
|
+
raise_error(FunctionalLightService::ReservedKeysInContextError, exception_msg)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
context "when the `promised` macro is called multiple times" do
|
|
112
|
+
it "collects promised keys " do
|
|
113
|
+
result = TestDoubles::MultiplePromisesAction \
|
|
114
|
+
.execute(:coffee => "espresso", :milk => "2%")
|
|
115
|
+
|
|
116
|
+
expect(result.cappuccino).to \
|
|
117
|
+
eq("Cappucino needs espresso and a little milk")
|
|
118
|
+
expect(result.latte).to \
|
|
119
|
+
eq("Latte needs espresso and a lot of milk")
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
data/spec/action_spec.rb
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'test_doubles'
|
|
3
|
+
|
|
4
|
+
describe FunctionalLightService::Action do
|
|
5
|
+
let(:context) { FunctionalLightService::Context.make }
|
|
6
|
+
|
|
7
|
+
context "when the action context has failure" do
|
|
8
|
+
it "returns immediately" do
|
|
9
|
+
context.fail!("an error")
|
|
10
|
+
|
|
11
|
+
TestDoubles::AddsTwoActionWithFetch.execute(context)
|
|
12
|
+
|
|
13
|
+
expect(context.to_hash.keys).to be_empty
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "returns the failure message in the context" do
|
|
17
|
+
context.fail!("an error")
|
|
18
|
+
|
|
19
|
+
returned_context = TestDoubles::AddsTwoActionWithFetch.execute(context)
|
|
20
|
+
|
|
21
|
+
expect(returned_context.message).to eq("an error")
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
context "when the action has an explicit success message" do
|
|
26
|
+
it "returns the success message in the context" do
|
|
27
|
+
context.succeed!("successful")
|
|
28
|
+
|
|
29
|
+
returned_context = TestDoubles::AddsTwoActionWithFetch.execute(context)
|
|
30
|
+
|
|
31
|
+
expect(returned_context.message).to eq("successful")
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
context "when the action context does not have failure" do
|
|
36
|
+
it "executes the block" do
|
|
37
|
+
TestDoubles::AddsTwoActionWithFetch.execute(context)
|
|
38
|
+
|
|
39
|
+
expect(context.to_hash.keys).to eq [:number]
|
|
40
|
+
expect(context.fetch(:number)).to eq(2)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
context "when the action context skips all" do
|
|
45
|
+
it "returns immediately" do
|
|
46
|
+
context.skip_remaining!
|
|
47
|
+
|
|
48
|
+
TestDoubles::AddsTwoActionWithFetch.execute(context)
|
|
49
|
+
|
|
50
|
+
expect(context.to_hash.keys).to be_empty
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "does not execute skipped actions" do
|
|
54
|
+
TestDoubles::AddsTwoActionWithFetch.execute(context)
|
|
55
|
+
expect(context.to_hash).to eq(:number => 2)
|
|
56
|
+
|
|
57
|
+
context.skip_remaining!
|
|
58
|
+
|
|
59
|
+
TestDoubles::AddsTwoActionWithFetch.execute(context)
|
|
60
|
+
# Since the action was skipped, the number remains 2
|
|
61
|
+
expect(context.to_hash).to eq(:number => 2)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "returns the context" do
|
|
66
|
+
result = TestDoubles::AddsTwoActionWithFetch.execute(context)
|
|
67
|
+
|
|
68
|
+
expect(result.to_hash).to eq(:number => 2)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
context "when invoked with hash" do
|
|
72
|
+
it "creates FunctionalLightService::Context implicitly" do
|
|
73
|
+
ctx = { :some_key => "some value" }
|
|
74
|
+
result = TestDoubles::AddsTwoActionWithFetch.execute(ctx)
|
|
75
|
+
|
|
76
|
+
expect(result).to be_success
|
|
77
|
+
expect(result.keys).to eq(%i[some_key number])
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
context "when invoked without arguments" do
|
|
82
|
+
it "creates FunctionalLightService::Context implicitly" do
|
|
83
|
+
result = TestDoubles::AddsTwoActionWithFetch.execute
|
|
84
|
+
|
|
85
|
+
expect(result).to be_success
|
|
86
|
+
expect(result.keys).to eq([:number])
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'test_doubles'
|
|
3
|
+
|
|
4
|
+
RSpec.describe FunctionalLightService::Context do
|
|
5
|
+
subject(:context) { FunctionalLightService::Context.make }
|
|
6
|
+
|
|
7
|
+
describe 'to_s' do
|
|
8
|
+
it 'prints the context hash' do
|
|
9
|
+
expect(context.to_s).to eq('{}')
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
describe '#inspect' do
|
|
14
|
+
it 'inspects the hash with all the fields' do
|
|
15
|
+
inspected_context =
|
|
16
|
+
'FunctionalLightService::Context({}, ' \
|
|
17
|
+
+ 'success: true, ' \
|
|
18
|
+
+ 'message: \'\', ' \
|
|
19
|
+
+ 'error_code: nil, ' \
|
|
20
|
+
+ 'skip_remaining: false, ' \
|
|
21
|
+
+ 'aliases: {}' \
|
|
22
|
+
+ ')'
|
|
23
|
+
|
|
24
|
+
expect(context.inspect).to eq(inspected_context)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'prints the error message' do
|
|
28
|
+
context.fail!('There was an error')
|
|
29
|
+
|
|
30
|
+
inspected_context =
|
|
31
|
+
'FunctionalLightService::Context({}, ' \
|
|
32
|
+
+ 'success: false, ' \
|
|
33
|
+
+ 'message: \'There was an error\', ' \
|
|
34
|
+
+ 'error_code: nil, ' \
|
|
35
|
+
+ 'skip_remaining: false, ' \
|
|
36
|
+
+ 'aliases: {}' \
|
|
37
|
+
+ ')'
|
|
38
|
+
|
|
39
|
+
expect(context.inspect).to eq(inspected_context)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'prints skip_remaining' do
|
|
43
|
+
context.skip_remaining!('No need to process')
|
|
44
|
+
|
|
45
|
+
inspected_context =
|
|
46
|
+
'FunctionalLightService::Context({}, ' \
|
|
47
|
+
+ 'success: true, ' \
|
|
48
|
+
+ 'message: \'No need to process\', ' \
|
|
49
|
+
+ 'error_code: nil, ' \
|
|
50
|
+
+ 'skip_remaining: true, ' \
|
|
51
|
+
+ 'aliases: {}' \
|
|
52
|
+
+ ')'
|
|
53
|
+
|
|
54
|
+
expect(context.inspect).to eq(inspected_context)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'test_doubles'
|
|
3
|
+
|
|
4
|
+
RSpec.describe FunctionalLightService::Context do
|
|
5
|
+
let(:context) { FunctionalLightService::Context.make }
|
|
6
|
+
|
|
7
|
+
describe "can be made" do
|
|
8
|
+
context "with no arguments" do
|
|
9
|
+
subject { FunctionalLightService::Context.make }
|
|
10
|
+
it { is_expected.to be_success }
|
|
11
|
+
specify "message is empty string" do
|
|
12
|
+
expect(context.message).to be_empty
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
context "with a hash" do
|
|
17
|
+
it "has the hash values" do
|
|
18
|
+
context = FunctionalLightService::Context.make(:one => 1)
|
|
19
|
+
|
|
20
|
+
expect(context[:one]).to eq(1)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
context "with FAILURE" do
|
|
25
|
+
it "is failed" do
|
|
26
|
+
outcome = FunctionalLightService::Result::Failure(:message => '', :error => nil)
|
|
27
|
+
context = FunctionalLightService::Context.new({}, outcome)
|
|
28
|
+
|
|
29
|
+
expect(context).to be_failure
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe "can't be made" do
|
|
35
|
+
specify "with invalid parameters" do
|
|
36
|
+
expect { FunctionalLightService::Context.make([]) }.to raise_error(ArgumentError)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "can be asked for success?" do
|
|
41
|
+
outcome = FunctionalLightService::Result::Success(:message => '', :error => nil)
|
|
42
|
+
context = FunctionalLightService::Context.new({}, outcome)
|
|
43
|
+
|
|
44
|
+
expect(context).to be_success
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "can be asked for failure?" do
|
|
48
|
+
outcome = FunctionalLightService::Result::Failure(:message => '', :error => nil)
|
|
49
|
+
context = FunctionalLightService::Context.new({}, outcome)
|
|
50
|
+
|
|
51
|
+
expect(context).to be_failure
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it "can be asked for skip_remaining?" do
|
|
55
|
+
context.skip_remaining!
|
|
56
|
+
|
|
57
|
+
expect(context.skip_remaining?).to be_truthy
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "can be pushed into a SUCCESS state" do
|
|
61
|
+
context.succeed!("a happy end")
|
|
62
|
+
|
|
63
|
+
expect(context).to be_success
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "can be pushed into a SUCCESS state without a message" do
|
|
67
|
+
context.succeed!
|
|
68
|
+
|
|
69
|
+
expect(context).to be_success
|
|
70
|
+
expect(context.message).to be_nil
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it "can be pushed into a FAILURE state without a message" do
|
|
74
|
+
context.fail!
|
|
75
|
+
|
|
76
|
+
expect(context).to be_failure
|
|
77
|
+
expect(context.message).to be_nil
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it "can be pushed into a FAILURE state with a message" do
|
|
81
|
+
context.fail!("a sad end")
|
|
82
|
+
|
|
83
|
+
expect(context).to be_failure
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it "can be pushed into a FAILURE state with a message in an options hash" do
|
|
87
|
+
context.fail!("a sad end")
|
|
88
|
+
|
|
89
|
+
expect(context).to be_failure
|
|
90
|
+
expect(context.message).to eq("a sad end")
|
|
91
|
+
expect(context.error_code).to be_nil
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it "can be pushed into a FAILURE state with an error code in options hash" do
|
|
95
|
+
context.fail!("a sad end", 10_005)
|
|
96
|
+
|
|
97
|
+
expect(context).to be_failure
|
|
98
|
+
expect(context.message).to eq("a sad end")
|
|
99
|
+
expect(context.error_code).to eq(10_005)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it "uses localization adapter to translate failure message" do
|
|
103
|
+
action_class = TestDoubles::AnAction
|
|
104
|
+
expect(FunctionalLightService::Configuration.localization_adapter)
|
|
105
|
+
.to receive(:failure)
|
|
106
|
+
.with(:failure_reason, action_class, {})
|
|
107
|
+
.and_return("message")
|
|
108
|
+
|
|
109
|
+
context = FunctionalLightService::Context.make
|
|
110
|
+
context.current_action = action_class
|
|
111
|
+
context.fail!(:failure_reason)
|
|
112
|
+
|
|
113
|
+
expect(context).to be_failure
|
|
114
|
+
expect(context.message).to eq("message")
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it "uses localization adapter to translate success message" do
|
|
118
|
+
action_class = TestDoubles::AnAction
|
|
119
|
+
expect(FunctionalLightService::Configuration.localization_adapter)
|
|
120
|
+
.to receive(:success)
|
|
121
|
+
.with(:action_passed, action_class, {})
|
|
122
|
+
.and_return("message")
|
|
123
|
+
|
|
124
|
+
context = FunctionalLightService::Context.make
|
|
125
|
+
context.current_action = action_class
|
|
126
|
+
context.succeed!(:action_passed)
|
|
127
|
+
|
|
128
|
+
expect(context).to be_success
|
|
129
|
+
expect(context.message).to eq("message")
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it "can set a flag to skip all subsequent actions" do
|
|
133
|
+
context.skip_remaining!
|
|
134
|
+
|
|
135
|
+
expect(context).to be_skip_remaining
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
context "stopping additional processing in an action" do
|
|
139
|
+
it "flags processing to stop on failure" do
|
|
140
|
+
context.fail!("on purpose")
|
|
141
|
+
expect(context.stop_processing?).to be_truthy
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
it "flags processing to stop when remaining actions should be skipped" do
|
|
145
|
+
context.skip_remaining!
|
|
146
|
+
expect(context.stop_processing?).to be_truthy
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
it "can fail with FailWithRollBackError" do
|
|
151
|
+
expect { context.fail_with_rollback!("roll me back") }.to \
|
|
152
|
+
raise_error(FunctionalLightService::FailWithRollbackError)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
it "exptected outcome reader get Success and message empty and error nil" do
|
|
156
|
+
outcome = FunctionalLightService::Result::Success(:message => "", :error => nil)
|
|
157
|
+
expect(context.outcome).to eq(outcome)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it "can contain false values" do
|
|
161
|
+
context = FunctionalLightService::Context.make(:foo => false)
|
|
162
|
+
expect(context[:foo]).to eq false
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
it "allows a default value for #fetch" do
|
|
166
|
+
expect(context.fetch(:madeup, :default)).to eq(:default)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
it "allows a default block value for #fetch" do
|
|
170
|
+
expect(context.fetch(:madeup) { :default }).to eq(:default)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
context "when aliases are included via .make" do
|
|
174
|
+
let(:context) do
|
|
175
|
+
FunctionalLightService::Context.make(
|
|
176
|
+
:foo => "foobar",
|
|
177
|
+
:foo2 => false,
|
|
178
|
+
:_aliases => aliases
|
|
179
|
+
)
|
|
180
|
+
end
|
|
181
|
+
let(:aliases) { { :foo => :bar, :foo2 => :bar2 } }
|
|
182
|
+
|
|
183
|
+
it "contains the aliases" do
|
|
184
|
+
expect(context.aliases).to eq(aliases)
|
|
185
|
+
expect(context).to include(:foo, :bar)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
it "returns the correct values for #[] and #fetch" do
|
|
189
|
+
expect(context[:bar]).to eq context[:foo]
|
|
190
|
+
expect(context.fetch(:bar)).to eq context[:foo]
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
it "can contain false values" do
|
|
194
|
+
expect(context[:bar2]).to eq false
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'functional-light-service/functional/enum'
|
|
3
|
+
|
|
4
|
+
Amount = FunctionalLightService.enum do
|
|
5
|
+
Due(:amount)
|
|
6
|
+
Paid(:amount)
|
|
7
|
+
Info(:amount)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class Amount
|
|
11
|
+
def self.from_f(f)
|
|
12
|
+
f >= 0 ? Amount::Due.new(f) : Amount::Paid.new(-1 * f)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
FunctionalLightService.impl(Amount) do
|
|
17
|
+
def to_s
|
|
18
|
+
match do
|
|
19
|
+
Due() { |a| format("%0.2f", a) }
|
|
20
|
+
Paid() { |a| format("-%0.2f", a) }
|
|
21
|
+
Info() { |a| format("(%0.2f)", a) }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def to_f
|
|
26
|
+
match do
|
|
27
|
+
Info() { |_a| 0 }
|
|
28
|
+
Due() { |a| a }
|
|
29
|
+
Paid() { |a| -1 * a }
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def +(other)
|
|
34
|
+
raise TypeError "Expected other to be an Amount, got #{other.class}" unless other.is_a? Amount
|
|
35
|
+
|
|
36
|
+
Amount.from_f(to_f + other.to_f)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
describe Amount do
|
|
41
|
+
# rubocop:disable Naming/MethodName
|
|
42
|
+
def Due(a)
|
|
43
|
+
Amount::Due.new(a)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def Paid(a)
|
|
47
|
+
Amount::Paid.new(a)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def Info(a)
|
|
51
|
+
Amount::Info.new(a)
|
|
52
|
+
end
|
|
53
|
+
# rubocop:enable Naming/MethodName
|
|
54
|
+
|
|
55
|
+
it "due" do
|
|
56
|
+
amount = Amount::Due.new(100.2)
|
|
57
|
+
expect(amount.to_s).to eq "100.20"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "paid" do
|
|
61
|
+
amount = Amount::Paid.new(100.1)
|
|
62
|
+
expect(amount.to_s).to eq "-100.10"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "paid" do
|
|
66
|
+
amount = Amount::Info.new(100.31)
|
|
67
|
+
expect(amount.to_s).to eq "(100.31)"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "+" do
|
|
71
|
+
expect(Due(10) + Paid(20)).to eq Paid(10)
|
|
72
|
+
expect(Due(10) + Paid(10)).to eq Due(0)
|
|
73
|
+
expect(Due(10) + Due(10)).to eq Due(20)
|
|
74
|
+
expect(Paid(10) + Paid(10)).to eq Paid(20)
|
|
75
|
+
expect(Paid(10) + Due(1) + Info(99)).to eq Paid(9)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module FunctionalLightService
|
|
4
|
+
module Procify
|
|
5
|
+
def py(m, *args)
|
|
6
|
+
args.count > 0 ? method(m).to_proc.curry[*args] : method(m)
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class BookingController
|
|
12
|
+
include FunctionalLightService::Prelude::Result
|
|
13
|
+
include FunctionalLightService::Procify
|
|
14
|
+
|
|
15
|
+
Context = Struct.new(:booking, :ability, :format)
|
|
16
|
+
|
|
17
|
+
def index(id, format = :html)
|
|
18
|
+
get_booking(id) << log(:booking) >>
|
|
19
|
+
py(:ability) << log(:ability) >>
|
|
20
|
+
py(:present, format) << log(:presenter) >>
|
|
21
|
+
py(:render) << log(:render)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def log(step)
|
|
25
|
+
->(data) { [step, data] }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def ability(ctx)
|
|
29
|
+
ctx.ability = {} # Ability.new(@booking)
|
|
30
|
+
Success(ctx)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def present(format, ctx)
|
|
34
|
+
ctx.format = format
|
|
35
|
+
|
|
36
|
+
Success(ctx)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def render(ctx)
|
|
40
|
+
send(ctx.format, ctx)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def html(ctx)
|
|
44
|
+
Success(ctx)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def get_booking(id)
|
|
48
|
+
ctx = Context.new
|
|
49
|
+
ctx.booking = { :ref_anixe => id }
|
|
50
|
+
Success(ctx)
|
|
51
|
+
# @booking = @bms.booking_by_id(id)
|
|
52
|
+
# rescue BSON::InvalidObjectId => ex
|
|
53
|
+
# @booking = nil
|
|
54
|
+
# @ui.error(404, ex.message)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
describe BookingController do
|
|
59
|
+
it "does something" do
|
|
60
|
+
bc = BookingController.new
|
|
61
|
+
bc.index('1234', :html)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
# A Unit of Work for validating an address
|
|
4
|
+
module ValidateAddress
|
|
5
|
+
extend FunctionalLightService::Prelude::Result
|
|
6
|
+
|
|
7
|
+
def self.call(candidate)
|
|
8
|
+
errors = {}
|
|
9
|
+
errors[:street] = "Street cannot be empty" unless candidate.key? :street
|
|
10
|
+
errors[:city] = "Street cannot be empty" unless candidate.key? :city
|
|
11
|
+
errors[:postal] = "Street cannot be empty" unless candidate.key? :postal
|
|
12
|
+
|
|
13
|
+
errors.empty? ? Success(candidate) : Failure(errors)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe ValidateAddress do
|
|
18
|
+
include FunctionalLightService
|
|
19
|
+
subject { ValidateAddress.call(candidate) }
|
|
20
|
+
context 'sunny day' do
|
|
21
|
+
let(:candidate) do
|
|
22
|
+
{ :title => "Hobbiton",
|
|
23
|
+
:street => "501 Buckland Rd",
|
|
24
|
+
:city => "Matamata",
|
|
25
|
+
:postal => "3472",
|
|
26
|
+
:country => "nz" }
|
|
27
|
+
end
|
|
28
|
+
specify { expect(subject).to be_a FunctionalLightService::Result::Success }
|
|
29
|
+
specify { expect(subject.value).to eq candidate }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
context 'empty data' do
|
|
33
|
+
let(:candidate) { {} }
|
|
34
|
+
specify { expect(subject).to be_a FunctionalLightService::Result::Failure }
|
|
35
|
+
specify { expect(subject.value).to include(:street, :city, :postal) }
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe 'Class Mixin' do
|
|
4
|
+
describe 'try' do
|
|
5
|
+
module MyApp
|
|
6
|
+
class Thing
|
|
7
|
+
include FunctionalLightService::Prelude::Result
|
|
8
|
+
|
|
9
|
+
def run
|
|
10
|
+
Success(11) >> method(:double)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def double(num)
|
|
14
|
+
Success(num * 2)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "cleanly mixes into a class" do
|
|
20
|
+
result = MyApp::Thing.new.run
|
|
21
|
+
expect(result).to eq FunctionalLightService::Result::Success.new(22)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|