trailblazer-operation 0.0.13 → 0.4.1

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 (60) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +5 -3
  3. data/CHANGES.md +100 -0
  4. data/Gemfile +4 -2
  5. data/Rakefile +8 -6
  6. data/lib/trailblazer/operation.rb +80 -13
  7. data/lib/trailblazer/operation/callable.rb +42 -0
  8. data/lib/trailblazer/operation/class_dependencies.rb +25 -0
  9. data/lib/trailblazer/operation/deprecated_macro.rb +19 -0
  10. data/lib/trailblazer/operation/heritage.rb +30 -0
  11. data/lib/trailblazer/operation/inject.rb +36 -0
  12. data/lib/trailblazer/operation/inspect.rb +79 -0
  13. data/lib/trailblazer/operation/public_call.rb +55 -0
  14. data/lib/trailblazer/operation/railway.rb +32 -0
  15. data/lib/trailblazer/operation/railway/fast_track.rb +13 -0
  16. data/lib/trailblazer/operation/railway/macaroni.rb +23 -0
  17. data/lib/trailblazer/operation/railway/normalizer.rb +58 -0
  18. data/lib/trailblazer/operation/railway/task_builder.rb +37 -0
  19. data/lib/trailblazer/operation/result.rb +6 -4
  20. data/lib/trailblazer/operation/trace.rb +46 -0
  21. data/lib/trailblazer/operation/version.rb +1 -1
  22. data/test/call_test.rb +27 -8
  23. data/test/callable_test.rb +147 -0
  24. data/test/class_dependencies_test.rb +16 -0
  25. data/test/docs/doormat_test.rb +189 -0
  26. data/test/docs/macaroni_test.rb +33 -0
  27. data/test/docs/operation_test.rb +23 -0
  28. data/test/docs/wiring_test.rb +559 -0
  29. data/test/dry_container_test.rb +4 -0
  30. data/test/fast_track_test.rb +197 -0
  31. data/test/gemfiles/Gemfile.ruby-2.0 +1 -2
  32. data/test/gemfiles/Gemfile.ruby-2.0.lock +40 -0
  33. data/test/inheritance_test.rb +1 -1
  34. data/test/inspect_test.rb +43 -0
  35. data/test/introspect_test.rb +51 -0
  36. data/test/macro_test.rb +60 -0
  37. data/test/operation_test.rb +94 -0
  38. data/test/result_test.rb +14 -8
  39. data/test/ruby-2.0.0/operation_test.rb +61 -0
  40. data/test/ruby-2.0.0/step_test.rb +136 -0
  41. data/test/skill_test.rb +66 -48
  42. data/test/step_test.rb +228 -0
  43. data/test/task_wrap_test.rb +97 -0
  44. data/test/test_helper.rb +37 -0
  45. data/test/trace_test.rb +57 -0
  46. data/test/wire_test.rb +113 -0
  47. data/test/wiring/defaults_test.rb +197 -0
  48. data/test/wiring/subprocess_test.rb +70 -0
  49. data/trailblazer-operation.gemspec +3 -5
  50. metadata +68 -37
  51. data/lib/trailblazer/operation/1.9.3/option.rb +0 -36
  52. data/lib/trailblazer/operation/generic.rb +0 -12
  53. data/lib/trailblazer/operation/option.rb +0 -54
  54. data/lib/trailblazer/operation/pipetree.rb +0 -142
  55. data/lib/trailblazer/operation/skill.rb +0 -41
  56. data/lib/trailblazer/skill.rb +0 -70
  57. data/test/2.0.0-pipetree_test.rb +0 -100
  58. data/test/2.1.0-pipetree_test.rb +0 -100
  59. data/test/operation_skill_test.rb +0 -89
  60. data/test/pipetree_test.rb +0 -185
