light-service 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 has_failure?(context, action)
23
- next if skip_all?(context, action)
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, action)
46
- return false unless context.respond_to?(:failure?)
47
- return false unless context.failure?
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, action)
56
- return false unless context.respond_to?(:skip_all?)
57
- return false unless context.skip_all?
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
@@ -1,3 +1,3 @@
1
1
  module LightService
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -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
- class AdditionOrganizer
4
- extend LightService::Organizer
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
- result = yield
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 too hot!"
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::MakesCappuccinoAction
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::MakesCappuccinoAction"
17
+ exception_error_text = "promised :cappuccino to be in the context during TestDoubles::MakesCappuccinoAction1"
15
18
  expect {
16
- TestDoubles::MakesCappuccinoAction.execute(:coffee => "espresso", :milk => "2%")
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::MakesCappuccinoAction
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::MakesCappuccinoAction.execute(
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::MakesCappuccinoAction
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::MakesCappuccinoAction.execute(
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::MakesCappuccinoAction
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::MakesCappuccinoAction.execute(
71
+ result_context = TestDoubles::MakesCappuccinoAction4.execute(
60
72
  :coffee => "espresso",
61
73
  :milk => "2%")
62
74