trailblazer-operation 0.0.13 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
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