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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/CHANGELOG.md +26 -0
  4. data/README.md +130 -11
  5. data/functional-light-service.gemspec +3 -1
  6. data/lib/functional-light-service/action.rb +48 -0
  7. data/lib/functional-light-service/configuration.rb +12 -2
  8. data/lib/functional-light-service/context/key_verifier.rb +1 -1
  9. data/lib/functional-light-service/context.rb +15 -2
  10. data/lib/functional-light-service/errors.rb +1 -0
  11. data/lib/functional-light-service/functional/sequencer.rb +144 -0
  12. data/lib/functional-light-service/i18n/localization_adapter.rb +50 -0
  13. data/lib/functional-light-service/localization_adapter.rb +19 -28
  14. data/lib/functional-light-service/localization_map.rb +9 -0
  15. data/lib/functional-light-service/organizer/reduce_case.rb +50 -0
  16. data/lib/functional-light-service/organizer/reduce_if_else.rb +23 -0
  17. data/lib/functional-light-service/organizer/reduce_until.rb +1 -1
  18. data/lib/functional-light-service/organizer/reduce_while.rb +31 -0
  19. data/lib/functional-light-service/organizer/scoped_reducable.rb +2 -2
  20. data/lib/functional-light-service/organizer/with_reducer_log_decorator.rb +2 -1
  21. data/lib/functional-light-service/organizer.rb +18 -3
  22. data/lib/functional-light-service/version.rb +1 -1
  23. data/lib/functional-light-service.rb +6 -0
  24. data/spec/acceptance/organizer/add_to_context_spec.rb +24 -0
  25. data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +22 -0
  26. data/spec/acceptance/organizer/execute_spec.rb +21 -0
  27. data/spec/acceptance/organizer/reduce_case_spec.rb +65 -0
  28. data/spec/acceptance/organizer/reduce_if_else_spec.rb +60 -0
  29. data/spec/acceptance/organizer/reduce_while_spec.rb +96 -0
  30. data/spec/acceptance/skip_all_remaining_spec.rb +139 -0
  31. data/spec/action_optional_expected_keys_spec.rb +107 -0
  32. data/spec/context/inspect_spec.rb +13 -3
  33. data/spec/context_spec.rb +12 -0
  34. data/spec/i18n_localization_adapter_spec.rb +83 -0
  35. data/spec/lib/deterministic/sequencer_spec.rb +506 -0
  36. data/spec/localization_adapter_spec.rb +66 -83
  37. data/spec/spec_helper.rb +3 -0
  38. data/spec/test_doubles.rb +28 -0
  39. 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