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.
Files changed (129) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/project-build.yml +71 -0
  3. data/.gitignore +24 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +101 -0
  6. data/.solargraph.yml +11 -0
  7. data/Appraisals +7 -0
  8. data/CHANGELOG.md +303 -0
  9. data/CODE_OF_CONDUCT.md +22 -0
  10. data/Gemfile +4 -0
  11. data/LICENSE +22 -0
  12. data/README.md +1663 -0
  13. data/Rakefile +12 -0
  14. data/VERSION +1 -0
  15. data/audit/bench.rb +99 -0
  16. data/audit/verify_findings.rb +172 -0
  17. data/gemfiles/dry_inflector_0_2_1.gemfile +5 -0
  18. data/gemfiles/i18n_1_8_11.gemfile +5 -0
  19. data/lib/switchyard/action.rb +145 -0
  20. data/lib/switchyard/configuration.rb +36 -0
  21. data/lib/switchyard/context/key_verifier.rb +124 -0
  22. data/lib/switchyard/context.rb +208 -0
  23. data/lib/switchyard/deprecations.rb +26 -0
  24. data/lib/switchyard/errors.rb +9 -0
  25. data/lib/switchyard/functional/enum.rb +286 -0
  26. data/lib/switchyard/functional/maybe.rb +21 -0
  27. data/lib/switchyard/functional/monad.rb +77 -0
  28. data/lib/switchyard/functional/null.rb +88 -0
  29. data/lib/switchyard/functional/option.rb +100 -0
  30. data/lib/switchyard/functional/result.rb +129 -0
  31. data/lib/switchyard/functional/sequencer.rb +144 -0
  32. data/lib/switchyard/i18n/localization_adapter.rb +50 -0
  33. data/lib/switchyard/localization_adapter.rb +39 -0
  34. data/lib/switchyard/localization_map.rb +9 -0
  35. data/lib/switchyard/organizer/execute.rb +16 -0
  36. data/lib/switchyard/organizer/iterate.rb +30 -0
  37. data/lib/switchyard/organizer/reduce_case.rb +50 -0
  38. data/lib/switchyard/organizer/reduce_if.rb +19 -0
  39. data/lib/switchyard/organizer/reduce_if_else.rb +23 -0
  40. data/lib/switchyard/organizer/reduce_until.rb +22 -0
  41. data/lib/switchyard/organizer/reduce_while.rb +31 -0
  42. data/lib/switchyard/organizer/scoped_reducable.rb +15 -0
  43. data/lib/switchyard/organizer/with_callback.rb +28 -0
  44. data/lib/switchyard/organizer/with_reducer.rb +81 -0
  45. data/lib/switchyard/organizer/with_reducer_factory.rb +20 -0
  46. data/lib/switchyard/organizer/with_reducer_log_decorator.rb +111 -0
  47. data/lib/switchyard/organizer.rb +129 -0
  48. data/lib/switchyard/testing/context_factory.rb +48 -0
  49. data/lib/switchyard/testing.rb +3 -0
  50. data/lib/switchyard/version.rb +5 -0
  51. data/lib/switchyard.rb +36 -0
  52. data/resources/fail_actions.png +0 -0
  53. data/resources/light-service.png +0 -0
  54. data/resources/organizer_and_actions.png +0 -0
  55. data/resources/skip_actions.png +0 -0
  56. data/spec/acceptance/add_numbers_spec.rb +11 -0
  57. data/spec/acceptance/after_actions_spec.rb +87 -0
  58. data/spec/acceptance/around_each_spec.rb +19 -0
  59. data/spec/acceptance/before_actions_spec.rb +115 -0
  60. data/spec/acceptance/custom_log_from_organizer_spec.rb +61 -0
  61. data/spec/acceptance/deprecation_warnings_spec.rb +82 -0
  62. data/spec/acceptance/fail_spec.rb +52 -0
  63. data/spec/acceptance/log_from_organizer_spec.rb +154 -0
  64. data/spec/acceptance/message_localization_spec.rb +119 -0
  65. data/spec/acceptance/organizer/add_aliases_spec.rb +28 -0
  66. data/spec/acceptance/organizer/add_to_context_spec.rb +54 -0
  67. data/spec/acceptance/organizer/around_each_with_reduce_if_spec.rb +42 -0
  68. data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +90 -0
  69. data/spec/acceptance/organizer/execute_spec.rb +67 -0
  70. data/spec/acceptance/organizer/iterate_spec.rb +44 -0
  71. data/spec/acceptance/organizer/reduce_case_spec.rb +65 -0
  72. data/spec/acceptance/organizer/reduce_if_else_spec.rb +60 -0
  73. data/spec/acceptance/organizer/reduce_if_spec.rb +89 -0
  74. data/spec/acceptance/organizer/reduce_until_spec.rb +49 -0
  75. data/spec/acceptance/organizer/reduce_while_spec.rb +96 -0
  76. data/spec/acceptance/organizer/with_callback_spec.rb +113 -0
  77. data/spec/acceptance/organizer_entry_point_spec.rb +35 -0
  78. data/spec/acceptance/rollback_spec.rb +183 -0
  79. data/spec/acceptance/skip_all_remaining_spec.rb +139 -0
  80. data/spec/acceptance/testing/context_factory_spec.rb +54 -0
  81. data/spec/action_expected_keys_spec.rb +63 -0
  82. data/spec/action_expects_and_promises_spec.rb +97 -0
  83. data/spec/action_optional_expected_keys_spec.rb +107 -0
  84. data/spec/action_promised_keys_spec.rb +126 -0
  85. data/spec/action_spec.rb +97 -0
  86. data/spec/context/inspect_spec.rb +52 -0
  87. data/spec/context_spec.rb +301 -0
  88. data/spec/examples/amount_spec.rb +77 -0
  89. data/spec/examples/controller_spec.rb +63 -0
  90. data/spec/examples/validate_address_spec.rb +38 -0
  91. data/spec/i18n_localization_adapter_spec.rb +83 -0
  92. data/spec/lib/deterministic/class_mixin_spec.rb +24 -0
  93. data/spec/lib/deterministic/currify_spec.rb +90 -0
  94. data/spec/lib/deterministic/monad_axioms.rb +46 -0
  95. data/spec/lib/deterministic/monad_spec.rb +47 -0
  96. data/spec/lib/deterministic/null_spec.rb +65 -0
  97. data/spec/lib/deterministic/option_spec.rb +140 -0
  98. data/spec/lib/deterministic/result/failure_spec.rb +65 -0
  99. data/spec/lib/deterministic/result/result_map_spec.rb +155 -0
  100. data/spec/lib/deterministic/result/result_shared.rb +25 -0
  101. data/spec/lib/deterministic/result/success_spec.rb +41 -0
  102. data/spec/lib/deterministic/result_spec.rb +63 -0
  103. data/spec/lib/deterministic/sequencer_spec.rb +506 -0
  104. data/spec/lib/edge_cases_spec.rb +156 -0
  105. data/spec/lib/enum_spec.rb +114 -0
  106. data/spec/lib/native_pattern_matching_spec.rb +74 -0
  107. data/spec/localization_adapter_spec.rb +66 -0
  108. data/spec/organizer/with_reducer_spec.rb +56 -0
  109. data/spec/organizer_key_aliases_spec.rb +29 -0
  110. data/spec/organizer_spec.rb +115 -0
  111. data/spec/readme_spec.rb +45 -0
  112. data/spec/sample/calculates_order_tax_action_spec.rb +16 -0
  113. data/spec/sample/calculates_tax_spec.rb +30 -0
  114. data/spec/sample/looks_up_tax_percentage_action_spec.rb +55 -0
  115. data/spec/sample/provides_free_shipping_action_spec.rb +25 -0
  116. data/spec/sample/tax/calculates_order_tax_action.rb +10 -0
  117. data/spec/sample/tax/calculates_tax.rb +11 -0
  118. data/spec/sample/tax/looks_up_tax_percentage_action.rb +28 -0
  119. data/spec/sample/tax/provides_free_shipping_action.rb +11 -0
  120. data/spec/spec_helper.rb +32 -0
  121. data/spec/support.rb +1 -0
  122. data/spec/test_doubles.rb +656 -0
  123. data/spec/testing/context_factory/iterate_spec.rb +39 -0
  124. data/spec/testing/context_factory/reduce_if_spec.rb +40 -0
  125. data/spec/testing/context_factory/reduce_until_spec.rb +40 -0
  126. data/spec/testing/context_factory/with_callback_spec.rb +38 -0
  127. data/spec/testing/context_factory_spec.rb +76 -0
  128. data/switchyard.gemspec +35 -0
  129. metadata +351 -0
