switchyard 7.0.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 +7 -0
- data/.github/workflows/project-build.yml +71 -0
- data/.gitignore +24 -0
- data/.rspec +3 -0
- data/.rubocop.yml +101 -0
- data/.solargraph.yml +11 -0
- data/Appraisals +7 -0
- data/CHANGELOG.md +303 -0
- data/CODE_OF_CONDUCT.md +22 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +1663 -0
- data/Rakefile +12 -0
- data/VERSION +1 -0
- data/audit/bench.rb +99 -0
- data/audit/verify_findings.rb +172 -0
- data/gemfiles/dry_inflector_0_2_1.gemfile +5 -0
- data/gemfiles/i18n_1_8_11.gemfile +5 -0
- data/lib/switchyard/action.rb +145 -0
- data/lib/switchyard/configuration.rb +36 -0
- data/lib/switchyard/context/key_verifier.rb +124 -0
- data/lib/switchyard/context.rb +208 -0
- data/lib/switchyard/deprecations.rb +26 -0
- data/lib/switchyard/errors.rb +9 -0
- data/lib/switchyard/functional/enum.rb +286 -0
- data/lib/switchyard/functional/maybe.rb +21 -0
- data/lib/switchyard/functional/monad.rb +77 -0
- data/lib/switchyard/functional/null.rb +88 -0
- data/lib/switchyard/functional/option.rb +100 -0
- data/lib/switchyard/functional/result.rb +129 -0
- data/lib/switchyard/functional/sequencer.rb +144 -0
- data/lib/switchyard/i18n/localization_adapter.rb +50 -0
- data/lib/switchyard/localization_adapter.rb +39 -0
- data/lib/switchyard/localization_map.rb +9 -0
- data/lib/switchyard/organizer/execute.rb +16 -0
- data/lib/switchyard/organizer/iterate.rb +30 -0
- data/lib/switchyard/organizer/reduce_case.rb +50 -0
- data/lib/switchyard/organizer/reduce_if.rb +19 -0
- data/lib/switchyard/organizer/reduce_if_else.rb +23 -0
- data/lib/switchyard/organizer/reduce_until.rb +22 -0
- data/lib/switchyard/organizer/reduce_while.rb +31 -0
- data/lib/switchyard/organizer/scoped_reducable.rb +15 -0
- data/lib/switchyard/organizer/with_callback.rb +28 -0
- data/lib/switchyard/organizer/with_reducer.rb +81 -0
- data/lib/switchyard/organizer/with_reducer_factory.rb +20 -0
- data/lib/switchyard/organizer/with_reducer_log_decorator.rb +111 -0
- data/lib/switchyard/organizer.rb +129 -0
- data/lib/switchyard/testing/context_factory.rb +48 -0
- data/lib/switchyard/testing.rb +3 -0
- data/lib/switchyard/version.rb +5 -0
- data/lib/switchyard.rb +36 -0
- data/resources/fail_actions.png +0 -0
- data/resources/light-service.png +0 -0
- data/resources/organizer_and_actions.png +0 -0
- data/resources/skip_actions.png +0 -0
- data/spec/acceptance/add_numbers_spec.rb +11 -0
- data/spec/acceptance/after_actions_spec.rb +87 -0
- data/spec/acceptance/around_each_spec.rb +19 -0
- data/spec/acceptance/before_actions_spec.rb +115 -0
- data/spec/acceptance/custom_log_from_organizer_spec.rb +61 -0
- data/spec/acceptance/deprecation_warnings_spec.rb +82 -0
- data/spec/acceptance/fail_spec.rb +52 -0
- data/spec/acceptance/log_from_organizer_spec.rb +154 -0
- data/spec/acceptance/message_localization_spec.rb +119 -0
- data/spec/acceptance/organizer/add_aliases_spec.rb +28 -0
- data/spec/acceptance/organizer/add_to_context_spec.rb +54 -0
- data/spec/acceptance/organizer/around_each_with_reduce_if_spec.rb +42 -0
- data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +90 -0
- data/spec/acceptance/organizer/execute_spec.rb +67 -0
- data/spec/acceptance/organizer/iterate_spec.rb +44 -0
- data/spec/acceptance/organizer/reduce_case_spec.rb +65 -0
- data/spec/acceptance/organizer/reduce_if_else_spec.rb +60 -0
- data/spec/acceptance/organizer/reduce_if_spec.rb +89 -0
- data/spec/acceptance/organizer/reduce_until_spec.rb +49 -0
- data/spec/acceptance/organizer/reduce_while_spec.rb +96 -0
- data/spec/acceptance/organizer/with_callback_spec.rb +113 -0
- data/spec/acceptance/organizer_entry_point_spec.rb +35 -0
- data/spec/acceptance/rollback_spec.rb +183 -0
- data/spec/acceptance/skip_all_remaining_spec.rb +139 -0
- data/spec/acceptance/testing/context_factory_spec.rb +54 -0
- data/spec/action_expected_keys_spec.rb +63 -0
- data/spec/action_expects_and_promises_spec.rb +97 -0
- data/spec/action_optional_expected_keys_spec.rb +107 -0
- data/spec/action_promised_keys_spec.rb +126 -0
- data/spec/action_spec.rb +97 -0
- data/spec/context/inspect_spec.rb +52 -0
- data/spec/context_spec.rb +301 -0
- data/spec/examples/amount_spec.rb +77 -0
- data/spec/examples/controller_spec.rb +63 -0
- data/spec/examples/validate_address_spec.rb +38 -0
- data/spec/i18n_localization_adapter_spec.rb +83 -0
- data/spec/lib/deterministic/class_mixin_spec.rb +24 -0
- data/spec/lib/deterministic/currify_spec.rb +90 -0
- data/spec/lib/deterministic/monad_axioms.rb +46 -0
- data/spec/lib/deterministic/monad_spec.rb +47 -0
- data/spec/lib/deterministic/null_spec.rb +65 -0
- data/spec/lib/deterministic/option_spec.rb +140 -0
- data/spec/lib/deterministic/result/failure_spec.rb +65 -0
- data/spec/lib/deterministic/result/result_map_spec.rb +155 -0
- data/spec/lib/deterministic/result/result_shared.rb +25 -0
- data/spec/lib/deterministic/result/success_spec.rb +41 -0
- data/spec/lib/deterministic/result_spec.rb +63 -0
- data/spec/lib/deterministic/sequencer_spec.rb +506 -0
- data/spec/lib/edge_cases_spec.rb +156 -0
- data/spec/lib/enum_spec.rb +114 -0
- data/spec/lib/native_pattern_matching_spec.rb +74 -0
- data/spec/localization_adapter_spec.rb +66 -0
- data/spec/organizer/with_reducer_spec.rb +56 -0
- data/spec/organizer_key_aliases_spec.rb +29 -0
- data/spec/organizer_spec.rb +115 -0
- data/spec/readme_spec.rb +45 -0
- data/spec/sample/calculates_order_tax_action_spec.rb +16 -0
- data/spec/sample/calculates_tax_spec.rb +30 -0
- data/spec/sample/looks_up_tax_percentage_action_spec.rb +55 -0
- data/spec/sample/provides_free_shipping_action_spec.rb +25 -0
- data/spec/sample/tax/calculates_order_tax_action.rb +10 -0
- data/spec/sample/tax/calculates_tax.rb +11 -0
- data/spec/sample/tax/looks_up_tax_percentage_action.rb +28 -0
- data/spec/sample/tax/provides_free_shipping_action.rb +11 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/support.rb +1 -0
- data/spec/test_doubles.rb +656 -0
- data/spec/testing/context_factory/iterate_spec.rb +39 -0
- data/spec/testing/context_factory/reduce_if_spec.rb +40 -0
- data/spec/testing/context_factory/reduce_until_spec.rb +40 -0
- data/spec/testing/context_factory/with_callback_spec.rb +38 -0
- data/spec/testing/context_factory_spec.rb +76 -0
- data/switchyard.gemspec +35 -0
- metadata +351 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'test_doubles'
|
|
3
|
+
|
|
4
|
+
RSpec.describe 'Action before_actions' do
|
|
5
|
+
# Gli hook restano sulla classe (non vengono piu consumati da #with):
|
|
6
|
+
# i test double sono condivisi, quindi vanno ripuliti dopo ogni esempio
|
|
7
|
+
after do
|
|
8
|
+
TestDoubles::AdditionOrganizer.before_actions = nil
|
|
9
|
+
TestDoubles::TestIterate.before_actions = nil
|
|
10
|
+
TestDoubles::TestWithCallback.before_actions = nil
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
describe 'works with simple organizers - from outside' do
|
|
14
|
+
it 'can be used to inject code block before each action' do
|
|
15
|
+
TestDoubles::AdditionOrganizer.before_actions = ->(ctx) do
|
|
16
|
+
ctx.number -= 2 if ctx.current_action == TestDoubles::AddsThreeAction
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
result = TestDoubles::AdditionOrganizer.call(0)
|
|
20
|
+
|
|
21
|
+
expect(result.fetch(:number)).to eq(4)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'works with iterator' do
|
|
25
|
+
TestDoubles::TestIterate.before_actions = [
|
|
26
|
+
->(ctx) { ctx.number -= 2 if ctx.current_action == TestDoubles::AddsOneAction }
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
result = TestDoubles::TestIterate.call(:number => 0,
|
|
30
|
+
:counters => [1, 2, 3, 4])
|
|
31
|
+
|
|
32
|
+
expect(result).to be_success
|
|
33
|
+
expect(result.number).to eq(-4)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe 'can be added to organizers declaratively' do
|
|
38
|
+
module BeforeActions
|
|
39
|
+
class AdditionOrganizer
|
|
40
|
+
extend Switchyard::Organizer
|
|
41
|
+
|
|
42
|
+
before_actions ->(ctx) do
|
|
43
|
+
ctx.number -= 2 if ctx.current_action == TestDoubles::AddsOneAction
|
|
44
|
+
end,
|
|
45
|
+
->(ctx) do
|
|
46
|
+
ctx.number -= 3 if ctx.current_action == TestDoubles::AddsThreeAction
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.call(number)
|
|
50
|
+
with(:number => number).reduce(actions)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.actions
|
|
54
|
+
[
|
|
55
|
+
TestDoubles::AddsOneAction,
|
|
56
|
+
TestDoubles::AddsTwoAction,
|
|
57
|
+
TestDoubles::AddsThreeAction
|
|
58
|
+
]
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it 'accepts before_actions hook lambdas from organizer' do
|
|
64
|
+
result = BeforeActions::AdditionOrganizer.call(0)
|
|
65
|
+
|
|
66
|
+
expect(result.fetch(:number)).to eq(1)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it 'keeps the declarative hooks on every subsequent call' do
|
|
70
|
+
first = BeforeActions::AdditionOrganizer.call(0)
|
|
71
|
+
second = BeforeActions::AdditionOrganizer.call(0)
|
|
72
|
+
|
|
73
|
+
expect(first.fetch(:number)).to eq(1)
|
|
74
|
+
expect(second.fetch(:number)).to eq(1)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
describe 'works with callbacks' do
|
|
79
|
+
it 'can interact with actions from the outside' do
|
|
80
|
+
TestDoubles::TestWithCallback.before_actions = [
|
|
81
|
+
->(ctx) { ctx.total -= 1000 if ctx.current_action == TestDoubles::AddToTotalAction }
|
|
82
|
+
]
|
|
83
|
+
result = TestDoubles::TestWithCallback.call
|
|
84
|
+
|
|
85
|
+
expect(result.counter).to eq(3)
|
|
86
|
+
expect(result.total).to eq(-2994)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
describe 'can halt all execution with a raised error' do
|
|
91
|
+
it 'does not call the rest of the callback steps' do
|
|
92
|
+
class SkipContextError < StandardError
|
|
93
|
+
attr_reader :ctx
|
|
94
|
+
|
|
95
|
+
def initialize(msg, ctx)
|
|
96
|
+
@ctx = ctx
|
|
97
|
+
super(msg)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
TestDoubles::TestWithCallback.before_actions = [
|
|
101
|
+
->(ctx) do
|
|
102
|
+
if ctx.current_action == TestDoubles::IncrementCountAction
|
|
103
|
+
ctx.total -= 1000
|
|
104
|
+
raise SkipContextError.new("stop context now", ctx)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
]
|
|
108
|
+
begin
|
|
109
|
+
TestDoubles::TestWithCallback.call
|
|
110
|
+
rescue SkipContextError => e
|
|
111
|
+
expect(e.ctx).not_to be_empty
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe "Log from an organizer with a custom logger" do
|
|
4
|
+
context "when overriding the global Switchyard organizer" do
|
|
5
|
+
let(:global_logger_organizer) do
|
|
6
|
+
Class.new do
|
|
7
|
+
extend Switchyard::Organizer
|
|
8
|
+
|
|
9
|
+
def self.call(number)
|
|
10
|
+
with(:number => number).reduce(actions)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.actions
|
|
14
|
+
[
|
|
15
|
+
TestDoubles::AddsOneAction,
|
|
16
|
+
TestDoubles::AddsTwoAction,
|
|
17
|
+
TestDoubles::AddsThreeAction
|
|
18
|
+
]
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
let(:global_logger_string) { StringIO.new }
|
|
24
|
+
|
|
25
|
+
let(:custom_logger_string) { StringIO.new }
|
|
26
|
+
let(:custom_logger_organizer) do
|
|
27
|
+
custom_logger = Logger.new(custom_logger_string)
|
|
28
|
+
|
|
29
|
+
Class.new do
|
|
30
|
+
extend Switchyard::Organizer
|
|
31
|
+
|
|
32
|
+
log_with custom_logger
|
|
33
|
+
|
|
34
|
+
def self.call(coffee, this_hot = :very_hot)
|
|
35
|
+
with(:milk => this_hot, :coffee => coffee)
|
|
36
|
+
.reduce(TestDoubles::MakesLatteAction,
|
|
37
|
+
TestDoubles::AddsTwoActionWithFetch)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
before do
|
|
43
|
+
@original_global_logger = Switchyard::Configuration.logger
|
|
44
|
+
Switchyard::Configuration.logger = Logger.new(global_logger_string)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "logs in own logger" do
|
|
48
|
+
global_logger_organizer.call(1)
|
|
49
|
+
custom_logger_organizer.call(:coffee => "Cappucino")
|
|
50
|
+
|
|
51
|
+
expect(custom_logger_string.string).to include("MakesLatteAction")
|
|
52
|
+
expect(custom_logger_string.string).to_not include("AddsOneAction")
|
|
53
|
+
expect(global_logger_string.string).to include("AddsOneAction")
|
|
54
|
+
expect(global_logger_string.string).to_not include("MakesLatteAction")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
after do
|
|
58
|
+
Switchyard::Configuration.logger = @original_global_logger
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
# Maybe()/Null e il monkey-patch di Object sono opt-in: non fanno parte
|
|
3
|
+
# del require chain di default della gem
|
|
4
|
+
require 'switchyard/functional/maybe'
|
|
5
|
+
|
|
6
|
+
describe "deprecation warnings" do
|
|
7
|
+
around do |example|
|
|
8
|
+
Switchyard::Deprecations.silenced = false
|
|
9
|
+
Switchyard::Deprecations.reset!
|
|
10
|
+
example.run
|
|
11
|
+
ensure
|
|
12
|
+
Switchyard::Deprecations.silenced = true
|
|
13
|
+
Switchyard::Deprecations.reset!
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
context "when including Switchyard::Organizer" do
|
|
17
|
+
it "warns on stderr" do
|
|
18
|
+
expect do
|
|
19
|
+
class OrganizerIncludingLS
|
|
20
|
+
include Switchyard::Organizer
|
|
21
|
+
end
|
|
22
|
+
end.to output(/Including Switchyard::Organizer is deprecated/).to_stderr
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
context "when including Switchyard::Action" do
|
|
27
|
+
it "warns on stderr" do
|
|
28
|
+
expect do
|
|
29
|
+
class ActionIncludingLS
|
|
30
|
+
include Switchyard::Action
|
|
31
|
+
end
|
|
32
|
+
end.to output(/Including Switchyard::Action is deprecated/).to_stderr
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
context "when using Maybe/Null" do
|
|
37
|
+
it "warns once per process" do
|
|
38
|
+
expect { Maybe(nil) }
|
|
39
|
+
.to output(%r{Maybe\(\)/Null are deprecated}).to_stderr
|
|
40
|
+
# warn-once: la seconda invocazione non emette nulla
|
|
41
|
+
expect { Maybe(nil) }.not_to output.to_stderr
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
context "when using the exotic operators" do
|
|
46
|
+
include Switchyard::Prelude::Result
|
|
47
|
+
|
|
48
|
+
it "Result#>= warns and delegates to #try" do
|
|
49
|
+
result = nil
|
|
50
|
+
expect { result = Success(1) >= ->(v) { Success(v + 1) } }
|
|
51
|
+
.to output(/Result#>= is deprecated/).to_stderr
|
|
52
|
+
expect(result).to eq(Success(2))
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "Result#<< warns and delegates to #pipe" do
|
|
56
|
+
expect { Success(1) << ->(_v) {} }
|
|
57
|
+
.to output(/Result#<< is deprecated/).to_stderr
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "Result#+ warns and still combines" do
|
|
61
|
+
result = nil
|
|
62
|
+
expect { result = Success(1) + Success(2) }
|
|
63
|
+
.to output(/Result#\+ is deprecated/).to_stderr
|
|
64
|
+
expect(result).to eq(Success(3))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "Option#+ warns and still combines" do
|
|
68
|
+
some = Switchyard::Option::Some
|
|
69
|
+
result = nil
|
|
70
|
+
expect { result = some.new(1) + some.new(2) }
|
|
71
|
+
.to output(/Option#\+ is deprecated/).to_stderr
|
|
72
|
+
expect(result).to eq(some.new(3))
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
context "when silenced" do
|
|
77
|
+
it "emits nothing" do
|
|
78
|
+
Switchyard::Deprecations.silenced = true
|
|
79
|
+
expect { Maybe(nil) }.not_to output.to_stderr
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe "fail_and_return!" do
|
|
4
|
+
describe "returns immediately from executed block" do
|
|
5
|
+
class FailAndReturnAction
|
|
6
|
+
extend Switchyard::Action
|
|
7
|
+
|
|
8
|
+
promises :one, :two
|
|
9
|
+
|
|
10
|
+
executed do |ctx|
|
|
11
|
+
ctx.one = 1
|
|
12
|
+
# Have to set it in Context
|
|
13
|
+
ctx.two = nil
|
|
14
|
+
|
|
15
|
+
ctx.fail_and_return!('Something went wrong')
|
|
16
|
+
ctx.two = 2
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "returns immediately from executed block" do
|
|
21
|
+
result = FailAndReturnAction.execute
|
|
22
|
+
|
|
23
|
+
expect(result).to be_failure
|
|
24
|
+
expect(result.two).to be_nil
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
describe "accepts error_code option" do
|
|
29
|
+
class FailAndReturnWithErrorCodeAction
|
|
30
|
+
extend Switchyard::Action
|
|
31
|
+
|
|
32
|
+
promises :one, :two
|
|
33
|
+
|
|
34
|
+
executed do |ctx|
|
|
35
|
+
ctx.one = 1
|
|
36
|
+
# Have to set it in Context
|
|
37
|
+
ctx.two = nil
|
|
38
|
+
|
|
39
|
+
ctx.fail_and_return!('Something went wrong', :error_code => 401)
|
|
40
|
+
ctx.two = 2
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "returned context contains the error_code" do
|
|
45
|
+
result = FailAndReturnWithErrorCodeAction.execute
|
|
46
|
+
|
|
47
|
+
expect(result).to be_failure
|
|
48
|
+
expect(result.error_code).to eq 401
|
|
49
|
+
expect(result.two).to be_nil
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'test_doubles'
|
|
3
|
+
require 'stringio'
|
|
4
|
+
|
|
5
|
+
describe "Logs from organizer" do
|
|
6
|
+
def collects_log
|
|
7
|
+
original_logger = Switchyard::Configuration.logger
|
|
8
|
+
|
|
9
|
+
strio = StringIO.new
|
|
10
|
+
Switchyard::Configuration.logger = Logger.new(strio)
|
|
11
|
+
|
|
12
|
+
yield
|
|
13
|
+
|
|
14
|
+
Switchyard::Configuration.logger = original_logger
|
|
15
|
+
|
|
16
|
+
strio.string
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
context "when every action has expects or promises" do
|
|
20
|
+
subject(:log_message) do
|
|
21
|
+
collects_log do
|
|
22
|
+
TestDoubles::MakesTeaAndCappuccino
|
|
23
|
+
.call("black tea", "2% milk", "espresso coffee")
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "describes what organizer was invoked" do
|
|
28
|
+
organizer_log_message = "[Switchyard] - calling organizer " \
|
|
29
|
+
"<TestDoubles::MakesTeaAndCappuccino>"
|
|
30
|
+
expect(log_message).to include(organizer_log_message)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "describes the actions invoked" do
|
|
34
|
+
organizer_log_message = "[Switchyard] - executing " \
|
|
35
|
+
"<TestDoubles::MakesTeaWithMilkAction>"
|
|
36
|
+
expect(log_message).to include(organizer_log_message)
|
|
37
|
+
organizer_log_message = "[Switchyard] - executing " \
|
|
38
|
+
"<TestDoubles::MakesLatteAction>"
|
|
39
|
+
expect(log_message).to include(organizer_log_message)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "lists the keys in context before the actions are executed" do
|
|
43
|
+
organizer_log_message = "[Switchyard] - " \
|
|
44
|
+
"keys in context: :tea, :milk, :coffee"
|
|
45
|
+
expect(log_message).to include(organizer_log_message)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "lists the expects actions are expecting" do
|
|
49
|
+
organizer_log_message = "[Switchyard] - expects: :tea, :milk"
|
|
50
|
+
expect(log_message).to include(organizer_log_message)
|
|
51
|
+
organizer_log_message = "[Switchyard] - expects: :coffee, :milk"
|
|
52
|
+
expect(log_message).to include(organizer_log_message)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "lists the promises actions are promising" do
|
|
56
|
+
organizer_log_message = "[Switchyard] - promises: :milk_tea"
|
|
57
|
+
expect(log_message).to include(organizer_log_message)
|
|
58
|
+
organizer_log_message = "[Switchyard] - promises: :latte"
|
|
59
|
+
expect(log_message).to include(organizer_log_message)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "lists the keys in contect after the actions are executed" do
|
|
63
|
+
organizer_log_message = "[Switchyard] - keys in context: " \
|
|
64
|
+
":tea, :milk, :coffee, :milk_tea, :latte"
|
|
65
|
+
expect(log_message).to include(organizer_log_message)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
context "when NOT every action expects or promises" do
|
|
70
|
+
subject(:log_message) do
|
|
71
|
+
collects_log do
|
|
72
|
+
TestDoubles::MakesCappuccinoAddsTwo.call("2% milk", "espresso coffee")
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it "describes what organizer was invoked" do
|
|
77
|
+
organizer_log_message = "[Switchyard] - calling organizer " \
|
|
78
|
+
"<TestDoubles::MakesCappuccinoAddsTwo>"
|
|
79
|
+
expect(log_message).to include(organizer_log_message)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "does not list empty expects or promises" do
|
|
83
|
+
organizer_log_message = "[Switchyard] - expects:\n"
|
|
84
|
+
expect(log_message).not_to include(organizer_log_message)
|
|
85
|
+
organizer_log_message = "[Switchyard] - promises:\n"
|
|
86
|
+
expect(log_message).not_to include(organizer_log_message)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
context "when the context has failed" do
|
|
91
|
+
subject(:log_message) do
|
|
92
|
+
collects_log do
|
|
93
|
+
TestDoubles::MakesCappuccinoAddsTwoAndFails
|
|
94
|
+
.call("espresso coffee")
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it "logs it with a warning" do
|
|
99
|
+
organizer_log_message = "WARN -- : [Switchyard] - :-((( " \
|
|
100
|
+
"<TestDoubles::MakesLatteAction> has failed..."
|
|
101
|
+
expect(log_message).to include(organizer_log_message)
|
|
102
|
+
organizer_log_message = "WARN -- : [Switchyard] - context message: " \
|
|
103
|
+
"Can't make a latte from a milk that's very hot!"
|
|
104
|
+
expect(log_message).to include(organizer_log_message)
|
|
105
|
+
organizer_log_message = "[Switchyard] - :-((( " \
|
|
106
|
+
"<TestDoubles::AddsTwoAction> has failed..."
|
|
107
|
+
expect(log_message).not_to include(organizer_log_message)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
context "when the context has failed with rollback" do
|
|
112
|
+
subject(:log_message) do
|
|
113
|
+
collects_log do
|
|
114
|
+
TestDoubles::MakesCappuccinoAddsTwoAndFails
|
|
115
|
+
.call("espresso coffee", :super_hot)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it "logs it with a warning" do
|
|
120
|
+
organizer_log_message = "WARN -- : [Switchyard] - :-((( " \
|
|
121
|
+
"<TestDoubles::MakesLatteAction> has failed..."
|
|
122
|
+
expect(log_message).to include(organizer_log_message)
|
|
123
|
+
organizer_log_message = "WARN -- : [Switchyard] - context message: " \
|
|
124
|
+
"Can't make a latte from a milk that's super hot!"
|
|
125
|
+
expect(log_message).to include(organizer_log_message)
|
|
126
|
+
organizer_log_message = "[Switchyard] - :-((( " \
|
|
127
|
+
"<TestDoubles::AddsTwoAction> " \
|
|
128
|
+
"has failed..."
|
|
129
|
+
expect(log_message).not_to include(organizer_log_message)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
context "when the context is skipping the rest" do
|
|
134
|
+
subject(:log_message) do
|
|
135
|
+
collects_log do
|
|
136
|
+
TestDoubles::MakesCappuccinoSkipsAddsTwo.call("espresso coffee")
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it "logs it with a warning" do
|
|
141
|
+
organizer_log_message = "INFO -- : [Switchyard] - ;-) " \
|
|
142
|
+
"<TestDoubles::MakesLatteAction> has decided " \
|
|
143
|
+
"to skip the rest of the actions"
|
|
144
|
+
expect(log_message).to include(organizer_log_message)
|
|
145
|
+
organizer_log_message = "INFO -- : [Switchyard] - context message: " \
|
|
146
|
+
"Can't make a latte with a fatty milk like that!"
|
|
147
|
+
expect(log_message).to include(organizer_log_message)
|
|
148
|
+
organizer_log_message = "INFO -- : [Switchyard] - ;-) " \
|
|
149
|
+
"<TestDoubles::AddsTwoAction> has decided " \
|
|
150
|
+
"to skip the rest of the actions"
|
|
151
|
+
expect(log_message).not_to include(organizer_log_message)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require "test_doubles"
|
|
3
|
+
|
|
4
|
+
class TestsLocalizationAdapter
|
|
5
|
+
extend Switchyard::Organizer
|
|
6
|
+
|
|
7
|
+
def self.call(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
|
+
extend Switchyard::Action
|
|
18
|
+
|
|
19
|
+
expects :pass_or_fail, :message_or_key, :i18n_options
|
|
20
|
+
|
|
21
|
+
executed do |context|
|
|
22
|
+
if context.pass_or_fail == true
|
|
23
|
+
context.succeed!(context.message_or_key, context.i18n_options)
|
|
24
|
+
else
|
|
25
|
+
context.fail!(context.message_or_key, context.i18n_options)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def pass_with(message_or_key, i18n_options = {})
|
|
31
|
+
TestsLocalizationAdapter.call(true, message_or_key, i18n_options)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def fail_with(message_or_key, i18n_options = {})
|
|
35
|
+
TestsLocalizationAdapter.call(false, message_or_key, i18n_options)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
describe "Localization Adapter" do
|
|
39
|
+
before do
|
|
40
|
+
I18n.backend.store_translations(
|
|
41
|
+
:en,
|
|
42
|
+
:tests_localization_invocation_options_action =>
|
|
43
|
+
{
|
|
44
|
+
:switchyard => {
|
|
45
|
+
:failures => {
|
|
46
|
+
:some_failure_reason => "This has failed",
|
|
47
|
+
:failure_with_interpolation => "Failed with %{reason}"
|
|
48
|
+
},
|
|
49
|
+
:successes => {
|
|
50
|
+
:some_success_reason => "This has passed",
|
|
51
|
+
:success_with_interpolation => "Passed with %{reason}"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
describe "passing a simple string message" do
|
|
59
|
+
describe "by failing the context" do
|
|
60
|
+
it "returns the string" do
|
|
61
|
+
result = fail_with("string message")
|
|
62
|
+
|
|
63
|
+
expect(result).to be_failure
|
|
64
|
+
expect(result.message).to eq("string message")
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
describe "by passing the context" do
|
|
69
|
+
it "returns the string" do
|
|
70
|
+
result = pass_with("string message")
|
|
71
|
+
|
|
72
|
+
expect(result).to be_success
|
|
73
|
+
expect(result.message).to eq("string message")
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
describe "passing a Symbol" do
|
|
79
|
+
describe "by failing the context" do
|
|
80
|
+
it "performs a translation" do
|
|
81
|
+
result = fail_with(:some_failure_reason)
|
|
82
|
+
|
|
83
|
+
expect(result).to be_failure
|
|
84
|
+
expect(result.message).to eq("This has failed")
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
describe "by passing the contenxt" do
|
|
89
|
+
it "performs a translation" do
|
|
90
|
+
result = pass_with(:some_success_reason)
|
|
91
|
+
|
|
92
|
+
expect(result).to be_success
|
|
93
|
+
expect(result.message).to eq("This has passed")
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
describe "passing a Symbol with interpolation variables" do
|
|
99
|
+
describe "by failing the context" do
|
|
100
|
+
it "performs a translation with interpolation" do
|
|
101
|
+
result = fail_with(:failure_with_interpolation,
|
|
102
|
+
:reason => "bad account")
|
|
103
|
+
|
|
104
|
+
expect(result).to be_failure
|
|
105
|
+
expect(result.message).to eq("Failed with bad account")
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
describe "by passing the context" do
|
|
110
|
+
it "performs a translation with interpolation" do
|
|
111
|
+
result = pass_with(:success_with_interpolation,
|
|
112
|
+
:reason => "account in good standing")
|
|
113
|
+
|
|
114
|
+
expect(result).to be_success
|
|
115
|
+
expect(result.message).to eq("Passed with account in good standing")
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe Switchyard::Organizer do
|
|
4
|
+
class TestAddAliases
|
|
5
|
+
extend Switchyard::Organizer
|
|
6
|
+
|
|
7
|
+
def self.call(context = Switchyard::Context.make)
|
|
8
|
+
with(context).reduce(steps)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.steps
|
|
12
|
+
[
|
|
13
|
+
add_to_context(:my_message => "Hello There"),
|
|
14
|
+
# This will add the alias `:a_message` which points
|
|
15
|
+
# to the :my_message key's value
|
|
16
|
+
add_aliases(:my_message => :a_message),
|
|
17
|
+
TestDoubles::CapitalizeMessage
|
|
18
|
+
]
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "adds aliases to the context embedded in the series of actions" do
|
|
23
|
+
result = TestAddAliases.call
|
|
24
|
+
|
|
25
|
+
expect(result).to be_success
|
|
26
|
+
expect(result.final_message).to eq("HELLO THERE")
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe Switchyard::Organizer do
|
|
4
|
+
class TestAddToContext
|
|
5
|
+
extend Switchyard::Organizer
|
|
6
|
+
|
|
7
|
+
def self.call(context = Switchyard::Context.make)
|
|
8
|
+
with(context).reduce(steps)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.steps
|
|
12
|
+
[
|
|
13
|
+
# This will add the `:number` key to the context
|
|
14
|
+
# with the value of 0, so it's available for
|
|
15
|
+
# AddsOneAction
|
|
16
|
+
add_to_context(:number => 0),
|
|
17
|
+
TestDoubles::AddsOneAction,
|
|
18
|
+
add_to_context(:something => "hello")
|
|
19
|
+
]
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "adds items to the context on the fly" do
|
|
24
|
+
result = TestAddToContext.call
|
|
25
|
+
|
|
26
|
+
expect(result).to be_success
|
|
27
|
+
expect(result.number).to eq(1)
|
|
28
|
+
expect(result[:something]).to eq("hello")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "defines accessors for the added keys" do
|
|
32
|
+
result = TestAddToContext.call
|
|
33
|
+
|
|
34
|
+
expect(result.something).to eq("hello")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "raises when the added key conflicts with a Context method" do
|
|
38
|
+
organizer = Class.new do
|
|
39
|
+
extend Switchyard::Organizer
|
|
40
|
+
|
|
41
|
+
def self.call
|
|
42
|
+
reduce(steps)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.steps
|
|
46
|
+
[add_to_context(:message => "boom")]
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
expect { organizer.call }
|
|
51
|
+
.to raise_error(Switchyard::ReservedKeysInContextError,
|
|
52
|
+
/:message conflicts/)
|
|
53
|
+
end
|
|
54
|
+
end
|