functional-light-service 6.0.0 → 6.2.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/.gitignore +2 -0
- data/CHANGELOG.md +26 -0
- data/README.md +130 -11
- data/functional-light-service.gemspec +3 -1
- data/lib/functional-light-service/action.rb +48 -0
- data/lib/functional-light-service/configuration.rb +12 -2
- data/lib/functional-light-service/context/key_verifier.rb +1 -1
- data/lib/functional-light-service/context.rb +15 -2
- data/lib/functional-light-service/errors.rb +1 -0
- data/lib/functional-light-service/functional/sequencer.rb +144 -0
- data/lib/functional-light-service/i18n/localization_adapter.rb +50 -0
- data/lib/functional-light-service/localization_adapter.rb +19 -28
- data/lib/functional-light-service/localization_map.rb +9 -0
- data/lib/functional-light-service/organizer/reduce_case.rb +50 -0
- data/lib/functional-light-service/organizer/reduce_if_else.rb +23 -0
- data/lib/functional-light-service/organizer/reduce_until.rb +1 -1
- data/lib/functional-light-service/organizer/reduce_while.rb +31 -0
- data/lib/functional-light-service/organizer/scoped_reducable.rb +2 -2
- data/lib/functional-light-service/organizer/with_reducer_log_decorator.rb +2 -1
- data/lib/functional-light-service/organizer.rb +18 -3
- data/lib/functional-light-service/version.rb +1 -1
- data/lib/functional-light-service.rb +6 -0
- data/spec/acceptance/organizer/add_to_context_spec.rb +24 -0
- data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +22 -0
- data/spec/acceptance/organizer/execute_spec.rb +21 -0
- data/spec/acceptance/organizer/reduce_case_spec.rb +65 -0
- data/spec/acceptance/organizer/reduce_if_else_spec.rb +60 -0
- data/spec/acceptance/organizer/reduce_while_spec.rb +96 -0
- data/spec/acceptance/skip_all_remaining_spec.rb +139 -0
- data/spec/action_optional_expected_keys_spec.rb +107 -0
- data/spec/context/inspect_spec.rb +13 -3
- data/spec/context_spec.rb +12 -0
- data/spec/i18n_localization_adapter_spec.rb +83 -0
- data/spec/lib/deterministic/sequencer_spec.rb +506 -0
- data/spec/localization_adapter_spec.rb +66 -83
- data/spec/spec_helper.rb +3 -0
- data/spec/test_doubles.rb +28 -0
- metadata +27 -14
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe "skip_all_remaining!" do
|
|
4
|
+
context "with regular organizer" do
|
|
5
|
+
let(:organizer) do
|
|
6
|
+
Class.new do
|
|
7
|
+
extend FunctionalLightService::Organizer
|
|
8
|
+
|
|
9
|
+
def self.call(ctx)
|
|
10
|
+
with(ctx).reduce(actions)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.actions
|
|
14
|
+
[
|
|
15
|
+
execute(->(c) { c[:first] = true }),
|
|
16
|
+
execute(lambda(&:skip_all_remaining!)),
|
|
17
|
+
execute(->(c) { c[:second] = true })
|
|
18
|
+
]
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "skips all remaining actions" do
|
|
24
|
+
result = organizer.call(FunctionalLightService::Context.make)
|
|
25
|
+
|
|
26
|
+
expect(result[:first]).to be true
|
|
27
|
+
expect(result[:second]).to be_nil
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
context "with an organizer with a reducer" do
|
|
32
|
+
let(:organizer) do
|
|
33
|
+
Class.new do
|
|
34
|
+
extend FunctionalLightService::Organizer
|
|
35
|
+
|
|
36
|
+
def self.call(ctx)
|
|
37
|
+
with(ctx).reduce(actions)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.actions
|
|
41
|
+
[
|
|
42
|
+
reduce_if(
|
|
43
|
+
->(_) { true },
|
|
44
|
+
[
|
|
45
|
+
execute(->(c) { c[:first_inside] = true }),
|
|
46
|
+
execute(lambda(&:skip_all_remaining!)),
|
|
47
|
+
execute(->(c) { c[:second_inside] = true })
|
|
48
|
+
]
|
|
49
|
+
),
|
|
50
|
+
execute(->(c) { c[:outside] = true })
|
|
51
|
+
]
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "skips all remaining actions inside and outside the reducer" do
|
|
57
|
+
result = organizer.call(FunctionalLightService::Context.make)
|
|
58
|
+
|
|
59
|
+
expect(result[:first_inside]).to be true
|
|
60
|
+
expect(result[:second_inside]).to be_nil
|
|
61
|
+
expect(result[:outside]).to be_nil
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
context "with a message" do
|
|
66
|
+
let(:organizer) do
|
|
67
|
+
Class.new do
|
|
68
|
+
extend FunctionalLightService::Organizer
|
|
69
|
+
|
|
70
|
+
def self.call(ctx)
|
|
71
|
+
with(ctx).reduce(actions)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def self.actions
|
|
75
|
+
[
|
|
76
|
+
reduce_if(
|
|
77
|
+
->(_) { true },
|
|
78
|
+
[
|
|
79
|
+
execute(->(c) { c.skip_all_remaining!("Skipping with message") })
|
|
80
|
+
]
|
|
81
|
+
),
|
|
82
|
+
execute(->(c) { c[:outside] = true })
|
|
83
|
+
]
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it "preserves the message when exiting scoped reducers" do
|
|
89
|
+
result = organizer.call(FunctionalLightService::Context.make)
|
|
90
|
+
|
|
91
|
+
expect(result.message).to eq("Skipping with message")
|
|
92
|
+
expect(result[:outside]).to be_nil
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
context "with an organizer with nested reducers" do
|
|
97
|
+
let(:organizer) do
|
|
98
|
+
Class.new do
|
|
99
|
+
extend FunctionalLightService::Organizer
|
|
100
|
+
|
|
101
|
+
def self.call(ctx)
|
|
102
|
+
with(ctx).reduce(actions)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def self.actions # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
106
|
+
[
|
|
107
|
+
reduce_if(
|
|
108
|
+
->(_) { true },
|
|
109
|
+
[
|
|
110
|
+
iterate(:items, [
|
|
111
|
+
execute(->(c) {
|
|
112
|
+
c[:executed_items] ||= []
|
|
113
|
+
c[:executed_items] << c[:item]
|
|
114
|
+
}),
|
|
115
|
+
execute(->(c) { c.skip_all_remaining! if c[:item] == 2 }),
|
|
116
|
+
execute(->(c) {
|
|
117
|
+
c[:skipped_in_iterate] ||= []
|
|
118
|
+
c[:skipped_in_iterate] << c[:item]
|
|
119
|
+
})
|
|
120
|
+
]),
|
|
121
|
+
execute(->(c) { c[:after_iterate_inside_if] = true })
|
|
122
|
+
]
|
|
123
|
+
),
|
|
124
|
+
execute(->(c) { c[:outside] = true })
|
|
125
|
+
]
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it "skips all remaining actions across all nested scopes" do
|
|
131
|
+
result = organizer.call(FunctionalLightService::Context.make(:items => [1, 2, 3]))
|
|
132
|
+
|
|
133
|
+
expect(result[:executed_items]).to eq([1, 2])
|
|
134
|
+
expect(result[:skipped_in_iterate]).to eq([1])
|
|
135
|
+
expect(result[:after_iterate_inside_if]).to be_nil
|
|
136
|
+
expect(result[:outside]).to be_nil
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'test_doubles'
|
|
3
|
+
|
|
4
|
+
describe ":expects macro using defaults" do
|
|
5
|
+
context "when all expected keys are supplied" do
|
|
6
|
+
it "is expected to ignore default values" do
|
|
7
|
+
outcome = TestDoubles::AddsNumbersWithOptionalDefaults.execute(
|
|
8
|
+
:first_number => 3,
|
|
9
|
+
:second_number => 5,
|
|
10
|
+
:third_number => 7
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
expect(outcome.total).to eq 15
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
context "when defaults are supplied" do
|
|
18
|
+
it "is expected to use static values" do
|
|
19
|
+
outcome = TestDoubles::AddsNumbersWithOptionalDefaults.execute(
|
|
20
|
+
:first_number => 3,
|
|
21
|
+
:second_number => 7
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
expect(outcome.total).to eq 20
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "is expected to use dynamic values" do
|
|
28
|
+
outcome = TestDoubles::AddsNumbersWithOptionalDefaults.execute(
|
|
29
|
+
:first_number => 3,
|
|
30
|
+
:third_number => 5
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
expect(outcome.total).to eq 18
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "is expected to process defaults in their defined order" do
|
|
37
|
+
outcome = TestDoubles::AddsNumbersWithOptionalDefaults.execute(
|
|
38
|
+
:third_number => 5,
|
|
39
|
+
:first_number => 3
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
expect(outcome.total).to eq 18
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "is expected to use all defaults if required" do
|
|
46
|
+
outcome = TestDoubles::AddsNumbersWithOptionalDefaults.execute(
|
|
47
|
+
:first_number => 3
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
expect(outcome.total).to eq 23
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
context "when used within an organizer" do
|
|
55
|
+
it "is expected to process required defaults" do
|
|
56
|
+
outcome = TestDoubles::OrganizerWithActionsUsingDefaults.call
|
|
57
|
+
|
|
58
|
+
expect(outcome.total).to eq 20
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
context "when the expected key is satisfied through an alias" do
|
|
63
|
+
it "is expected to not apply the default" do
|
|
64
|
+
action = Class.new do
|
|
65
|
+
extend FunctionalLightService::Action
|
|
66
|
+
|
|
67
|
+
expects :greeting, :default => "hello"
|
|
68
|
+
promises :greeted
|
|
69
|
+
|
|
70
|
+
executed do |ctx|
|
|
71
|
+
ctx.greeted = ctx.greeting
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
organizer = Class.new do
|
|
76
|
+
extend FunctionalLightService::Organizer
|
|
77
|
+
|
|
78
|
+
aliases :salutation => :greeting
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
outcome = organizer.with(:salutation => "ciao").reduce([action])
|
|
82
|
+
|
|
83
|
+
expect(outcome.greeted).to eq "ciao"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
context "when defaults are misconfigured" do
|
|
88
|
+
it "is expected to raise an exception" do
|
|
89
|
+
expect do
|
|
90
|
+
# Needs to be specified in the block
|
|
91
|
+
# as error is raised at define time
|
|
92
|
+
class AddsNumbersWithIncorrectDefaults
|
|
93
|
+
extend FunctionalLightService::Action
|
|
94
|
+
|
|
95
|
+
expects :first, :default => 10 # This one is fine. Other two arent
|
|
96
|
+
expects :second, :defalut => ->(ctx) { ctx[:first] + 7 }
|
|
97
|
+
expects :third, :deafult => 10
|
|
98
|
+
promises :total
|
|
99
|
+
|
|
100
|
+
executed do |ctx|
|
|
101
|
+
ctx.total = ctx.first + ctx.second + ctx.third
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end.to raise_error(FunctionalLightService::UnusableExpectKeyDefaultError)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -14,7 +14,7 @@ RSpec.describe FunctionalLightService::Context do
|
|
|
14
14
|
it 'inspects the hash with all the fields' do
|
|
15
15
|
inspected_context =
|
|
16
16
|
"FunctionalLightService::Context({}, success: true, message: '', error_code: nil, " \
|
|
17
|
-
"skip_remaining: false, aliases: {})"
|
|
17
|
+
"skip_remaining: false, skip_all_remaining: false, aliases: {})"
|
|
18
18
|
|
|
19
19
|
expect(context.inspect).to eq(inspected_context)
|
|
20
20
|
end
|
|
@@ -24,7 +24,7 @@ RSpec.describe FunctionalLightService::Context do
|
|
|
24
24
|
|
|
25
25
|
inspected_context =
|
|
26
26
|
"FunctionalLightService::Context({}, success: false, message: 'There was an error', " \
|
|
27
|
-
"error_code: nil, skip_remaining: false, aliases: {})"
|
|
27
|
+
"error_code: nil, skip_remaining: false, skip_all_remaining: false, aliases: {})"
|
|
28
28
|
|
|
29
29
|
expect(context.inspect).to eq(inspected_context)
|
|
30
30
|
end
|
|
@@ -34,7 +34,17 @@ RSpec.describe FunctionalLightService::Context do
|
|
|
34
34
|
|
|
35
35
|
inspected_context =
|
|
36
36
|
"FunctionalLightService::Context({}, success: true, message: 'No need to process', " \
|
|
37
|
-
"error_code: nil, skip_remaining: true, aliases: {})"
|
|
37
|
+
"error_code: nil, skip_remaining: true, skip_all_remaining: false, aliases: {})"
|
|
38
|
+
|
|
39
|
+
expect(context.inspect).to eq(inspected_context)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'prints skip_all_remaining' do
|
|
43
|
+
context.skip_all_remaining!('Nothing else to process')
|
|
44
|
+
|
|
45
|
+
inspected_context =
|
|
46
|
+
"FunctionalLightService::Context({}, success: true, message: 'Nothing else to process', " \
|
|
47
|
+
"error_code: nil, skip_remaining: false, skip_all_remaining: true, aliases: {})"
|
|
38
48
|
|
|
39
49
|
expect(context.inspect).to eq(inspected_context)
|
|
40
50
|
end
|
data/spec/context_spec.rb
CHANGED
|
@@ -198,6 +198,18 @@ RSpec.describe FunctionalLightService::Context do
|
|
|
198
198
|
expect { action.execute(:_before_actions => []) }
|
|
199
199
|
.to raise_error(FunctionalLightService::ReservedKeysInContextError)
|
|
200
200
|
end
|
|
201
|
+
|
|
202
|
+
it "rejects :organized_by in expects/promises" do
|
|
203
|
+
action = Class.new do
|
|
204
|
+
extend FunctionalLightService::Action
|
|
205
|
+
|
|
206
|
+
expects :organized_by
|
|
207
|
+
executed { |_ctx| } # rubocop:disable Lint/EmptyBlock
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
expect { action.execute(:organized_by => Object) }
|
|
211
|
+
.to raise_error(FunctionalLightService::ReservedKeysInContextError)
|
|
212
|
+
end
|
|
201
213
|
end
|
|
202
214
|
|
|
203
215
|
describe "#fail! does not mutate the caller's options hash" do
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require 'test_doubles'
|
|
3
|
+
|
|
4
|
+
describe FunctionalLightService::I18n::LocalizationAdapter do
|
|
5
|
+
let(:action_class) { TestDoubles::AnAction }
|
|
6
|
+
let(:adapter) { described_class.new }
|
|
7
|
+
|
|
8
|
+
describe "#failure" do
|
|
9
|
+
subject { adapter.failure(message_or_key, action_class) }
|
|
10
|
+
|
|
11
|
+
context "when provided a Symbol" do
|
|
12
|
+
let(:message_or_key) { :not_found }
|
|
13
|
+
|
|
14
|
+
it "translates the message" do
|
|
15
|
+
expected_scope = "test_doubles/an_action.light_service.failures"
|
|
16
|
+
|
|
17
|
+
expect(I18n).to receive(:t)
|
|
18
|
+
.with(message_or_key, :scope => expected_scope)
|
|
19
|
+
.and_return("message")
|
|
20
|
+
|
|
21
|
+
expect(subject).to eq("message")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "allows passing interpolation options to I18n layer" do
|
|
25
|
+
expect(I18n).to receive(:t)
|
|
26
|
+
.with(message_or_key, hash_including(:i18n_variable => "value"))
|
|
27
|
+
.and_return("message")
|
|
28
|
+
|
|
29
|
+
subject = adapter.failure(message_or_key,
|
|
30
|
+
action_class,
|
|
31
|
+
:i18n_variable => "value")
|
|
32
|
+
|
|
33
|
+
expect(subject).to eq("message")
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
context "when provided a String" do
|
|
38
|
+
let(:message_or_key) { "action failed" }
|
|
39
|
+
|
|
40
|
+
it "returns the message" do
|
|
41
|
+
expect(subject).to eq(message_or_key)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe "#success" do
|
|
47
|
+
subject { adapter.success(message_or_key, action_class) }
|
|
48
|
+
|
|
49
|
+
context "when provided a Symbol" do
|
|
50
|
+
let(:message_or_key) { :not_found }
|
|
51
|
+
|
|
52
|
+
it "translates the message" do
|
|
53
|
+
expected_scope = "test_doubles/an_action.light_service.successes"
|
|
54
|
+
|
|
55
|
+
expect(I18n).to receive(:t)
|
|
56
|
+
.with(message_or_key, :scope => expected_scope)
|
|
57
|
+
.and_return("message")
|
|
58
|
+
|
|
59
|
+
expect(subject).to eq("message")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "allows passing interpolation options to I18n layer" do
|
|
63
|
+
expect(I18n).to receive(:t)
|
|
64
|
+
.with(message_or_key, hash_including(:i18n_variable => "value"))
|
|
65
|
+
.and_return("message")
|
|
66
|
+
|
|
67
|
+
subject = adapter.success(message_or_key,
|
|
68
|
+
action_class,
|
|
69
|
+
:i18n_variable => "value")
|
|
70
|
+
|
|
71
|
+
expect(subject).to eq("message")
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
context "when provided a String" do
|
|
76
|
+
let(:message_or_key) { "action failed" }
|
|
77
|
+
|
|
78
|
+
it "returns the message" do
|
|
79
|
+
expect(subject).to eq(message_or_key)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|