light-service 0.8.4 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -0
  3. data/README.md +25 -33
  4. data/RELEASES.md +3 -0
  5. data/lib/light-service.rb +7 -0
  6. data/lib/light-service/orchestrator.rb +21 -3
  7. data/lib/light-service/organizer.rb +15 -20
  8. data/lib/light-service/organizer/execute.rb +14 -0
  9. data/lib/light-service/organizer/iterate.rb +22 -0
  10. data/lib/light-service/organizer/reduce_if.rb +17 -0
  11. data/lib/light-service/organizer/reduce_until.rb +20 -0
  12. data/lib/light-service/organizer/scoped_reducable.rb +13 -0
  13. data/lib/light-service/organizer/verify_call_method_exists.rb +28 -0
  14. data/lib/light-service/organizer/with_callback.rb +26 -0
  15. data/lib/light-service/organizer/with_reducer.rb +14 -4
  16. data/lib/light-service/organizer/with_reducer_factory.rb +2 -0
  17. data/lib/light-service/version.rb +1 -1
  18. data/light-service.gemspec +1 -1
  19. data/resources/orchestrators_deprecated.svg +10 -0
  20. data/spec/acceptance/orchestrator/context_failure_and_skipping_spec.rb +6 -4
  21. data/spec/acceptance/orchestrator/execute_spec.rb +6 -4
  22. data/spec/acceptance/orchestrator/iterate_spec.rb +7 -5
  23. data/spec/acceptance/orchestrator/organizer_action_combination_spec.rb +13 -11
  24. data/spec/acceptance/orchestrator/reduce_if_spec.rb +7 -5
  25. data/spec/acceptance/orchestrator/reduce_until_spec.rb +6 -4
  26. data/spec/acceptance/orchestrator/with_callback_spec.rb +8 -6
  27. data/spec/acceptance/organizer/around_each_with_reduce_if_spec.rb +42 -0
  28. data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +65 -0
  29. data/spec/acceptance/organizer/execute_spec.rb +46 -0
  30. data/spec/acceptance/organizer/iterate_spec.rb +51 -0
  31. data/spec/acceptance/organizer/reduce_if_spec.rb +51 -0
  32. data/spec/acceptance/organizer/reduce_until_spec.rb +43 -0
  33. data/spec/acceptance/organizer/with_callback_spec.rb +169 -0
  34. data/spec/organizer/with_reducer_spec.rb +2 -7
  35. data/spec/spec_helper.rb +12 -0
  36. data/spec/support.rb +9 -0
  37. data/spec/test_doubles.rb +7 -3
  38. metadata +28 -4
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ RSpec.describe LightService::Organizer do
5
+ class TestExecute
6
+ extend LightService::Organizer
7
+
8
+ def self.call(context)
9
+ with(context).reduce(steps)
10
+ end
11
+
12
+ def self.steps
13
+ [
14
+ TestDoubles::AddOneAction,
15
+ execute(->(ctx) { ctx.number += 1 }),
16
+ execute(->(ctx) { ctx[:something] = 'hello' }),
17
+ TestDoubles::AddOneAction
18
+ ]
19
+ end
20
+ end
21
+
22
+ let(:empty_context) { LightService::Context.make }
23
+
24
+ it 'calls the lambda in the execute block using the context' do
25
+ result = TestExecute.call(:number => 0)
26
+
27
+ expect(result).to be_success
28
+ expect(result.number).to eq(3)
29
+ expect(result[:something]).to eq('hello')
30
+ end
31
+
32
+ it 'will not execute a failed context' do
33
+ empty_context.fail!('Something bad happened')
34
+
35
+ result = TestExecute.call(empty_context)
36
+
37
+ expect(result).to be_failure
38
+ end
39
+
40
+ it 'does not execute over a skipped context' do
41
+ empty_context.skip_remaining!('No more needed')
42
+
43
+ result = TestExecute.call(empty_context)
44
+ expect(result).to be_success
45
+ end
46
+ end
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ RSpec.describe LightService::Organizer do
5
+ class TestIterate
6
+ extend LightService::Organizer
7
+
8
+ def self.call(context)
9
+ with(context)
10
+ .reduce([iterate(:numbers,
11
+ [TestDoubles::AddOneAction])])
12
+ end
13
+
14
+ def self.call_single(context)
15
+ with(context)
16
+ .reduce([iterate(:numbers,
17
+ TestDoubles::AddOneAction)])
18
+ end
19
+ end
20
+
21
+ let(:empty_context) { LightService::Context.make }
22
+
23
+ it 'reduces each item of a collection and singularizes the collection key' do
24
+ result = TestIterate.call(:numbers => [1, 2, 3, 4])
25
+
26
+ expect(result).to be_success
27
+ expect(result.number).to eq(5)
28
+ end
29
+
30
+ it 'accepts a single action or organizer' do
31
+ result = TestIterate.call_single(:numbers => [1, 2, 3, 4])
32
+
33
+ expect(result).to be_success
34
+ expect(result.number).to eq(5)
35
+ end
36
+
37
+ it 'will not iterate over a failed context' do
38
+ empty_context.fail!('Something bad happened')
39
+
40
+ result = TestIterate.call(empty_context)
41
+
42
+ expect(result).to be_failure
43
+ end
44
+
45
+ it 'does not iterate over a skipped context' do
46
+ empty_context.skip_remaining!('No more needed')
47
+
48
+ result = TestIterate.call(empty_context)
49
+ expect(result).to be_success
50
+ end
51
+ end
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ RSpec.describe LightService::Organizer do
5
+ class TestReduceIf
6
+ extend LightService::Organizer
7
+
8
+ def self.call(context)
9
+ with(context).reduce(actions)
10
+ end
11
+
12
+ def self.actions
13
+ [
14
+ TestDoubles::AddOneAction,
15
+ reduce_if(->(ctx) { ctx.number == 1 },
16
+ TestDoubles::AddOneAction)
17
+ ]
18
+ end
19
+ end
20
+
21
+ let(:empty_context) { LightService::Context.make }
22
+
23
+ it 'reduces if the block evaluates to true' do
24
+ result = TestReduceIf.call(:number => 0)
25
+
26
+ expect(result).to be_success
27
+ expect(result[:number]).to eq(2)
28
+ end
29
+
30
+ it 'does not reduce if the block evaluates to false' do
31
+ result = TestReduceIf.call(:number => 2)
32
+
33
+ expect(result).to be_success
34
+ expect(result[:number]).to eq(3)
35
+ end
36
+
37
+ it 'will not reduce over a failed context' do
38
+ empty_context.fail!('Something bad happened')
39
+
40
+ result = TestReduceIf.call(empty_context)
41
+
42
+ expect(result).to be_failure
43
+ end
44
+
45
+ it 'does not reduce over a skipped context' do
46
+ empty_context.skip_remaining!('No more needed')
47
+
48
+ result = TestReduceIf.call(empty_context)
49
+ expect(result).to be_success
50
+ end
51
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ RSpec.describe LightService::Organizer do
5
+ class TestReduceUntil
6
+ extend LightService::Organizer
7
+
8
+ def self.call(context)
9
+ with(context).reduce(actions)
10
+ end
11
+
12
+ def self.actions
13
+ [
14
+ reduce_until(->(ctx) { ctx.number == 3 },
15
+ TestDoubles::AddOneAction)
16
+ ]
17
+ end
18
+ end
19
+
20
+ let(:empty_context) { LightService::Context.make }
21
+
22
+ it 'reduces until the block evaluates to true' do
23
+ context = { :number => 1 }
24
+ result = TestReduceUntil.call(context)
25
+
26
+ expect(result).to be_success
27
+ expect(result.number).to eq(3)
28
+ end
29
+
30
+ it 'does not execute on failed context' do
31
+ empty_context.fail!('Something bad happened')
32
+
33
+ result = TestReduceUntil.call(empty_context)
34
+ expect(result).to be_failure
35
+ end
36
+
37
+ it 'does not execute a skipped context' do
38
+ empty_context.skip_remaining!('No more needed')
39
+
40
+ result = TestReduceUntil.call(empty_context)
41
+ expect(result).to be_success
42
+ end
43
+ end
@@ -0,0 +1,169 @@
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ RSpec.describe LightService::Organizer do
5
+ class TestWithCallback
6
+ extend LightService::Organizer
7
+
8
+ def self.call(context = {})
9
+ with(context).reduce(actions)
10
+ end
11
+
12
+ def self.actions
13
+ [
14
+ SetUpContextAction,
15
+ with_callback(IterateCollectionAction,
16
+ [IncrementCountAction,
17
+ AddToTotalAction])
18
+ ]
19
+ end
20
+ end
21
+
22
+ class SetUpContextAction
23
+ extend LightService::Action
24
+ promises :numbers, :counter, :total
25
+
26
+ executed do |ctx|
27
+ ctx.numbers = [1, 2, 3]
28
+ ctx.counter = 0
29
+ ctx.total = 0
30
+ end
31
+ end
32
+
33
+ class IterateCollectionAction
34
+ extend LightService::Action
35
+ expects :numbers, :callback
36
+ promises :number
37
+
38
+ executed do |ctx|
39
+ ctx.numbers.each do |number|
40
+ ctx.number = number
41
+ ctx.callback.call(ctx)
42
+ end
43
+ end
44
+ end
45
+
46
+ class IncrementCountAction
47
+ extend LightService::Action
48
+ expects :counter
49
+
50
+ executed do |ctx|
51
+ ctx.counter = ctx.counter + 1
52
+ end
53
+ end
54
+
55
+ class AddToTotalAction
56
+ extend LightService::Action
57
+ expects :number, :total
58
+
59
+ executed do |ctx|
60
+ ctx.total += ctx.number
61
+ end
62
+ end
63
+
64
+ describe 'a simple case with a single callback' do
65
+ it 'calls the actions defined with callback' do
66
+ result = TestWithCallback.call
67
+
68
+ expect(result.counter).to eq(3)
69
+ expect(result.total).to eq(6)
70
+ end
71
+ end
72
+
73
+ describe 'a more complex example with nested callbacks' do
74
+ class TestWithNestedCallback
75
+ extend LightService::Organizer
76
+
77
+ def self.call(context = {})
78
+ with(context).reduce(actions)
79
+ end
80
+
81
+ def self.actions
82
+ [
83
+ SetUpNestedContextAction,
84
+ with_callback(IterateOuterCollectionAction,
85
+ [IncrementOuterCountAction,
86
+ with_callback(IterateCollectionAction,
87
+ [IncrementCountAction,
88
+ AddToTotalAction])])
89
+ ]
90
+ end
91
+ end
92
+
93
+ class SetUpNestedContextAction
94
+ extend LightService::Action
95
+ promises :outer_numbers, :outer_counter,
96
+ :numbers, :counter, :total
97
+
98
+ executed do |ctx|
99
+ ctx.outer_numbers = [12, 17]
100
+ ctx.outer_counter = 0
101
+ ctx.numbers = [1, 2, 3]
102
+ ctx.counter = 0
103
+ ctx.total = 0
104
+ end
105
+ end
106
+
107
+ class IterateOuterCollectionAction
108
+ extend LightService::Action
109
+ expects :outer_numbers, :callback
110
+ promises :outer_number
111
+
112
+ executed do |ctx|
113
+ ctx.outer_numbers.each do |outer_number|
114
+ ctx.outer_number = outer_number
115
+ ctx.callback.call(ctx)
116
+ end
117
+ end
118
+ end
119
+
120
+ class IncrementOuterCountAction
121
+ extend LightService::Action
122
+ expects :outer_counter
123
+
124
+ executed do |ctx|
125
+ ctx.outer_counter = ctx.outer_counter + 1
126
+ end
127
+ end
128
+
129
+ it 'calls both the action and the nested callbacks' do
130
+ result = TestWithNestedCallback.call
131
+
132
+ expect(result.outer_counter).to eq(2)
133
+ # Counts and total are the duplicates of
134
+ # what you'll see in the simple spec,
135
+ # as the internal callback logic is called
136
+ # twice due to 2 items in the outer_numbers
137
+ # collection.
138
+ expect(result.counter).to eq(6)
139
+ expect(result.total).to eq(12)
140
+ end
141
+ end
142
+
143
+ describe 'with failed or skipped context' do
144
+ class TestWithFailureCallback
145
+ extend LightService::Organizer
146
+
147
+ def self.call(context = {})
148
+ with(context).reduce(actions)
149
+ end
150
+
151
+ def self.actions
152
+ [
153
+ SetUpContextAction,
154
+ with_callback(IterateCollectionAction,
155
+ [IncrementCountAction,
156
+ TestDoubles::FailureAction])
157
+ ]
158
+ end
159
+ end
160
+
161
+ it 'will not process the routine' do
162
+ result = TestWithFailureCallback.call
163
+
164
+ expect(result).to be_failure
165
+ expect(result.counter).to eq(1)
166
+ expect(result.total).to eq(0)
167
+ end
168
+ end
169
+ end
@@ -3,16 +3,13 @@ require 'test_doubles'
3
3
 
