light-service 0.4.0 → 0.5.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/README.md +121 -8
- data/lib/light-service.rb +2 -0
- data/lib/light-service/action.rb +21 -6
- data/lib/light-service/configuration.rb +15 -9
- data/lib/light-service/context.rb +27 -10
- data/lib/light-service/context_key_verifier.rb +6 -5
- data/lib/light-service/errors.rb +5 -0
- data/lib/light-service/localization_adapter.rb +41 -0
- data/lib/light-service/organizer/with_reducer.rb +30 -2
- data/lib/light-service/organizer/with_reducer_log_decorator.rb +18 -14
- data/lib/light-service/version.rb +1 -1
- data/light-service.gemspec +2 -0
- data/spec/acceptance/add_numbers_spec.rb +4 -44
- data/spec/acceptance/log_from_organizer_spec.rb +19 -2
- data/spec/acceptance/message_localization_spec.rb +116 -0
- data/spec/acceptance/rollback_spec.rb +134 -0
- data/spec/action_promised_keys_spec.rb +21 -9
- data/spec/action_spec.rb +8 -8
- data/spec/context_spec.rb +34 -10
- data/spec/localization_adapter_spec.rb +79 -0
- data/spec/organizer/with_reducer_spec.rb +44 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/test_doubles.rb +54 -7
- metadata +38 -14
@@ -1,6 +1,8 @@
|
|
1
1
|
module LightService; module Organizer
|
2
2
|
class WithReducerLogDecorator
|
3
|
-
attr_reader :logger, :decorated, :organizer
|
3
|
+
attr_reader :logged, :logger, :decorated, :organizer
|
4
|
+
|
5
|
+
alias_method :logged?, :logged
|
4
6
|
|
5
7
|
def initialize(decorated = WithReducer.new, organizer)
|
6
8
|
@decorated, @organizer = decorated, organizer
|
@@ -19,8 +21,13 @@ module LightService; module Organizer
|
|
19
21
|
|
20
22
|
def reduce(*actions)
|
21
23
|
decorated.reduce(*actions) do |context, action|
|
22
|
-
next if
|
23
|
-
|
24
|
+
next if logged?
|
25
|
+
if has_failure?(context)
|
26
|
+
write_failure_log(context, action) and next
|
27
|
+
end
|
28
|
+
if skip_all?(context)
|
29
|
+
write_skip_all_log(context, action) and next
|
30
|
+
end
|
24
31
|
|
25
32
|
logger.info("[LightService] - executing <#{action.to_s}>")
|
26
33
|
if defined? action.expects and action.expects.any?
|
@@ -34,29 +41,26 @@ module LightService; module Organizer
|
|
34
41
|
end
|
35
42
|
|
36
43
|
private
|
37
|
-
def logged?
|
38
|
-
@logged
|
39
|
-
end
|
40
44
|
|
41
45
|
def extract_keys(keys)
|
42
46
|
keys.map {|key| ":#{key}" }.join(', ')
|
43
47
|
end
|
44
48
|
|
45
|
-
def has_failure?(context
|
46
|
-
|
47
|
-
|
48
|
-
return true if logged?
|
49
|
+
def has_failure?(context)
|
50
|
+
context.respond_to?(:failure?) && context.failure?
|
51
|
+
end
|
49
52
|
|
53
|
+
def write_failure_log(context, action)
|
50
54
|
logger.warn("[LightService] - :-((( <#{action.to_s}> has failed...")
|
51
55
|
logger.warn("[LightService] - context message: #{context.message}")
|
52
56
|
@logged = true
|
53
57
|
end
|
54
58
|
|
55
|
-
def skip_all?(context
|
56
|
-
|
57
|
-
|
58
|
-
return true if logged?
|
59
|
+
def skip_all?(context)
|
60
|
+
context.respond_to?(:skip_all?) && context.skip_all?
|
61
|
+
end
|
59
62
|
|
63
|
+
def write_skip_all_log(context, action)
|
60
64
|
logger.info("[LightService] - ;-) <#{action.to_s}> has decided to skip the rest of the actions")
|
61
65
|
logger.info("[LightService] - context message: #{context.message}")
|
62
66
|
@logged = true
|
data/light-service.gemspec
CHANGED
@@ -16,6 +16,8 @@ Gem::Specification.new do |gem|
|
|
16
16
|
gem.require_paths = ["lib"]
|
17
17
|
gem.version = LightService::VERSION
|
18
18
|
|
19
|
+
gem.add_dependency("activesupport", ">= 4.0")
|
20
|
+
|
19
21
|
gem.add_development_dependency("rspec", "~> 3.0")
|
20
22
|
gem.add_development_dependency("rspec-its", "~> 1.0")
|
21
23
|
gem.add_development_dependency("simplecov", "~> 0.7.1")
|
@@ -1,49 +1,9 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'test_doubles'
|
2
3
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
def self.add_numbers(number)
|
7
|
-
with(:number => number).reduce(
|
8
|
-
AddsOneAction,
|
9
|
-
AddsTwoAction,
|
10
|
-
AddsThreeAction
|
11
|
-
)
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
class AddsOneAction
|
16
|
-
include LightService::Action
|
17
|
-
expects :number
|
18
|
-
promises :number
|
19
|
-
|
20
|
-
executed do |context|
|
21
|
-
context.number += 1
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
class AddsTwoAction
|
26
|
-
include LightService::Action
|
27
|
-
expects :number
|
28
|
-
|
29
|
-
executed do |context|
|
30
|
-
context.number += 2
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
class AddsThreeAction
|
35
|
-
include LightService::Action
|
36
|
-
expects :number
|
37
|
-
promises :product
|
38
|
-
|
39
|
-
executed do |context|
|
40
|
-
context.product = context.number + 3
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
describe AdditionOrganizer do
|
45
|
-
it "Adds 1 2 3 and through to 1" do
|
46
|
-
result = AdditionOrganizer.add_numbers 1
|
4
|
+
describe TestDoubles::AdditionOrganizer do
|
5
|
+
it "Adds 1, 2 and 3 to the initial value of 1" do
|
6
|
+
result = TestDoubles::AdditionOrganizer.add_numbers 1
|
47
7
|
number = result.fetch(:product)
|
48
8
|
|
49
9
|
expect(number).to eq(7)
|
@@ -9,7 +9,7 @@ describe "Logs from organizer" do
|
|
9
9
|
strio = StringIO.new
|
10
10
|
LightService::Configuration.logger = Logger.new(strio)
|
11
11
|
|
12
|
-
|
12
|
+
yield
|
13
13
|
|
14
14
|
LightService::Configuration.logger = original_logger
|
15
15
|
|
@@ -90,7 +90,24 @@ describe "Logs from organizer" do
|
|
90
90
|
it "logs it with a warning" do
|
91
91
|
organizer_log_message = "WARN -- : [LightService] - :-((( <TestDoubles::MakesLatteAction> has failed..."
|
92
92
|
expect(log_message).to include(organizer_log_message)
|
93
|
-
organizer_log_message = "WARN -- : [LightService] - context message: Can't make a latte from a milk that's
|
93
|
+
organizer_log_message = "WARN -- : [LightService] - context message: Can't make a latte from a milk that's very hot!"
|
94
|
+
expect(log_message).to include(organizer_log_message)
|
95
|
+
organizer_log_message = "[LightService] - :-((( <TestDoubles::AddsTwoAction> has failed..."
|
96
|
+
expect(log_message).not_to include(organizer_log_message)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context "when the context has failed with rollback" do
|
101
|
+
subject(:log_message) do
|
102
|
+
collects_log do
|
103
|
+
TestDoubles::MakesCappuccinoAddsTwoAndFails.call("espresso coffee", :super_hot)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
it "logs it with a warning" do
|
108
|
+
organizer_log_message = "WARN -- : [LightService] - :-((( <TestDoubles::MakesLatteAction> has failed..."
|
109
|
+
expect(log_message).to include(organizer_log_message)
|
110
|
+
organizer_log_message = "WARN -- : [LightService] - context message: Can't make a latte from a milk that's super hot!"
|
94
111
|
expect(log_message).to include(organizer_log_message)
|
95
112
|
organizer_log_message = "[LightService] - :-((( <TestDoubles::AddsTwoAction> has failed..."
|
96
113
|
expect(log_message).not_to include(organizer_log_message)
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "test_doubles"
|
3
|
+
|
4
|
+
class TestsLocalizationAdapter
|
5
|
+
extend LightService::Organizer
|
6
|
+
|
7
|
+
def self.with_message(pass_or_fail, message_or_key, i18n_options={})
|
8
|
+
with({
|
9
|
+
pass_or_fail: pass_or_fail,
|
10
|
+
message_or_key: message_or_key,
|
11
|
+
i18n_options: i18n_options
|
12
|
+
}).reduce(TestsLocalizationInvocationOptionsAction)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class TestsLocalizationInvocationOptionsAction
|
17
|
+
include LightService::Action
|
18
|
+
expects :pass_or_fail, :message_or_key, :i18n_options
|
19
|
+
|
20
|
+
executed do |context|
|
21
|
+
if context.pass_or_fail == true
|
22
|
+
context.succeed!(context.message_or_key, context.i18n_options)
|
23
|
+
else
|
24
|
+
context.fail!(context.message_or_key, context.i18n_options)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def pass_with(message_or_key, i18n_options={})
|
30
|
+
TestsLocalizationAdapter.with_message(true, message_or_key, i18n_options)
|
31
|
+
end
|
32
|
+
|
33
|
+
def fail_with(message_or_key, i18n_options={})
|
34
|
+
TestsLocalizationAdapter.with_message(false, message_or_key, i18n_options)
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "Localization Adapter" do
|
38
|
+
|
39
|
+
before do
|
40
|
+
I18n.backend.store_translations(:en, {
|
41
|
+
tests_localization_invocation_options_action: {
|
42
|
+
light_service: {
|
43
|
+
failures: {
|
44
|
+
some_failure_reason: "This has failed",
|
45
|
+
failure_with_interpolation: "Failed with %{reason}"
|
46
|
+
},
|
47
|
+
successes: {
|
48
|
+
some_success_reason: "This has passed",
|
49
|
+
success_with_interpolation: "Passed with %{reason}"
|
50
|
+
}
|
51
|
+
}
|
52
|
+
}
|
53
|
+
})
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "passing a simple string message" do
|
57
|
+
describe "by failing the context" do
|
58
|
+
it "returns the string" do
|
59
|
+
result = fail_with("string message")
|
60
|
+
|
61
|
+
expect(result).to be_failure
|
62
|
+
expect(result.message).to eq("string message")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "by passing the context" do
|
67
|
+
it "returns the string" do
|
68
|
+
result = pass_with("string message")
|
69
|
+
|
70
|
+
expect(result).to be_success
|
71
|
+
expect(result.message).to eq("string message")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "passing a Symbol" do
|
77
|
+
describe "by failing the context" do
|
78
|
+
it "performs a translation" do
|
79
|
+
result = fail_with(:some_failure_reason)
|
80
|
+
|
81
|
+
expect(result).to be_failure
|
82
|
+
expect(result.message).to eq("This has failed")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "by passing the contenxt" do
|
87
|
+
it "performs a translation" do
|
88
|
+
result = pass_with(:some_success_reason)
|
89
|
+
|
90
|
+
expect(result).to be_success
|
91
|
+
expect(result.message).to eq("This has passed")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe "passing a Symbol with interpolation variables" do
|
97
|
+
describe "by failing the context" do
|
98
|
+
it "performs a translation with interpolation" do
|
99
|
+
result = fail_with(:failure_with_interpolation, :reason => "bad account")
|
100
|
+
|
101
|
+
expect(result).to be_failure
|
102
|
+
expect(result.message).to eq("Failed with bad account")
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe "by passing the context" do
|
107
|
+
it "performs a translation with interpolation" do
|
108
|
+
result = pass_with(:success_with_interpolation, :reason => "account in good standing")
|
109
|
+
|
110
|
+
expect(result).to be_success
|
111
|
+
expect(result.message).to eq("Passed with account in good standing")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'test_doubles'
|
3
|
+
|
4
|
+
class RollbackOrganizer
|
5
|
+
extend LightService::Organizer
|
6
|
+
|
7
|
+
def self.for(number)
|
8
|
+
with(:number => number).reduce(
|
9
|
+
AddsOneWithRollbackAction,
|
10
|
+
TestDoubles::AddsTwoAction,
|
11
|
+
AddsThreeWithRollbackAction
|
12
|
+
)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class AddsOneWithRollbackAction
|
17
|
+
include LightService::Action
|
18
|
+
expects :number
|
19
|
+
promises :number
|
20
|
+
|
21
|
+
executed do |context|
|
22
|
+
if context.number == 0
|
23
|
+
context.fail_with_rollback!
|
24
|
+
end
|
25
|
+
|
26
|
+
context.number += 1
|
27
|
+
end
|
28
|
+
|
29
|
+
rolled_back do |context|
|
30
|
+
context.number -= 1
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class AddsThreeWithRollbackAction
|
35
|
+
include LightService::Action
|
36
|
+
expects :number
|
37
|
+
|
38
|
+
executed do |context|
|
39
|
+
context.number = context.number + 3
|
40
|
+
|
41
|
+
context.fail_with_rollback!("I did not like this!")
|
42
|
+
end
|
43
|
+
|
44
|
+
rolled_back do |context|
|
45
|
+
context.number -= 3
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class RollbackOrganizerWithNoRollback
|
50
|
+
extend LightService::Organizer
|
51
|
+
|
52
|
+
def self.for(number)
|
53
|
+
with(:number => number).reduce(
|
54
|
+
TestDoubles::AddsOneAction,
|
55
|
+
TestDoubles::AddsTwoAction,
|
56
|
+
AddsThreeWithNoRollbackAction
|
57
|
+
)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class AddsThreeWithNoRollbackAction
|
62
|
+
include LightService::Action
|
63
|
+
expects :number
|
64
|
+
|
65
|
+
executed do |context|
|
66
|
+
context.number = context.number + 3
|
67
|
+
|
68
|
+
context.fail_with_rollback!("I did not like this!")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class RollbackOrganizerWithMiddleRollback
|
73
|
+
extend LightService::Organizer
|
74
|
+
|
75
|
+
def self.for(number)
|
76
|
+
with(:number => number).reduce(
|
77
|
+
TestDoubles::AddsOneAction,
|
78
|
+
AddsTwoActionWithRollback,
|
79
|
+
TestDoubles::AddsThreeAction
|
80
|
+
)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class AddsTwoActionWithRollback
|
85
|
+
include LightService::Action
|
86
|
+
expects :number
|
87
|
+
|
88
|
+
executed do |context|
|
89
|
+
context.number = context.number + 2
|
90
|
+
|
91
|
+
context.fail_with_rollback!("I did not like this a bit!")
|
92
|
+
end
|
93
|
+
|
94
|
+
rolled_back do |context|
|
95
|
+
context.number -= 2
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe "Rolling back actions when there is a failure" do
|
100
|
+
it "Adds 1, 2, 3 to 1 and rolls back " do
|
101
|
+
result = RollbackOrganizer.for 1
|
102
|
+
number = result.fetch(:number)
|
103
|
+
|
104
|
+
expect(result).to be_failure
|
105
|
+
expect(result.message).to eq("I did not like this!")
|
106
|
+
expect(number).to eq(3)
|
107
|
+
end
|
108
|
+
|
109
|
+
it "won't error out when actions don't define rollback" do
|
110
|
+
result = RollbackOrganizerWithNoRollback.for 1
|
111
|
+
number = result.fetch(:number)
|
112
|
+
|
113
|
+
expect(result).to be_failure
|
114
|
+
expect(result.message).to eq("I did not like this!")
|
115
|
+
expect(number).to eq(7)
|
116
|
+
end
|
117
|
+
|
118
|
+
it "rolls back properly when triggered with an action in the middle" do
|
119
|
+
result = RollbackOrganizerWithMiddleRollback.for 1
|
120
|
+
number = result.fetch(:number)
|
121
|
+
|
122
|
+
expect(result).to be_failure
|
123
|
+
expect(result.message).to eq("I did not like this a bit!")
|
124
|
+
expect(number).to eq(2)
|
125
|
+
end
|
126
|
+
|
127
|
+
it "rolls back from the first action" do
|
128
|
+
result = RollbackOrganizer.for 0
|
129
|
+
number = result.fetch(:number)
|
130
|
+
|
131
|
+
expect(result).to be_failure
|
132
|
+
expect(number).to eq(-1)
|
133
|
+
end
|
134
|
+
end
|
@@ -5,26 +5,32 @@ describe ":promises macro" do
|
|
5
5
|
|
6
6
|
context "when the promised key is not in the context" do
|
7
7
|
it "raises an ArgumentError" do
|
8
|
-
class TestDoubles::
|
8
|
+
class TestDoubles::MakesCappuccinoAction1
|
9
|
+
include LightService::Action
|
10
|
+
expects :coffee, :milk
|
11
|
+
promises :cappuccino
|
9
12
|
executed do |context|
|
10
13
|
context[:macchiato] = "#{context.coffee} - #{context.milk}"
|
11
14
|
end
|
12
15
|
end
|
13
16
|
|
14
|
-
exception_error_text = "promised :cappuccino to be in the context during TestDoubles::
|
17
|
+
exception_error_text = "promised :cappuccino to be in the context during TestDoubles::MakesCappuccinoAction1"
|
15
18
|
expect {
|
16
|
-
TestDoubles::
|
19
|
+
TestDoubles::MakesCappuccinoAction1.execute(:coffee => "espresso", :milk => "2%")
|
17
20
|
}.to raise_error(LightService::PromisedKeysNotInContextError, exception_error_text)
|
18
21
|
end
|
19
22
|
|
20
23
|
it "can fail the context without fulfilling its promise" do
|
21
|
-
class TestDoubles::
|
24
|
+
class TestDoubles::MakesCappuccinoAction2
|
25
|
+
include LightService::Action
|
26
|
+
expects :coffee, :milk
|
27
|
+
promises :cappuccino
|
22
28
|
executed do |context|
|
23
29
|
context.fail!("Sorry, something bad has happened.")
|
24
30
|
end
|
25
31
|
end
|
26
32
|
|
27
|
-
result_context = TestDoubles::
|
33
|
+
result_context = TestDoubles::MakesCappuccinoAction2.execute(
|
28
34
|
:coffee => "espresso",
|
29
35
|
:milk => "2%")
|
30
36
|
|
@@ -35,14 +41,17 @@ describe ":promises macro" do
|
|
35
41
|
|
36
42
|
context "when the promised key is in the context" do
|
37
43
|
it "can be set with an actual value" do
|
38
|
-
class TestDoubles::
|
44
|
+
class TestDoubles::MakesCappuccinoAction3
|
45
|
+
include LightService::Action
|
46
|
+
expects :coffee, :milk
|
47
|
+
promises :cappuccino
|
39
48
|
executed do |context|
|
40
49
|
context.cappuccino = "#{context.coffee} - with #{context.milk} milk"
|
41
50
|
context.cappuccino += " hot"
|
42
51
|
end
|
43
52
|
end
|
44
53
|
|
45
|
-
result_context = TestDoubles::
|
54
|
+
result_context = TestDoubles::MakesCappuccinoAction3.execute(
|
46
55
|
:coffee => "espresso",
|
47
56
|
:milk => "2%")
|
48
57
|
|
@@ -51,12 +60,15 @@ describe ":promises macro" do
|
|
51
60
|
end
|
52
61
|
|
53
62
|
it "can be set with nil" do
|
54
|
-
class TestDoubles::
|
63
|
+
class TestDoubles::MakesCappuccinoAction4
|
64
|
+
include LightService::Action
|
65
|
+
expects :coffee, :milk
|
66
|
+
promises :cappuccino
|
55
67
|
executed do |context|
|
56
68
|
context.cappuccino = nil
|
57
69
|
end
|
58
70
|
end
|
59
|
-
result_context = TestDoubles::
|
71
|
+
result_context = TestDoubles::MakesCappuccinoAction4.execute(
|
60
72
|
:coffee => "espresso",
|
61
73
|
:milk => "2%")
|
62
74
|
|