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,107 @@
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ describe ":expects macro using defaults" do
5
+ context "when all expected keys are supplied" do
6
+ it "is expected to ignore default values" do
7
+ outcome = TestDoubles::AddsNumbersWithOptionalDefaults.execute(
8
+ :first_number => 3,
9
+ :second_number => 5,
10
+ :third_number => 7
11
+ )
12
+
13
+ expect(outcome.total).to eq 15
14
+ end
15
+ end
16
+
17
+ context "when defaults are supplied" do
18
+ it "is expected to use static values" do
19
+ outcome = TestDoubles::AddsNumbersWithOptionalDefaults.execute(
20
+ :first_number => 3,
21
+ :second_number => 7
22
+ )
23
+
24
+ expect(outcome.total).to eq 20
25
+ end
26
+
27
+ it "is expected to use dynamic values" do
28
+ outcome = TestDoubles::AddsNumbersWithOptionalDefaults.execute(
29
+ :first_number => 3,
30
+ :third_number => 5
31
+ )
32
+
33
+ expect(outcome.total).to eq 18
34
+ end
35
+
36
+ it "is expected to process defaults in their defined order" do
37
+ outcome = TestDoubles::AddsNumbersWithOptionalDefaults.execute(
38
+ :third_number => 5,
39
+ :first_number => 3
40
+ )
41
+
42
+ expect(outcome.total).to eq 18
43
+ end
44
+
45
+ it "is expected to use all defaults if required" do
46
+ outcome = TestDoubles::AddsNumbersWithOptionalDefaults.execute(
47
+ :first_number => 3
48
+ )
49
+
50
+ expect(outcome.total).to eq 23
51
+ end
52
+ end
53
+
54
+ context "when used within an organizer" do
55
+ it "is expected to process required defaults" do
56
+ outcome = TestDoubles::OrganizerWithActionsUsingDefaults.call
57
+
58
+ expect(outcome.total).to eq 20
59
+ end
60
+ end
61
+
62
+ context "when the expected key is satisfied through an alias" do
63
+ it "is expected to not apply the default" do
64
+ action = Class.new do
65
+ extend Switchyard::Action
66
+
67
+ expects :greeting, :default => "hello"
68
+ promises :greeted
69
+
70
+ executed do |ctx|
71
+ ctx.greeted = ctx.greeting
72
+ end
73
+ end
74
+
75
+ organizer = Class.new do
76
+ extend Switchyard::Organizer
77
+
78
+ aliases :salutation => :greeting
79
+ end
80
+
81
+ outcome = organizer.with(:salutation => "ciao").reduce([action])
82
+
83
+ expect(outcome.greeted).to eq "ciao"
84
+ end
85
+ end
86
+
87
+ context "when defaults are misconfigured" do
88
+ it "is expected to raise an exception" do
89
+ expect do
90
+ # Needs to be specified in the block
91
+ # as error is raised at define time
92
+ class AddsNumbersWithIncorrectDefaults
93
+ extend Switchyard::Action
94
+
95
+ expects :first, :default => 10 # This one is fine. Other two arent
96
+ expects :second, :defalut => ->(ctx) { ctx[:first] + 7 }
97
+ expects :third, :deafult => 10
98
+ promises :total
99
+
100
+ executed do |ctx|
101
+ ctx.total = ctx.first + ctx.second + ctx.third
102
+ end
103
+ end
104
+ end.to raise_error(Switchyard::UnusableExpectKeyDefaultError)
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,126 @@
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ describe ":promises macro" do
5
+ context "when the promised key is not in the context" do
6
+ it "raises an ArgumentError" do
7
+ module TestDoubles
8
+ class MakesCappuccinoAction1
9
+ extend Switchyard::Action
10
+
11
+ expects :coffee, :milk
12
+ promises :cappuccino
13
+ executed do |context|
14
+ context[:macchiato] = "#{context.coffee} - #{context.milk}"
15
+ end
16
+ end
17
+ end
18
+
19
+ exception_msg = "promised :cappuccino to be in the context during " \
20
+ "TestDoubles::MakesCappuccinoAction1"
21
+ expect do
22
+ TestDoubles::MakesCappuccinoAction1.execute(:coffee => "espresso",
23
+ :milk => "2%")
24
+ end.to \
25
+ raise_error(Switchyard::PromisedKeysNotInContextError, exception_msg)
26
+ end
27
+
28
+ it "can fail the context without fulfilling its promise" do
29
+ module TestDoubles
30
+ class MakesCappuccinoAction2
31
+ extend Switchyard::Action
32
+
33
+ expects :coffee, :milk
34
+ promises :cappuccino
35
+ executed do |context|
36
+ context.fail!("Sorry, something bad has happened.")
37
+ end
38
+ end
39
+ end
40
+
41
+ result_context = TestDoubles::MakesCappuccinoAction2
42
+ .execute(:coffee => "espresso",
43
+ :milk => "2%")
44
+
45
+ expect(result_context).to be_failure
46
+ expect(result_context.keys).not_to include(:cappuccino)
47
+ end
48
+ end
49
+
50
+ context "when the promised key is in the context" do
51
+ it "can be set with an actual value" do
52
+ module TestDoubles
53
+ class MakesCappuccinoAction3
54
+ extend Switchyard::Action
55
+
56
+ expects :coffee, :milk
57
+ promises :cappuccino
58
+ executed do |context|
59
+ context.cappuccino = "#{context.coffee} - with #{context.milk} milk"
60
+ context.cappuccino += " hot"
61
+ end
62
+ end
63
+ end
64
+
65
+ result_context = TestDoubles::MakesCappuccinoAction3
66
+ .execute(:coffee => "espresso",
67
+ :milk => "2%")
68
+
69
+ expect(result_context).to be_success
70
+ expect(result_context.cappuccino).to eq("espresso - with 2% milk hot")
71
+ end
72
+
73
+ it "can be set with nil" do
74
+ module TestDoubles
75
+ class MakesCappuccinoAction4
76
+ extend Switchyard::Action
77
+
78
+ expects :coffee, :milk
79
+ promises :cappuccino
80
+ executed do |context|
81
+ context.cappuccino = nil
82
+ end
83
+ end
84
+ end
85
+ result_context = TestDoubles::MakesCappuccinoAction4
86
+ .execute(:coffee => "espresso",
87
+ :milk => "2%")
88
+
89
+ expect(result_context).to be_success
90
+ expect(result_context[:cappuccino]).to be_nil
91
+ end
92
+ end
93
+
94
+ context "when a reserved key is listed as a promised key" do
95
+ it "raises error indicating a reserved key has been promised" do
96
+ exception_msg = "promised or expected keys cannot be a reserved key: " \
97
+ "[:message]"
98
+ expect do
99
+ TestDoubles::MakesTeaPromisingReservedKey.execute(:tea => "black")
100
+ end.to \
101
+ raise_error(Switchyard::ReservedKeysInContextError, exception_msg)
102
+ end
103
+
104
+ it "raises error indicating multiple reserved keys have been promised" do
105
+ exception_msg = "promised or expected keys cannot be a reserved key: " \
106
+ "[:message, :error_code, :current_action]"
107
+ expect do
108
+ ctx = { :tea => "black" }
109
+ TestDoubles::MakesTeaPromisingMultipleReservedKeys.execute(ctx)
110
+ end.to \
111
+ raise_error(Switchyard::ReservedKeysInContextError, exception_msg)
112
+ end
113
+ end
114
+
115
+ context "when the `promised` macro is called multiple times" do
116
+ it "collects promised keys " do
117
+ result = TestDoubles::MultiplePromisesAction
118
+ .execute(:coffee => "espresso", :milk => "2%")
119
+
120
+ expect(result.cappuccino).to \
121
+ eq("Cappucino needs espresso and a little milk")
122
+ expect(result.latte).to \
123
+ eq("Latte needs espresso and a lot of milk")
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,97 @@
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ describe Switchyard::Action do
5
+ let(:context) { Switchyard::Context.make }
6
+
7
+ context "when the action context has failure" do
8
+ it "returns immediately" do
9
+ context.fail!("an error")
10
+
11
+ TestDoubles::AddsTwoActionWithFetch.execute(context)
12
+
13
+ expect(context.to_hash.keys).to be_empty
14
+ end
15
+
16
+ it "returns the failure message in the context" do
17
+ context.fail!("an error")
18
+
19
+ returned_context = TestDoubles::AddsTwoActionWithFetch.execute(context)
20
+
21
+ expect(returned_context.message).to eq("an error")
22
+ end
23
+ end
24
+
25
+ context "when the action has an explicit success message" do
26
+ it "returns the success message in the context" do
27
+ context.succeed!("successful")
28
+
29
+ returned_context = TestDoubles::AddsTwoActionWithFetch.execute(context)
30
+
31
+ expect(returned_context.message).to eq("successful")
32
+ end
33
+ end
34
+
35
+ context "when the action context does not have failure" do
36
+ it "executes the block" do
37
+ TestDoubles::AddsTwoActionWithFetch.execute(context)
38
+
39
+ expect(context.to_hash.keys).to eq [:number]
40
+ expect(context.fetch(:number)).to eq(2)
41
+ end
42
+ end
43
+
44
+ context "when the action context skips all" do
45
+ it "returns immediately" do
46
+ context.skip_remaining!
47
+
48
+ TestDoubles::AddsTwoActionWithFetch.execute(context)
49
+
50
+ expect(context.to_hash.keys).to be_empty
51
+ end
52
+
53
+ it "does not execute skipped actions" do
54
+ TestDoubles::AddsTwoActionWithFetch.execute(context)
55
+ expect(context.to_hash).to eq(:number => 2)
56
+
57
+ context.skip_remaining!
58
+
59
+ TestDoubles::AddsTwoActionWithFetch.execute(context)
60
+ # Since the action was skipped, the number remains 2
61
+ expect(context.to_hash).to eq(:number => 2)
62
+ end
63
+ end
64
+
65
+ it "returns the context" do
66
+ result = TestDoubles::AddsTwoActionWithFetch.execute(context)
67
+
68
+ expect(result.to_hash).to eq(:number => 2)
69
+ end
70
+
71
+ context "when called directly" do
72
+ it "is expected to not be organized" do
73
+ result = TestDoubles::AddsTwoActionWithFetch.execute(context)
74
+
75
+ expect(result.organized_by).to be_nil
76
+ end
77
+ end
78
+
79
+ context "when invoked with hash" do
80
+ it "creates Switchyard::Context implicitly" do
81
+ ctx = { :some_key => "some value" }
82
+ result = TestDoubles::AddsTwoActionWithFetch.execute(ctx)
83
+
84
+ expect(result).to be_success
85
+ expect(result.keys).to eq(%i[some_key number])
86
+ end
87
+ end
88
+
89
+ context "when invoked without arguments" do
90
+ it "creates Switchyard::Context implicitly" do
91
+ result = TestDoubles::AddsTwoActionWithFetch.execute
92
+
93
+ expect(result).to be_success
94
+ expect(result.keys).to eq([:number])
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ RSpec.describe Switchyard::Context do
5
+ subject(:context) { Switchyard::Context.make }
6
+
7
+ describe 'to_s' do
8
+ it 'prints the context hash' do
9
+ expect(context.to_s).to eq('{}')
10
+ end
11
+ end
12
+
13
+ describe '#inspect' do
14
+ it 'inspects the hash with all the fields' do
15
+ inspected_context =
16
+ "Switchyard::Context({}, success: true, message: '', error_code: nil, " \
17
+ "skip_remaining: false, skip_all_remaining: false, aliases: {})"
18
+
19
+ expect(context.inspect).to eq(inspected_context)
20
+ end
21
+
22
+ it 'prints the error message' do
23
+ context.fail!('There was an error')
24
+
25
+ inspected_context =
26
+ "Switchyard::Context({}, success: false, message: 'There was an error', " \
27
+ "error_code: nil, skip_remaining: false, skip_all_remaining: false, aliases: {})"
28
+
29
+ expect(context.inspect).to eq(inspected_context)
30
+ end
31
+
32
+ it 'prints skip_remaining' do
33
+ context.skip_remaining!('No need to process')
34
+
35
+ inspected_context =
36
+ "Switchyard::Context({}, success: true, message: 'No need to process', " \
37
+ "error_code: nil, skip_remaining: true, skip_all_remaining: false, aliases: {})"
38
+
39
+ expect(context.inspect).to eq(inspected_context)
40
+ end
41
+
42
+ it 'prints skip_all_remaining' do
43
+ context.skip_all_remaining!('Nothing else to process')
44
+
45
+ inspected_context =
46
+ "Switchyard::Context({}, success: true, message: 'Nothing else to process', " \
47
+ "error_code: nil, skip_remaining: false, skip_all_remaining: true, aliases: {})"
48
+
49
+ expect(context.inspect).to eq(inspected_context)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,301 @@
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ RSpec.describe Switchyard::Context do
5
+ let(:context) { Switchyard::Context.make }
6
+
7
+ describe "can be made" do
8
+ context "with no arguments" do
9
+ subject { Switchyard::Context.make }
10
+ it { is_expected.to be_success }
11
+ specify "message is empty string" do
12
+ expect(context.message).to be_empty
13
+ end
14
+ end
15
+
16
+ context "with a hash" do
17
+ it "has the hash values" do
18
+ context = Switchyard::Context.make(:one => 1)
19
+
20
+ expect(context[:one]).to eq(1)
21
+ end
22
+ end
23
+
24
+ context "with FAILURE" do
25
+ it "is failed" do
26
+ outcome = Switchyard::Result::Failure(:message => '', :error => nil)
27
+ context = Switchyard::Context.new({}, outcome)
28
+
29
+ expect(context).to be_failure
30
+ end
31
+ end
32
+ end
33
+
34
+ describe "can't be made" do
35
+ specify "with invalid parameters" do
36
+ expect { Switchyard::Context.make([]) }.to raise_error(ArgumentError)
37
+ end
38
+ end
39
+
40
+ it "can be asked for success?" do
41
+ outcome = Switchyard::Result::Success(:message => '', :error => nil)
42
+ context = Switchyard::Context.new({}, outcome)
43
+
44
+ expect(context).to be_success
45
+ end
46
+
47
+ it "can be asked for failure?" do
48
+ outcome = Switchyard::Result::Failure(:message => '', :error => nil)
49
+ context = Switchyard::Context.new({}, outcome)
50
+
51
+ expect(context).to be_failure
52
+ end
53
+
54
+ it "can be asked for skip_remaining?" do
55
+ context.skip_remaining!
56
+
57
+ expect(context.skip_remaining?).to be_truthy
58
+ end
59
+
60
+ it "can be pushed into a SUCCESS state" do
61
+ context.succeed!("a happy end")
62
+
63
+ expect(context).to be_success
64
+ end
65
+
66
+ it "can be pushed into a SUCCESS state without a message" do
67
+ context.succeed!
68
+
69
+ expect(context).to be_success
70
+ expect(context.message).to be_nil
71
+ end
72
+
73
+ it "can be pushed into a FAILURE state without a message" do
74
+ context.fail!
75
+
76
+ expect(context).to be_failure
77
+ expect(context.message).to be_nil
78
+ end
79
+
80
+ it "can be pushed into a FAILURE state with a message" do
81
+ context.fail!("a sad end")
82
+
83
+ expect(context).to be_failure
84
+ end
85
+
86
+ it "can be pushed into a FAILURE state with a message in an options hash" do
87
+ context.fail!("a sad end")
88
+
89
+ expect(context).to be_failure
90
+ expect(context.message).to eq("a sad end")
91
+ expect(context.error_code).to be_nil
92
+ end
93
+
94
+ it "can be pushed into a FAILURE state with an error code in options hash" do
95
+ context.fail!("a sad end", 10_005)
96
+
97
+ expect(context).to be_failure
98
+ expect(context.message).to eq("a sad end")
99
+ expect(context.error_code).to eq(10_005)
100
+ end
101
+
102
+ it "uses localization adapter to translate failure message" do
103
+ action_class = TestDoubles::AnAction
104
+ expect(Switchyard::Configuration.localization_adapter)
105
+ .to receive(:failure)
106
+ .with(:failure_reason, action_class, {})
107
+ .and_return("message")
108
+
109
+ context = Switchyard::Context.make
110
+ context.current_action = action_class
111
+ context.fail!(:failure_reason)
112
+
113
+ expect(context).to be_failure
114
+ expect(context.message).to eq("message")
115
+ end
116
+
117
+ it "uses localization adapter to translate success message" do
118
+ action_class = TestDoubles::AnAction
119
+ expect(Switchyard::Configuration.localization_adapter)
120
+ .to receive(:success)
121
+ .with(:action_passed, action_class, {})
122
+ .and_return("message")
123
+
124
+ context = Switchyard::Context.make
125
+ context.current_action = action_class
126
+ context.succeed!(:action_passed)
127
+
128
+ expect(context).to be_success
129
+ expect(context.message).to eq("message")
130
+ end
131
+
132
+ it "can set a flag to skip all subsequent actions" do
133
+ context.skip_remaining!
134
+
135
+ expect(context).to be_skip_remaining
136
+ end
137
+
138
+ context "stopping additional processing in an action" do
139
+ it "flags processing to stop on failure" do
140
+ context.fail!("on purpose")
141
+ expect(context.stop_processing?).to be_truthy
142
+ end
143
+
144
+ it "flags processing to stop when remaining actions should be skipped" do
145
+ context.skip_remaining!
146
+ expect(context.stop_processing?).to be_truthy
147
+ end
148
+ end
149
+
150
+ it "can fail with FailWithRollBackError" do
151
+ expect { context.fail_with_rollback!("roll me back") }.to \
152
+ raise_error(Switchyard::FailWithRollbackError)
153
+ end
154
+
155
+ it "exptected outcome reader get Success and message empty and error nil" do
156
+ outcome = Switchyard::Result::Success(:message => "", :error => nil)
157
+ expect(context.outcome).to eq(outcome)
158
+ end
159
+
160
+ it "can contain false values" do
161
+ context = Switchyard::Context.make(:foo => false)
162
+ expect(context[:foo]).to eq false
163
+ end
164
+
165
+ it "allows a default value for #fetch" do
166
+ expect(context.fetch(:madeup, :default)).to eq(:default)
167
+ end
168
+
169
+ it "allows a default block value for #fetch" do
170
+ # la forma a blocco e' esattamente cio' che il test verifica
171
+ expect(context.fetch(:madeup) { :default }).to eq(:default) # rubocop:disable Style/RedundantFetchBlock
172
+ end
173
+
174
+ describe "#define_accessor_methods_for_keys" do
175
+ it "raises when a key conflicts with an existing Hash/Context method" do
176
+ expect { context.define_accessor_methods_for_keys([:size]) }
177
+ .to raise_error(Switchyard::ReservedKeysInContextError, /:size conflicts/)
178
+ end
179
+
180
+ it "does not raise when re-defining accessors for the same key" do
181
+ ctx = Switchyard::Context.make(:number => 1)
182
+ ctx.define_accessor_methods_for_keys([:number])
183
+
184
+ expect { ctx.define_accessor_methods_for_keys([:number]) }.not_to raise_error
185
+ expect(ctx.number).to eq(1)
186
+ end
187
+ end
188
+
189
+ describe "reserved keys" do
190
+ it "rejects infrastructure keys in expects/promises" do
191
+ action = Class.new do
192
+ extend Switchyard::Action
193
+
194
+ expects :_before_actions
195
+ executed { |_ctx| } # rubocop:disable Lint/EmptyBlock
196
+ end
197
+
198
+ expect { action.execute(:_before_actions => []) }
199
+ .to raise_error(Switchyard::ReservedKeysInContextError)
200
+ end
201
+
202
+ it "rejects :organized_by in expects/promises" do
203
+ action = Class.new do
204
+ extend Switchyard::Action
205
+
206
+ expects :organized_by
207
+ executed { |_ctx| } # rubocop:disable Lint/EmptyBlock
208
+ end
209
+
210
+ expect { action.execute(:organized_by => Object) }
211
+ .to raise_error(Switchyard::ReservedKeysInContextError)
212
+ end
213
+ end
214
+
215
+ describe "#fail! does not mutate the caller's options hash" do
216
+ it "leaves :error_code in the original hash" do
217
+ options = { :error_code => 500 }
218
+ context.fail!("boom", options)
219
+
220
+ expect(options).to eq(:error_code => 500)
221
+ expect(context.error_code).to eq(500)
222
+ end
223
+ end
224
+
225
+ describe "#reset_skip_remaining!" do
226
+ it "clears the flag but keeps the outcome and its message" do
227
+ context.skip_remaining!("No need to process")
228
+ context.reset_skip_remaining!
229
+
230
+ expect(context.skip_remaining?).to be(false)
231
+ expect(context).to be_success
232
+ expect(context.message).to eq("No need to process")
233
+ end
234
+ end
235
+
236
+ describe "#fetch honours the Hash#fetch contract" do
237
+ it "raises KeyError for a missing key without default" do
238
+ expect { context.fetch(:madeup) }.to raise_error(KeyError)
239
+ end
240
+
241
+ it "does not write to the context when a default is used" do
242
+ context.fetch(:madeup, :default)
243
+ context.fetch(:another_madeup) { :default } # rubocop:disable Style/RedundantFetchBlock
244
+
245
+ expect(context.to_h).not_to have_key(:madeup)
246
+ expect(context.to_h).not_to have_key(:another_madeup)
247
+ end
248
+
249
+ it "returns existing falsy values instead of the default" do
250
+ context = Switchyard::Context.make(:flag => false)
251
+
252
+ expect(context.fetch(:flag, true)).to eq(false)
253
+ end
254
+ end
255
+
256
+ context "when aliases are included via .make" do
257
+ let(:context) do
258
+ Switchyard::Context.make(
259
+ :foo => "foobar",
260
+ :foo2 => false,
261
+ :_aliases => aliases
262
+ )
263
+ end
264
+ let(:aliases) { { :foo => :bar, :foo2 => :bar2 } }
265
+
266
+ it "contains the aliases" do
267
+ expect(context.aliases).to eq(aliases)
268
+ expect(context).to include(:foo, :bar)
269
+ end
270
+
271
+ it "returns the correct values for #[] and #fetch" do
272
+ expect(context[:bar]).to eq context[:foo]
273
+ expect(context.fetch(:bar)).to eq context[:foo]
274
+ end
275
+
276
+ it "can contain false values" do
277
+ expect(context[:bar2]).to eq false
278
+ end
279
+
280
+ it "resolves aliases on writes too, symmetrically with reads" do
281
+ context[:bar] = "updated"
282
+
283
+ expect(context[:bar]).to eq("updated")
284
+ expect(context[:foo]).to eq("updated")
285
+ # nessuna chiave duplicata: l'alias e' un nome alternativo, non una copia
286
+ expect(context.to_h).not_to have_key(:bar)
287
+ end
288
+
289
+ it "resolves aliases in #key? and friends" do
290
+ expect(context.key?(:bar)).to be(true)
291
+ expect(context.has_key?(:bar)).to be(true) # rubocop:disable Style/PreferredHashMethods
292
+ expect(context.member?(:bar)).to be(true)
293
+ expect(context.include?(:bar)).to be(true)
294
+ expect(context.key?(:madeup)).to be(false)
295
+ end
296
+
297
+ it "resolves aliases in #fetch with defaults" do
298
+ expect(context.fetch(:bar, :default)).to eq("foobar")
299
+ end
300
+ end
301
+ end