4
4
  describe LightService::Organizer::WithReducer do
5
5
  let(:context) { LightService::Context.make }
6
- let(:action1) { double(:action1) }
7
- let(:action2) { double(:action2) }
6
+ let(:action1) { TestDoubles::NullAction }
7
+ let(:action2) { TestDoubles::NullAction.clone }
8
8
  let(:actions) { [action1, action2] }
9
9
 
10
10
  before { context.current_action = action2 }
11
11
 
12
12
  it "reduces the provided actions" do
13
- expect(action1).to receive(:execute).with(context).and_return(context)
14
- expect(action2).to receive(:execute).with(context).and_return(context)
15
-
16
13
  result = described_class.new.with(context).reduce(actions)
17
14
 
18
15
  expect(result).to eq(context)
@@ -21,8 +18,6 @@ describe LightService::Organizer::WithReducer do
21
18
 
22
19
  it "executes a handler around each action and continues reducing" do
23
20
  expect(action1).to receive(:execute).with(context).and_return(context)
24
- expect(TestDoubles::AroundEachNullHandler).to receive(:call)
25
- .with(context).and_yield
26
21
 
27
22
  result = described_class.new.with(context)
28
23
  .around_each(TestDoubles::AroundEachNullHandler)
data/spec/spec_helper.rb CHANGED
@@ -1,10 +1,22 @@
1
1
  $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