@@ -0,0 +1,97 @@
1
+ require "test_helper"
2
+
3
+ require "trailblazer/operation/inject" # an optional feature.com.
4
+
5
+ class TaskWrapTest < Minitest::Spec
6
+ MyMacro = ->( (options, *args), *) do
7
+ options["MyMacro.contract"] = options[:contract]
8
+ [ Trailblazer::Activity::Right, [options, *args] ]
9
+ end
10
+
11
+ class Create < Trailblazer::Operation
12
+ step :model!
13
+ # step [ MyMacro, { name: "MyMacro" }, { dependencies: { "contract" => :external_maybe } }]
14
+ step(
15
+ task: MyMacro,
16
+ id: "MyMacro",
17
+
18
+ Trailblazer::Activity::DSL::Extension.new(
19
+ Trailblazer::Activity::TaskWrap::Merge.new(
20
+ Module.new do
21
+ extend Trailblazer::Activity::Path::Plan()
22
+
23
+ task Trailblazer::Operation::Wrap::Inject::ReverseMergeDefaults.new( contract: "MyDefaultContract" ),
24
+ id: "inject.my_default",
25
+ before: "task_wrap.call_task"
26
+ end
27
+ )
28
+ ) => true
29
+ )
30
+
31
+ def model!(options, **)
32
+ options["options.contract"] = options[:contract]
33
+ true
34
+ end
35
+ end
36
+
37
+ # it { Create.call("adsf", options={}, {}).inspect("MyMacro.contract", "options.contract").must_equal %{} }
38
+
39
+ def inspect_hash(hash, *keys)
40
+ Hash[ keys.collect { |key| [key, hash[key]] } ].inspect
41
+ end
42
+
43
+ #-
44
+ # default gets set by Injection.
45
+ it do
46
+ result = Create.call( {} )
47
+
48
+ inspect_hash(result, "options.contract", :contract, "MyMacro.contract").
49
+ must_equal %{{"options.contract"=>nil, :contract=>"MyDefaultContract", "MyMacro.contract"=>"MyDefaultContract"}}
50
+ end
51
+
52
+ # injected from outside, Injection skips.
53
+ it do
54
+ result = Create.call( { :contract=>"MyExternalContract" } )
55
+
56
+ inspect_hash(result, "options.contract", :contract, "MyMacro.contract").
57
+ must_equal %{{"options.contract"=>"MyExternalContract", :contract=>"MyExternalContract", "MyMacro.contract"=>"MyExternalContract"}}
58
+ end
59
+
60
+ #- Nested task_wraps should not override the outer.
61
+ AnotherMacro = ->( (options, *args), *) do
62
+ options["AnotherMacro.another_contract"] = options[:another_contract]
63
+ [ Trailblazer::Activity::Right, [options, *args] ]
64
+ end
65
+
66
+ class Update < Trailblazer::Operation
67
+ step(
68
+ task: ->( (options, *args), circuit_options ) {
69
+ _d, *o = Create.call( [ options, *args ], circuit_options )
70
+
71
+ [ Trailblazer::Activity::Right, *o ]
72
+ },
73
+ id: "Create"
74
+ )
75
+ step(
76
+ task: AnotherMacro,
77
+ id: "AnotherMacro",
78
+ Trailblazer::Activity::DSL::Extension.new(
79
+ Trailblazer::Activity::TaskWrap::Merge.new(
80
+ Module.new do
81
+ extend Trailblazer::Activity::Path::Plan()
82
+
83
+ task Trailblazer::Operation::Wrap::Inject::ReverseMergeDefaults.new( another_contract: "AnotherDefaultContract" ), id: "inject.my_default",
84
+ before: "task_wrap.call_task"
85
+ end
86
+ )
87
+ ) => true,
88
+ )
89
+ end
90
+
91
+ it do
92
+ result = Update.call( {} )
93
+
94
+ inspect_hash(result, "options.contract", :contract, "MyMacro.contract", "AnotherMacro.another_contract").
95
+ must_equal %{{"options.contract"=>nil, :contract=>"MyDefaultContract", "MyMacro.contract"=>"MyDefaultContract", "AnotherMacro.another_contract"=>"AnotherDefaultContract"}}
96
+ end
97
+ end
@@ -1,2 +1,39 @@
1
+ require "pp"
2
+
1
3
  require "minitest/autorun"
