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.
@@ -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