@@ -0,0 +1,113 @@
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ RSpec.describe Switchyard::Organizer do
5
+ describe 'a simple case with a single callback' do
6
+ it 'calls the actions defined with callback' do
7
+ result = TestDoubles::TestWithCallback.call
8
+
9
+ expect(result.counter).to eq(3)
10
+ expect(result.total).to eq(6)
11
+ end
12
+ end
13
+
14
+ describe 'a more complex example with nested callbacks' do
15
+ class TestWithNestedCallback
16
+ extend Switchyard::Organizer
17
+
18
+ def self.call(context = {})
19
+ with(context).reduce(actions)
20
+ end
21
+
22
+ def self.actions
23
+ [
24
+ SetUpNestedContextAction,
25
+ with_callback(IterateOuterCollectionAction,
26
+ [IncrementOuterCountAction,
27
+ with_callback(TestDoubles::IterateCollectionAction,
28
+ [TestDoubles::IncrementCountAction,
29
+ TestDoubles::AddToTotalAction])])
30
+ ]
31
+ end
32
+ end
33
+
34
+ class SetUpNestedContextAction
35
+ extend Switchyard::Action
36
+
37
+ promises :outer_numbers, :outer_counter,
38
+ :numbers, :counter, :total
39
+
40
+ executed do |ctx|
41
+ ctx.outer_numbers = [12, 17]
42
+ ctx.outer_counter = 0
43
+ ctx.numbers = [1, 2, 3]
44
+ ctx.counter = 0
45
+ ctx.total = 0
46
+ end
47
+ end
48
+
49
+ class IterateOuterCollectionAction
50
+ extend Switchyard::Action
51
+
52
+ expects :outer_numbers, :callback
53
+ promises :outer_number
54
+
55
+ executed do |ctx|
56
+ ctx.outer_numbers.each do |outer_number|
57
+ ctx.outer_number = outer_number
58
+ ctx.callback.call(ctx)
59
+ end
60
+ end
61
+ end
62
+
63
+ class IncrementOuterCountAction
64
+ extend Switchyard::Action
65
+
66
+ expects :outer_counter
67
+
68
+ executed do |ctx|
69
+ ctx.outer_counter = ctx.outer_counter + 1
70
+ end
71
+ end
72
+
73
+ it 'calls both the action and the nested callbacks' do
74
+ result = TestWithNestedCallback.call
75
+
76
+ expect(result.outer_counter).to eq(2)
77
+ # Counts and total are the duplicates of
78
+ # what you'll see in the simple spec,
79
+ # as the internal callback logic is called
80
+ # twice due to 2 items in the outer_numbers
81
+ # collection.
82
+ expect(result.counter).to eq(6)
83
+ expect(result.total).to eq(12)
84
+ end
85
+ end
86
+
87
+ describe 'with failed or skipped context' do
88
+ class TestWithFailureCallback
89
+ extend Switchyard::Organizer
90
+
91
+ def self.call(context = {})
92
+ with(context).reduce(actions)
93
+ end
94
+
95
+ def self.actions
96
+ [
97
+ TestDoubles::SetUpContextAction,
98
+ with_callback(TestDoubles::IterateCollectionAction,
99
+ [TestDoubles::IncrementCountAction,
100
+ TestDoubles::FailureAction])
101
+ ]
102
+ end
103
+ end
104
+
105
+ it 'will not process the routine' do
106
+ result = TestWithFailureCallback.call
107
+
108
+ expect(result).to be_failure
109
+ expect(result.counter).to eq(1)
110
+ expect(result.total).to eq(0)
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ describe "Organizer entry point" do
5
+ context "when the organizer entry method is not named `call`" do
6
+ it "works without emitting any warning" do
7
+ class OrganizerWithoutCallMethod
8
+ extend Switchyard::Organizer
9
+
10
+ def self.do_something
11
+ reduce([])
12
+ end
13
+ end
14
+
15
+ result = nil
16
+ expect { result = OrganizerWithoutCallMethod.do_something }
17
+ .not_to output.to_stdout
18
+ expect(result).to be_a_kind_of(Switchyard::Context)
19
+ end
20
+ end
21
+
22
+ context "when the organizer has the `call` method" do
23
+ it "works without emitting any warning" do
24
+ class OrganizerWithCallMethod
25
+ extend Switchyard::Organizer
26
+
27
+ def self.call
28
+ reduce([])
29
+ end
30
+ end
31
+
32
+ expect(OrganizerWithCallMethod.call).to be_a_kind_of(Switchyard::Context)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,183 @@
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ class RollbackOrganizer
5
+ extend Switchyard::Organizer
6
+
7
+ def self.call(number)
8
+ with(:number => number).reduce(
9
+ AddsOneWithRollbackAction,
10
+ TestDoubles::AddsTwoAction,
11
+ AddsThreeWithRollbackAction
12
+ )
13
+ end
14
+ end
15
+
16
+ class AddsOneWithRollbackAction
17
+ extend Switchyard::Action
18
+
19
+ expects :number
20
+ promises :number
21
+
22
+ executed do |context|
23
+ context.fail_with_rollback! if context.number.zero?
24
+
25
+ context.number += 1
26
+ end
27
+
28
+ rolled_back do |context|
29
+ context.number -= 1
30
+ end
31
+ end
32
+
33
+ class AddsThreeWithRollbackAction
34
+ extend Switchyard::Action
35
+
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 Switchyard::Organizer
51
+
52
+ def self.call(number)
53
+ with(:number => number).reduce(
54
+ TestDoubles::AddsOneAction,
55
+ TestDoubles::AddsTwoAction,
56
+ AddsThreeWithNoRollbackAction
57
+ )
58
+ end
59
+ end
60
+
61
+ class AddsThreeWithNoRollbackAction
62
+ extend Switchyard::Action
63
+
64
+ expects :number
65
+
66
+ executed do |context|
67
+ context.number = context.number + 3
68
+
69
+ context.fail_with_rollback!("I did not like this!")
70
+ end
71
+ end
72
+
73
+ class RollbackOrganizerWithMiddleRollback
74
+ extend Switchyard::Organizer
75
+
76
+ def self.call(number)
77
+ with(:number => number).reduce(
78
+ TestDoubles::AddsOneAction,
79
+ AddsTwoActionWithRollback,
80
+ TestDoubles::AddsThreeAction
81
+ )
82
+ end
83
+ end
84
+
85
+ class AddsTwoActionWithRollback
86
+ extend Switchyard::Action
87
+
88
+ expects :number
89
+
90
+ executed do |context|
91
+ context.number = context.number + 2
92
+
93
+ context.fail_with_rollback!("I did not like this a bit!")
94
+ end
95
+
96
+ rolled_back do |context|
97
+ context.number -= 2
98
+ end
99
+ end
100
+
101
+ class RollbackOrganizerWithDuplicatedAction
102
+ extend Switchyard::Organizer
103
+
104
+ def self.call(ctx)
105
+ with(ctx).reduce(
106
+ TracksRollbackAction,
107
+ FailsOnSecondRunWithRollbackAction,
108
+ TracksRollbackAction,
109
+ FailsOnSecondRunWithRollbackAction
110
+ )
111
+ end
112
+ end
113
+
114
+ class TracksRollbackAction
115
+ extend Switchyard::Action
116
+
117
+ executed do |context|
118
+ (context[:executed] ||= []) << :tracks
119
+ end
120
+
121
+ rolled_back do |context|
122
+ (context[:rolled_back] ||= []) << :tracks
123
+ end
124
+ end
125
+
126
+ class FailsOnSecondRunWithRollbackAction
127
+ extend Switchyard::Action
128
+
129
+ executed do |context|
130
+ (context[:executed] ||= []) << :fails
131
+ context.fail_with_rollback!("boom") if context[:executed].count(:fails) == 2
132
+ end
133
+
134
+ rolled_back do |context|
135
+ (context[:rolled_back] ||= []) << :fails
136
+ end
137
+ end
138
+
139
+ describe "Rolling back actions when there is a failure" do
140
+ it "Adds 1, 2, 3 to 1 and rolls back " do
141
+ result = RollbackOrganizer.call 1
142
+ number = result.fetch(:number)
143
+
144
+ expect(result).to be_failure
145
+ expect(result.message).to eq("I did not like this!")
146
+ expect(number).to eq(3)
147
+ end
148
+
149
+ it "won't error out when actions don't define rollback" do
150
+ result = RollbackOrganizerWithNoRollback.call 1
151
+ number = result.fetch(:number)
152
+
153
+ expect(result).to be_failure
154
+ expect(result.message).to eq("I did not like this!")
155
+ expect(number).to eq(7)
156
+ end
157
+
158
+ it "rolls back properly when triggered with an action in the middle" do
159
+ result = RollbackOrganizerWithMiddleRollback.call 1
160
+ number = result.fetch(:number)
161
+
162
+ expect(result).to be_failure
163
+ expect(result.message).to eq("I did not like this a bit!")
164
+ expect(number).to eq(2)
165
+ end
166
+
167
+ it "rolls back from the first action" do
168
+ result = RollbackOrganizer.call 0
169
+ number = result.fetch(:number)
170
+
171
+ expect(result).to be_failure
172
+ expect(number).to eq(-1)
173
+ end
174
+
175
+ it "rolls back every executed action when the same action appears twice" do
176
+ result = RollbackOrganizerWithDuplicatedAction.call({})
177
+
178
+ expect(result).to be_failure
179
+ expect(result[:executed]).to eq(%i[tracks fails tracks fails])
180
+ # tutte e 4 le azioni eseguite vengono compensate, in ordine inverso
181
+ expect(result[:rolled_back]).to eq(%i[fails tracks fails tracks])
182
+ end
183
+ end
@@ -0,0 +1,139 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe "skip_all_remaining!" do
4
+ context "with regular organizer" do
5
+ let(:organizer) do
6
+ Class.new do
7
+ extend Switchyard::Organizer
8
+
9
+ def self.call(ctx)
10
+ with(ctx).reduce(actions)
11
+ end
12
+
13
+ def self.actions
14
+ [
15
+ execute(->(c) { c[:first] = true }),
16
+ execute(lambda(&:skip_all_remaining!)),
17
+ execute(->(c) { c[:second] = true })
18
+ ]
19
+ end
20
+ end
21
+ end
22
+
23
+ it "skips all remaining actions" do
24
+ result = organizer.call(Switchyard::Context.make)
25
+
26
+ expect(result[:first]).to be true
27
+ expect(result[:second]).to be_nil
28
+ end
29
+ end
30
+
31
+ context "with an organizer with a reducer" do
32
+ let(:organizer) do
33
+ Class.new do
34
+ extend Switchyard::Organizer
35
+
36
+ def self.call(ctx)
37
+ with(ctx).reduce(actions)
38
+ end
39
+
40
+ def self.actions
41
+ [
42
+ reduce_if(
43
+ ->(_) { true },
44
+ [
45
+ execute(->(c) { c[:first_inside] = true }),
46
+ execute(lambda(&:skip_all_remaining!)),
47
+ execute(->(c) { c[:second_inside] = true })
48
+ ]
49
+ ),
50
+ execute(->(c) { c[:outside] = true })
51
+ ]
52
+ end
53
+ end
54
+ end
55
+
56
+ it "skips all remaining actions inside and outside the reducer" do
57
+ result = organizer.call(Switchyard::Context.make)
58
+
59
+ expect(result[:first_inside]).to be true
60
+ expect(result[:second_inside]).to be_nil
61
+ expect(result[:outside]).to be_nil
62
+ end
63
+ end
64
+
65
+ context "with a message" do
66
+ let(:organizer) do
67
+ Class.new do
68
+ extend Switchyard::Organizer
69
+
70
+ def self.call(ctx)
71
+ with(ctx).reduce(actions)
72
+ end
73
+
74
+ def self.actions
75
+ [
76
+ reduce_if(
77
+ ->(_) { true },
78
+ [
79
+ execute(->(c) { c.skip_all_remaining!("Skipping with message") })
80
+ ]
81
+ ),
82
+ execute(->(c) { c[:outside] = true })
83
+ ]
84
+ end
85
+ end
86
+ end
87
+
88
+ it "preserves the message when exiting scoped reducers" do
89
+ result = organizer.call(Switchyard::Context.make)
90
+
91
+ expect(result.message).to eq("Skipping with message")
92
+ expect(result[:outside]).to be_nil
93
+ end
94
+ end
95
+
96
+ context "with an organizer with nested reducers" do
97
+ let(:organizer) do
98
+ Class.new do
99
+ extend Switchyard::Organizer
100
+
101
+ def self.call(ctx)
102
+ with(ctx).reduce(actions)
103
+ end
104
+
105
+ def self.actions # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
106
+ [
107
+ reduce_if(
108
+ ->(_) { true },
109
+ [
110
+ iterate(:items, [
111
+ execute(->(c) {
112
+ c[:executed_items] ||= []
113
+ c[:executed_items] << c[:item]
114
+ }),
115
+ execute(->(c) { c.skip_all_remaining! if c[:item] == 2 }),
116
+ execute(->(c) {
117
+ c[:skipped_in_iterate] ||= []
118
+ c[:skipped_in_iterate] << c[:item]
119
+ })
120
+ ]),
121
+ execute(->(c) { c[:after_iterate_inside_if] = true })
122
+ ]
123
+ ),
124
+ execute(->(c) { c[:outside] = true })
125
+ ]
126
+ end
127
+ end
128
+ end
129
+
130
+ it "skips all remaining actions across all nested scopes" do
131
+ result = organizer.call(Switchyard::Context.make(:items => [1, 2, 3]))
132
+
133
+ expect(result[:executed_items]).to eq([1, 2])
134
+ expect(result[:skipped_in_iterate]).to eq([1])
135
+ expect(result[:after_iterate_inside_if]).to be_nil
136
+ expect(result[:outside]).to be_nil
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ class AdditionOrganizerContextFactory
5
+ def self.make_for(action, number)
6
+ number += 3 # You can add more logic to prepare your context
7
+
8
+ Switchyard::Testing::ContextFactory
9
+ .make_from(TestDoubles::AdditionOrganizer)
10
+ .for(action)
11
+ .with(number)
12
+ end
13
+ end
14
+
15
+ RSpec.describe TestDoubles::AddsThreeAction do
16
+ it 'creates a context for the action with ContextFactory wrapper' do
17
+ context =
18
+ AdditionOrganizerContextFactory
19
+ .make_for(TestDoubles::AddsThreeAction, 1)
20
+
21
+ expect(context.number).to eq(7)
22
+ end
23
+
24
+ it 'creates a context for the action using the ContextFactory' do
25
+ context =
26
+ Switchyard::Testing::ContextFactory
27
+ .make_from(TestDoubles::AdditionOrganizer)
28
+ .for(TestDoubles::AddsThreeAction)
29
+ .with(4) # Context is a "glorified" hash
30
+
31
+ expect(context.number).to eq(7)
32
+ end
33
+
34
+ it "works with multiple arguments passed to Organizer's call method" do
35
+ context = Switchyard::Testing::ContextFactory
36
+ .make_from(TestDoubles::ExtraArgumentAdditionOrganizer)
37
+ .for(described_class)
38
+ .with(4, 2)
39
+
40
+ expect(context.number).to eq(9)
41
+ end
42
+ end
43
+
44
+ RSpec.describe TestDoubles::AddsTwoAction do
45
+ it 'does not execute a callback entirely from a ContextFactory' do
46
+ context = Switchyard::Testing::ContextFactory
47
+ .make_from(TestDoubles::CallbackOrganizer)
48
+ .for(described_class)
49
+ .with(:number => 0)
50
+
51
+ # add 1, add 10, then stop before executing first add 2
52
+ expect(context.number).to eq(11)
53
+ end
54
+ end
@@ -0,0 +1,63 @@
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ describe ":expects macro" do
5
+ context "when expected keys are in the context" do
6
+ it "can access the keys as class methods" do
7
+ resulting_context = TestDoubles::MakesTeaWithMilkAction.execute(
8
+ :tea => "black",
9
+ :milk => "full cream",
10
+ :something => "else"
11
+ )
12
+ expect(resulting_context[:milk_tea]).to eq("black - full cream")
13
+ end
14
+ end
15
+
16
+ context "when an expected key is not in the context" do
17
+ it "raises an Switchyard::ExpectedKeysNotInContextError" do
18
+ exception_msg = "expected :milk to be in the context during " \
19
+ "TestDoubles::MakesTeaWithMilkAction"
20
+ expect do
21
+ TestDoubles::MakesTeaWithMilkAction.execute(:tea => "black")
22
+ end.to \
23
+ raise_error(Switchyard::ExpectedKeysNotInContextError, exception_msg)
24
+ end
25
+ end
26
+
27
+ context "when the `expects` macro is called multiple times" do
28
+ it "can collect expected keys " do
29
+ result = TestDoubles::MultipleExpectsAction.execute(
30
+ :tea => "black",
31
+ :milk => "full cream",
32
+ :chocolate => "dark chocolate"
33
+ )
34
+ expect(result[:milk_tea]).to \
35
+ eq("black - full cream - with dark chocolate")
36
+ end
37
+ end
38
+
39
+ context "when a reserved key is listed as an expected key" do
40
+ it "raises an error indicating a reserved key is expected" do
41
+ exception_msg = "promised or expected keys cannot be a reserved key: " \
42
+ "[:message]"
43
+ expect do
44
+ TestDoubles::MakesTeaExpectingReservedKey.execute(:tea => "black",
45
+ :message => "no no")
46
+ end.to \
47
+ raise_error(Switchyard::ReservedKeysInContextError, exception_msg)
48
+ end
49
+
50
+ it "raises an error indicating that multiple reserved keys are expected" do
51
+ exception_msg = "promised or expected keys cannot be a reserved key: " \
52
+ "[:message, :error_code, :current_action]"
53
+ expect do
54
+ TestDoubles::MakesTeaExpectingMultipleReservedKeys
55
+ .execute(:tea => "black",
56
+ :message => "no no",
57
+ :error_code => 1,
58
+ :current_action => "update")
59
+ end.to raise_error(Switchyard::ReservedKeysInContextError,
60
+ exception_msg)
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,97 @@
1
+ require 'spec_helper'
2
+
3
+ describe ":expects and :promises macros" do
4
+ describe "actions are backward compatible" do
5
+ class FooAction
6
+ extend Switchyard::Action
7
+
8
+ executed do |context|
9
+ baz = context.fetch :baz
10
+
11
+ bar = baz + 2
12
+ context[:bar] = bar
13
+ end
14
+ end
15
+ it "works without expects and promises" do
16
+ result = FooAction.execute(:baz => 3)
17
+ expect(result).to be_success
18
+ expect(result[:bar]).to eq(5)
19
+ end
20
+ end
21
+
22
+ context "when expected keys are not in context" do
23
+ class FooNoExpectedKeyAction
24
+ extend Switchyard::Action
25
+
26
+ expects :baz
27
+
28
+ executed do |context|
29
+ baz = context.fetch :baz
30
+
31
+ bar = baz + 2
32
+ context[:bar] = bar
33
+ end
34
+ end
35
+ it "throws an ExpectedKeysNotInContextError" do
36
+ # FooAction invoked with nothing in the context
37
+ expect { FooNoExpectedKeyAction.execute }.to \
38
+ raise_error(Switchyard::ExpectedKeysNotInContextError)
39
+ end
40
+ end
41
+
42
+ describe "expected keys" do
43
+ class FooWithReaderAction
44
+ extend Switchyard::Action
45
+
46
+ expects :baz
47
+
48
+ executed do |context|
49
+ # Notice how I use `context.baz` here
50
+ bar = context.baz + 2
51
+ context[:bar] = bar
52
+ end
53
+ end
54
+ it "can be accessed through a reader" do
55
+ result = FooWithReaderAction.execute(:baz => 3)
56
+ expect(result).to be_success
57
+ expect(result[:bar]).to eq(5)
58
+ end
59
+ end
60
+
61
+ context "when promised keys are not in context" do
62
+ class FooNoPromisedKeyAction
63
+ extend Switchyard::Action
64
+
65
+ expects :baz
66
+ promises :bar
67
+
68
+ executed do |context|
69
+ # I am not adding anything to the context
70
+ end
71
+ end
72
+ it "throws a PromisedKeysNotInContextError" do
73
+ # FooAction invoked with nothing placed in the context
74
+ expect { FooNoPromisedKeyAction.execute(:baz => 3) }.to \
75
+ raise_error(Switchyard::PromisedKeysNotInContextError)
76
+ end
77
+ end
78
+
79
+ describe "promised keys" do
80
+ class FooWithExpectsAndPromisesAction
81
+ extend Switchyard::Action
82
+
83
+ expects :baz
84
+ promises :bar
85
+
86
+ executed do |context|
87
+ # Notice how I use `context.bar` here
88
+ context.bar = context.baz + 2
89
+ end
90
+ end
91
+ it "puts the value through the accessor into the context" do
92
+ result = FooWithExpectsAndPromisesAction.execute(:baz => 3)
93
+ expect(result).to be_success
94
+ expect(result[:bar]).to eq(5)
95
+ end
96
+ end
97
+ end