2
4
  require "trailblazer/operation"
5
+
6
+ Minitest::Spec::Activity = Trailblazer::Activity
7
+
8
+ module Test
9
+ # Create a step method in `klass` with the following body.
10
+ #
11
+ # def a(options, a_return:, data:, **)
12
+ # data << :a
13
+ #
14
+ # a_return
15
+ # end
16
+ def self.step(klass, *names)
17
+ names.each do |name|
18
+ method_def =
19
+ %{def #{name}(options, #{name}_return:, data:, **)
20
+ data << :#{name}
21
+
22
+ #{name}_return
23
+ end}
24
+
25
+ klass.class_eval(method_def)
26
+ end
27
+ end
28
+
29
+ # builder for PlusPoles
30
+ def self.plus_poles_for(mapping)
31
+ ary = mapping.collect { |evt, semantic| [Trailblazer:: Activity::Output(evt, semantic), semantic ] }
32
+
33
+ Trailblazer::Activity::Magnetic::DSL::PlusPoles.new.merge(::Hash[ary])
34
+ end
35
+ end
36
+
37
+ Minitest::Spec.class_eval do
38
+ Activity = Trailblazer::Activity
39
+ end
@@ -0,0 +1,57 @@
1
+ require "test_helper"
2
+
3
+ class TraceTest < Minitest::Spec
4
+ class B < Trailblazer::Operation
5
+ step ->(options, **) { options[:b] = true }, id: "B.task.b"
6
+ step ->(options, **) { options[:e] = true }, id: "B.task.e"
7
+ end
8
+
9
+ class Create < Trailblazer::Operation
10
+ step ->(options, a_return:, **) { options[:a] = a_return }, id: "Create.task.a"
11
+ step( {task: B, id: "MyNested"}, B.outputs[:success] => Track(:success) )
12
+ step ->(options, **) { options[:c] = true }, id: "Create.task.c"
13
+ step ->(options, params:, **) { params.any? }, id: "Create.task.params"
14
+ end
15
+ # raise Create["__task_wraps__"].inspect
16
+
17
+ it "allows using low-level Activity::Trace" do
18
+ operation = ->(*args) { puts "@@@@@ #{args.last.inspect}"; Create.__call__(*args) }
19
+
20
+ stack, _ = Trailblazer::Activity::Trace.(
21
+ Create,
22
+ [
23
+ { a_return: true, params: {} },
24
+ {}
25
+ ]
26
+ )
27
+
28
+ puts output = Trailblazer::Activity::Trace::Present.(stack)
29
+
30
+ output.gsub(/0x\w+/, "").gsub(/@.+_test/, "").must_equal %{`-- TraceTest::Create
31
+ |-- Start.default
32
+ |-- Create.task.a
33
+ |-- MyNested
34
+ | |-- Start.default
35
+ | |-- B.task.b
36
+ | |-- B.task.e
37
+ | `-- End.success
38
+ |-- Create.task.c
39
+ |-- Create.task.params
40
+ `-- End.failure}
41
+ end
42
+
43
+ it "Operation::trace" do
44
+ result = Create.trace({ params: { x: 1 }, a_return: true })
45
+ result.wtf.gsub(/0x\w+/, "").gsub(/@.+_test/, "").must_equal %{`-- TraceTest::Create
46
+ |-- Start.default
47
+ |-- Create.task.a
48
+ |-- MyNested
49
+ | |-- Start.default
50
+ | |-- B.task.b
51
+ | |-- B.task.e
52
+ | `-- End.success
53
+ |-- Create.task.c
54
+ |-- Create.task.params
55
+ `-- End.success}
56
+ end
57
+ end
@@ -0,0 +1,113 @@
1
+ # require "test_helper"
2
+ # # require "trailblazer/developer"
3
+
4
+ # class WireTest < Minitest::Spec
5
+ # Circuit = Trailblazer::Circuit
6
+ # ExceptionFromD = Class.new # custom signal
7
+
8
+ # D = ->((options, *args), *) do
9
+ # options["D"] = [ options["a"], options["b"], options["c"] ]
10
+
11
+ # signal = options["D_return"]
12
+ # [ signal, [ options, *args ] ]
13
+ # end
14
+
15
+ # #---
16
+ # #- step providing all :outputs manually.
17
+ # class Create < Trailblazer::Operation
18
+ # step ->(options, **) { options["a"] = 1 }
19
+ # step ->(options, **) { options["b"] = 2 }, name: "b"
20
+
21
+ # step( { task: D,
22
+ # outputs: { Circuit::Right => :success, Circuit::Left => :failure, ExceptionFromD => :exception }, # any outputs and their polarization, generic.
23
+ # id: :d,
24
+ # },
25
+ # :exception => MyEnd = End("End.ExceptionFromD_happened")
26
+ # )
27
+
28
+ # fail ->(options, **) { options["f"] = 4 }, id: "f"
29
+ # step ->(options, **) { options["c"] = 3 }, id: "c"
30
+ # end
31
+
32
+ # # myend ==> d
33
+ # it { Trailblazer::Operation::Inspect.(Create).gsub(/0x.+?wire_test.rb/, "").must_equal %{[>#<Proc::18 (lambda)>,>b,>d,<<f,>c]} }
34
+
35
+ # # normal flow as D sits on the Right track.
36
+ # it do
37
+ # result = Create.({}, "D_return" => Circuit::Right)
38
+
39
+ # result.inspect("a", "b", "c", "D", "f").must_equal %{<Result:true [1, 2, 3, [1, 2, nil], nil] >}
40
+ # result.event.must_equal Create.outputs.keys[1]
41
+ # end
42
+
43
+ # # ends on MyEnd, without hitting fail.
44
+ # it do
45
+ # result = Create.({}, "D_return" => ExceptionFromD)
46
+
47
+ # result.event.must_equal Create::MyEnd
48
+ # result.inspect("a", "b", "c", "D", "f").must_equal %{<Result:false [1, 2, nil, [1, 2, nil], nil] >}
49
+ # end
50
+
51
+ # # normal flow to left track.
52
+ # it do
53
+ # result = Create.({}, "D_return" => Circuit::Left)
54
+
55
+ # result.inspect("a", "b", "c", "D", "f").must_equal %{<Result:false [1, 2, nil, [1, 2, nil], 4] >}
56
+ # result.event.must_equal Create.outputs.keys[0]
57
+ # end
58
+
59
+ # #---
60
+ # #- step with Merge
61
+ # class CreateWithDefaults < Trailblazer::Operation
62
+ # step ->(options, **) { options["a"] = 1 }
63
+ # step ->(options, **) { options["b"] = 2 }, name: "b"
64
+
65
+ # step( { task: D,
66
+ # outputs: Merge( ExceptionFromD => { role: :exception } ), # any outputs and their polarization, generic.
67
+ # id: :d,
68
+ # },
69
+ # :exception => MyEnd = End("End.ExceptionFromD_happened")
70
+ # )
71
+
72
+ # fail ->(options, **) { options["f"] = 4 }, id: "f"
73
+ # step ->(options, **) { options["c"] = 3 }, id: "c"
74
+ # end
75
+
76
+ # # normal flow as D sits on the Right track.
77
+ # it do
78
+ # result = CreateWithDefaults.({}, "D_return" => Circuit::Right)
79
+
80
+ # result.inspect("a", "b", "c", "D", "f").must_equal %{<Result:true [1, 2, 3, [1, 2, nil], nil] >}
81
+ # result.event.must_equal CreateWithDefaults.outputs.keys[1]
82
+ # end
83
+
84
+ # # ends on MyEnd, without hitting fail.
85
+ # it do
86
+ # result = CreateWithDefaults.({}, "D_return" => ExceptionFromD)
87
+
88
+ # result.event.must_equal CreateWithDefaults::MyEnd
89
+ # result.inspect("a", "b", "c", "D", "f").must_equal %{<Result:false [1, 2, nil, [1, 2, nil], nil] >}
90
+ # end
91
+
92
+ # # normal flow to left track.
93
+ # it do
94
+ # result = CreateWithDefaults.({}, "D_return" => Circuit::Left)
95
+
96
+ # result.inspect("a", "b", "c", "D", "f").must_equal %{<Result:false [1, 2, nil, [1, 2, nil], 4] >}
97
+ # result.event.must_equal CreateWithDefaults.outputs.keys[0]
98
+ # end
99
+
100
+ # end
101
+
102
+ # class WireExceptionTest < Minitest::Spec
103
+ # # role in :outputs can't be connected because not in :connect_to.
104
+ # it do
105
+ # exception = assert_raises do
106
+ # class Create < Trailblazer::Operation
107
+ # step :a, outputs: { "some" => { role: :success } }, connect_to: { :not_existent => "End.success" }
108
+ # end
109
+ # end
110
+
111
+ # exception.message.must_equal %{Couldn't map output role :success for {:not_existent=>"End.success"}}
112
+ # end
113
+ # end
@@ -0,0 +1,197 @@
1
+ require "test_helper"
2
+
3
+ # class WireDefaultsTest < Minitest::Spec
4
+ # class C < Trailblazer::Operation
5
+ # # here, D has a step interface!
6
+ # D = ->(options, a:raise, b:raise, **) {
7
+ # options["D"] = [ a, b, options["c"] ]
8
+
9
+ # options["D_return"]
10
+ # }
11
+
12
+ # ExceptionFromD = Class.new(Circuit::Signal) # for steps, return value has to be subclass of Signal to be passed through as a signal and not a boolean.
13
+ # MyEnd = Class.new(Circuit::End)
14
+
15
+ # step ->(options, **) { options["a"] = 1 }, id: "a"
16
+ # step ->(options, **) { options["b"] = 2 }, id: "b"
17
+
18
+ # attach MyEnd.new(:myend), id: "End.myend"
19
+
20
+ # # step provides defaults:
21
+ # step D,
22
+ # outputs: Merge( ExceptionFromD => { role: :exception } ),
23
+ # connect_to: Merge( exception: "End.myend" ),
24
+ # id: "d"
25
+
26
+ # fail ->(options, **) { options["f"] = 4 }, id: "f"
27
+ # step ->(options, **) { options["c"] = 3 }, id: "c"
28
+ # end
29
+
30
+ # # normal flow as D sits on the Right track.
31
+ # it { C.( "D_return" => Circuit::Right).inspect("a", "b", "c", "D", "f").must_equal %{<Result:true [1, 2, 3, [1, 2, nil], nil] >} }
32
+ # # ends on MyEnd, without hitting fail.
33
+ # it { C.( "D_return" => C::ExceptionFromD).inspect("a", "b", "c", "D", "f").must_equal %{<Result:false [1, 2, nil, [1, 2, nil], nil] >} } # todo: HOW TO CHECK End instance?
34
+ # it { C.( "D_return" => Circuit::Left).inspect("a", "b", "c", "D", "f").must_equal %{<Result:false [1, 2, nil, [1, 2, nil], 4] >} } # todo: HOW TO CHECK End instance?
35
+ # it do
36
+ # step1 = C["__sequence__"][0].instructions[0].last[:node][0]
37
+ # step2 = C["__sequence__"][1].instructions[0].last[:node][0]
38
+ # step3 = C["__sequence__"][3].instructions[0].last[:node][0] # D
39
+ # step4 = C["__sequence__"][4].instructions[0].last[:node][0]
40
+ # step5 = C["__sequence__"][5].instructions[0].last[:node][0]
41
+
42
+ # require "trailblazer/activity/schema"
43
+
44
+ # Output = Trailblazer::Activity::Schema::Output
45
+
46
+ # steps = [
47
+ # [ [:success], step1, [Output.new(Circuit::Right, :success), Output.new(Circuit::Left, :failure)] ],
48
+ # [ [:success], step2, [Output.new(Circuit::Right, :success), Output.new(Circuit::Left, :failure)] ],
49
+
50
+ # [ [:success], step3, [Output.new(Circuit::Right, :success), Output.new(Circuit::Left, :failure), Output.new(C::ExceptionFromD, :exception)] ],
51
+ # [ [:exception], C::MyEnd.new(:myend), [] ],
52
+
53
+ # [ [:failure], step4, [Output.new(Circuit::Right, :failure), Output.new(Circuit::Left, :failure)] ],
54
+ # [ [:success], step5, [Output.new(Circuit::Right, :success), Output.new(Circuit::Left, :failure)] ],
55
+ # ]
56
+
57
+ # ends = [
58
+ # [ [:success], Trailblazer::Operation::Railway::End::Success.new(:success), [] ],
59
+ # [ [:failure], Trailblazer::Operation::Railway::End::Failure.new(:failure), [] ],
60
+ # ]
61
+
62
+
63
+ # graph = Trailblazer::Activity::Schema.bla(steps + ends)
64
+ # circuit = Trailblazer::Activity.new(graph)
65
+ # # pp schema
66
+
67
+ # C["__activity__"] = circuit # this is so wrong
68
+ # C.( "D_return" => Circuit::Right).inspect("a", "b", "c", "D", "f").must_equal %{<Result:true [1, 2, 3, [1, 2, nil], nil] >}
69
+
70
+ # end
71
+ # end
72
+
73
+ # # step :a
74
+ # # fail :b, connect_to: { Circuit::Right => "End.success" }
75
+ # # fail :c, connect_to: { Circuit::Right => "End.success" }
76
+
77
+ # Connect failure steps to right track, allowing to append steps after.
78
+ # @see https://github.com/trailblazer/trailblazer/issues/190#issuecomment-326992255
79
+ class WireDefaultsEarlyExitSuccessTest < Minitest::Spec
80
+ class Create < Trailblazer::Operation
81
+ step :a
82
+ fail :b, Output(:success) => Track(:success) #{}"End.success"
83
+ fail :c, Output(:success) => Track(:success)
84
+
85
+ Test.step(self, :a, :b, :c)
86
+ end
87
+
88
+ # a => true
89
+ it { Create.( a_return: true, data: []).inspect(:data).must_equal %{<Result:true [[:a]] >} }
90
+ # b => true
91
+ it { Create.( a_return: false, b_return: true, data: []).inspect(:data).must_equal %{<Result:true [[:a, :b]] >} }
92
+ # c => true
93
+ it { Create.( a_return: false, b_return: false, c_return: true, data: []).inspect(:data).must_equal %{<Result:true [[:a, :b, :c]] >} }
94
+ # a => b => c => false
95
+ it { Create.( a_return: false, b_return: false, c_return: false, data: []).inspect(:data).must_equal %{<Result:false [[:a, :b, :c]] >} }
96
+
97
+ # # require "trailblazer/developer"
98
+ # # it { Trailblazer::Developer::Client.push( operation: Create, name: "ushi" ) }
99
+
100
+
101
+ # #---
102
+ # # with => Track(:success), steps can still be added before End.success and they will be executed.
103
+ class Update < Create
104
+ pass :d
105
+
106
+ def d(options, data:, **)
107
+ data << :d
108
+ end
109
+ end
110
+
111
+ # a => true
112
+ it { Update.( a_return: true, data: []).inspect(:data).must_equal %{<Result:true [[:a, :d]] >} }
113
+ # b => true
114
+ it { Update.( a_return: false, b_return: true, data: []).inspect(:data).must_equal %{<Result:true [[:a, :b, :d]] >} }
115
+ # c => true
116
+ it { Update.( a_return: false, b_return: false, c_return: true, data: []).inspect(:data).must_equal %{<Result:true [[:a, :b, :c, :d]] >} }
117
+ # a => b => c => false
118
+ it { Update.( a_return: false, b_return: false, c_return: false, data: []).inspect(:data).must_equal %{<Result:false [[:a, :b, :c]] >} }
119
+
120
+ #---
121
+ # failure steps reference End.success and not just the polarization. This won't call #d in failure=>success case.
122
+ class Delete < Trailblazer::Operation
123
+ step :a
124
+ fail :b, Output(:success) => "End.success"
125
+ fail :c, Output(:success) => "End.success"
126
+ pass :d
127
+
128
+ Test.step(self, :a, :b, :c)
129
+
130
+ def d(options, data:, **)
131
+ data << :d
132
+ end
133
+ end
134
+
135
+ # a => true
136
+ it { Delete.( a_return: true, data: []).inspect(:data).must_equal %{<Result:true [[:a, :d]] >} }
137
+ # b => true
138
+ it { Delete.( a_return: false, b_return: true, data: []).inspect(:data).must_equal %{<Result:true [[:a, :b]] >} }
139
+ # c => true
140
+ it { Delete.( a_return: false, b_return: false, c_return: true, data: []).inspect(:data).must_equal %{<Result:true [[:a, :b, :c]] >} }
141
+ # a => b => c => false
142
+ it { Delete.( a_return: false, b_return: false, c_return: false, data: []).inspect(:data).must_equal %{<Result:false [[:a, :b, :c]] >} }
143
+
144
+ #---
145
+ # |----|
146
+ # a --> b c--d --> E.s
147
+ # |_____|_|_______ E.f
148
+ class Connect < Trailblazer::Operation
149
+ step :a
150
+ step :b, Output(:success) => "d"
151
+ step :c, magnetic_to: [] # otherwise :success will be an open input!
152
+ pass :d, id: "d"
153
+
154
+ Test.step(self, :a, :b, :c)
155
+
156
+ def d(options, data:, **)
157
+ data << :d
158
+ end
159
+ end
160
+
161
+ # it { puts Trailblazer::Activity::Magnetic::Introspect.seq( Connect.decompose.first ) }
162
+
163
+ # a => true
164
+ it { Connect.( a_return: true, b_return: true,data: []).inspect(:data).must_equal %{<Result:true [[:a, :b, :d]] >} }
165
+ # a => false
166
+ it { Connect.( a_return: false, data: []).inspect(:data).must_equal %{<Result:false [[:a]] >} }
167
+ # b => false
168
+ it { Connect.( a_return: true, b_return: false, data: []).inspect(:data).must_equal %{<Result:false [[:a, :b]] >} }
169
+
170
+ #---
171
+ # |---------|
172
+ # | V
173
+ # a c----d --
174
+ # |\ ^\ \
175
+ # | \ / V
176
+ # |__f____g----E.f
177
+ class Post < Trailblazer::Operation
178
+ step :a, Output(:success) => "d", id: "a"
179
+ fail :f, Output(:success) => "c"
180
+ step :c, magnetic_to: [], id: "c" # otherwise :success will be an open input!
181
+ fail :g
182
+ step :d, id: "d"
183
+
184
+ Test.step(self, :a, :f, :c, :g, :d)
185
+ end
186
+
187
+ pp Post["__sequence__"]
188
+
189
+ # a => true
190
+ it { Post.( a_return: true, d_return: true, data: []).inspect(:data).must_equal %{<Result:true [[:a, :d]] >} }
191
+ # a => false
192
+ it { Post.( a_return: false, f_return: false, g_return: nil, data: []).inspect(:data).must_equal %{<Result:false [[:a, :f, :g]] >} }
193
+ # a => false, f => true
194
+ it { Post.( a_return: false, f_return: true, c_return: true, d_return: true, data: []).inspect(:data).must_equal %{<Result:true [[:a, :f, :c, :d]] >} }
195
+ # a => false, f => true, c => false
196
+ it { Post.( a_return: false, f_return: true, c_return: false, g_return: true, data: []).inspect(:data).must_equal %{<Result:false [[:a, :f, :c, :g]] >} }
197
+ end