2
2
  $LOAD_PATH << File.join(File.dirname(__FILE__))
3
3
 
4
+ if ENV['RUN_COVERAGE_REPORT']
5
+ require 'simplecov'
6
+
7
+ SimpleCov.start do
8
+ add_filter 'vendor/'
9
+ add_filter %r{^/spec/}
10
+ end
11
+
12
+ SimpleCov.minimum_coverage_by_file 90
13
+ end
14
+
4
15
  require 'light-service'
5
16
  require 'light-service/testing'
6
17
  require 'ostruct'
7
18
  require 'active_support/core_ext/string'
8
19
  require 'pry'
20
+ require 'support'
9
21
 
10
22
  I18n.enforce_available_locales = true
data/spec/support.rb ADDED
@@ -0,0 +1,9 @@
1
+ RSpec.shared_context 'expect orchestrator warning' do
2
+ before do
3
+ expect(ActiveSupport::Deprecation)
4
+ .to receive(:warn)
5
+ .with(/^`Orchestrator#/)
6
+ .at_least(:once)
7
+ end
8
+ end
9
+
data/spec/test_doubles.rb CHANGED
@@ -49,7 +49,7 @@ module TestDoubles
49
49
  end
50
50
 
51
51
  class AroundEachNullHandler
52
- def self.call(_action, _context)
52
+ def self.call(_action)
53
53
  yield
54
54
  end
55
55
  end
@@ -149,13 +149,11 @@ module TestDoubles
149
149
  executed do |context|
150
150
  if context.milk == :very_hot
151
151
  context.fail!("Can't make a latte from a milk that's very hot!")
152
- next context
153
152
  end
154
153
 
155
154
  if context.milk == :super_hot
156
155
  error_message = "Can't make a latte from a milk that's super hot!"
157
156
  context.fail_with_rollback!(error_message)
158
- next context
159
157
  end
160
158
 
161
159
  context[:latte] = "#{context.coffee} - with lots of #{context.milk}"
@@ -337,4 +335,10 @@ module TestDoubles
337
335
  ctx.final_key = ctx.expected_key
338
336
  end
339
337
  end
338
+
339
+ class NullAction
340
+ extend LightService::Action
341
+
342
+ executed { |_ctx| }
343
+ end
340
344
  end