trailblazer-macro 2.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,25 @@
1
+ module Test
2
+ module Methods
3
+ def find_model(ctx, seq:, **)
4
+ seq << :find_model
5
+ end
6
+
7
+ def update(ctx, seq:, **)
8
+ seq << :update
9
+ end
10
+
11
+ def notify(ctx, seq:, **)
12
+ seq << :notify
13
+ end
14
+
15
+ def rehash(ctx, seq:, rehash_raise:false, **)
16
+ seq << :rehash
17
+ raise if rehash_raise
18
+ true
19
+ end
20
+
21
+ def log_error(ctx, seq:, **)
22
+ seq << :log_error
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,54 @@
1
+ require "test_helper"
2
+
3
+ class ModelTest < Minitest::Spec
4
+ Song = Struct.new(:id) do
5
+ def self.find(id); new(id) end
6
+ def self.find_by(id:nil); id.nil? ? nil : new(id) end
7
+ end
8
+
9
+ #---
10
+ # use Model semantics, no customizations.
11
+ class Create < Trailblazer::Operation
12
+ step Model( Song, :new )
13
+ end
14
+
15
+ # :new new.
16
+ it { Create.(params: {})[:model].inspect.must_equal %{#<struct ModelTest::Song id=nil>} }
17
+
18
+ class Update < Create
19
+ step Model( Song, :find ), override: true
20
+ end
21
+
22
+ #---
23
+ #- inheritance
24
+
25
+ # :find it
26
+ it { Update.(params: { id: 1 })[:model].inspect.must_equal %{#<struct ModelTest::Song id=1>} }
27
+
28
+ # inherited inspect is ok
29
+ it { Trailblazer::Operation::Inspect.(Update).must_equal %{[>model.build]} }
30
+
31
+ #---
32
+ # :find_by, exceptionless.
33
+ class Find < Trailblazer::Operation
34
+ step Model Song, :find_by
35
+ step :process
36
+
37
+ def process(options, **); options["x"] = true end
38
+ end
39
+
40
+ # can't find model.
41
+ #- result object, model
42
+ it do
43
+ Find.(params: {id: nil})["result.model"].failure?.must_equal true
44
+ Find.(params: {id: nil})["x"].must_be_nil
45
+ Find.(params: {id: nil}).failure?.must_equal true
46
+ end
47
+
48
+ #- result object, model
49
+ it do
50
+ Find.(params: {id: 9})["result.model"].success?.must_equal true
51
+ Find.(params: {id: 9})["x"].must_equal true
52
+ Find.(params: {id: 9})[:model].inspect.must_equal %{#<struct ModelTest::Song id=9>}
53
+ end
54
+ end
@@ -0,0 +1,293 @@
1
+ require "test_helper"
2
+
3
+ # exec_context nach Nested
4
+
5
+ class NestedTest < Minitest::Spec
6
+ #---
7
+ #- shared data
8
+ class B < Trailblazer::Operation
9
+ pass ->(options, **) { options["can.B.see.A.mutable.data?"] = options["mutable.data.from.A"] }
10
+ pass ->(options, **) { options["can.B.see.current_user?"] = options["current_user"] }
11
+ pass ->(options, **) { options["can.B.see.params?"] = options["params"] }
12
+ pass ->(options, **) { options["can.B.see.A.class.data?"] = options["A.class.data"] }
13
+ pass ->(options, **) { options["can.B.see.container.data?"] = options["some.container.data"] }
14
+ pass ->(options, **) { options["mutable.data.from.B"] = "from B!" }
15
+ end
16
+
17
+ class A < Trailblazer::Operation
18
+ extend ClassDependencies
19
+ self["A.class.data"] = "yes" # class data on A
20
+
21
+ pass ->(options, **) { options["mutable.data.from.A"] = "from A!" } # mutable data on A
22
+ step Nested( B )
23
+ pass ->(options, **) { options["can.A.see.B.mutable.data?"] = options["mutable.data.from.B"] }
24
+ end
25
+
26
+ #---
27
+ #- default behavior: share everything.
28
+ # no containers
29
+ # no runtime data
30
+ # no params
31
+ it do
32
+ result = A.("params" => {})
33
+ # everything from A visible
34
+ result["A.class.data"]. must_equal "yes"
35
+ result["mutable.data.from.A"].must_equal "from A!"
36
+
37
+ # B can see everything
38
+ result["can.B.see.A.mutable.data?"].must_equal "from A!"
39
+ result["can.B.see.current_user?"].must_be_nil
40
+ result["can.B.see.params?"].must_equal({})
41
+ result["can.B.see.A.class.data?"].must_equal "yes"
42
+ result["can.B.see.container.data?"].must_be_nil
43
+
44
+ result["can.A.see.B.mutable.data?"].must_equal "from B!"
45
+ end
46
+
47
+ #---
48
+ #- Nested::NonActivity
49
+ class AlmostB < Trailblazer::Operation
50
+ step ->(options, is_successful:raise, **) { is_successful } # {AlmostB} fails if {is_successful} isn't true.
51
+ step ->(options, **) { options["can.B.see.A.mutable.data?"] = options["mutable.data.from.A"] }
52
+ pass ->(options, **) { options["mutable.data.from.B"] = "from AlmostB!" }
53
+ end
54
+
55
+ #- Nested( ->{} )
56
+ class SomeNestedWithProc < Trailblazer::Operation
57
+ extend ClassDependencies
58
+ self["A.class.data"] = "yes" # class data on A
59
+
60
+ Decider = ->(options, use_class:raise, **) { use_class }
61
+
62
+ pass ->(options, **) { options["mutable.data.from.A"] = "from A!" } # mutable data on A
63
+ step Nested( Decider )
64
+ pass ->(options, **) { options["can.A.see.B.mutable.data?"] = options["mutable.data.from.B"] }
65
+ end
66
+
67
+ #- Nested( Callable )
68
+ class SomeNestedWithCallable < Trailblazer::Operation
69
+ extend ClassDependencies
70
+ self["A.class.data"] = "yes" # class data on A
71
+
72
+ class Decider
73
+ def self.call(options, use_class:raise, **)
74
+ use_class
75
+ end
76
+ end
77
+
78
+ pass ->(options, **) { options["mutable.data.from.A"] = "from A!" } # mutable data on A
79
+ step Nested( Decider )
80
+ pass ->(options, **) { options["can.A.see.B.mutable.data?"] = options["mutable.data.from.B"] }
81
+ end
82
+
83
+ #- Nested( :method )
84
+ class SomeNestedWithMethod < Trailblazer::Operation
85
+ extend ClassDependencies
86
+ self["A.class.data"] = "yes" # class data on A
87
+
88
+ def decider(options, use_class:raise, **)
89
+ use_class
90
+ end
91
+
92
+ pass ->(options, **) { options["mutable.data.from.A"] = "from A!" } # mutable data on A
93
+ step Nested( :decider )
94
+ pass ->(options, **) { options["can.A.see.B.mutable.data?"] = options["mutable.data.from.B"] }
95
+ end
96
+
97
+
98
+ #- test callable
99
+ # B with Callable, successful
100
+ it do
101
+ result = SomeNestedWithCallable.("params" => {}, use_class: B)
102
+ assert_b(result, is_successful: "whatever")
103
+ end
104
+
105
+ # AlmostB with Callable, successful
106
+ it do
107
+ result = SomeNestedWithCallable.("params" => {}, use_class: AlmostB, is_successful: true)
108
+ assert_almost_b(result, is_successful: true)
109
+ end
110
+
111
+ # AlmostB with Callable, failure
112
+ it do
113
+ result = SomeNestedWithCallable.("params" => {}, use_class: AlmostB, is_successful: false)
114
+ assert_almost_b(result, is_successful: false)
115
+ end
116
+
117
+ #- test proc
118
+ # B with proc, successful
119
+ it do
120
+ result = SomeNestedWithProc.("params" => {}, use_class: B)
121
+ assert_b(result, is_successful: "whatever")
122
+ end
123
+
124
+ # AlmostB with proc, successful
125
+ it do
126
+ result = SomeNestedWithProc.("params" => {}, use_class: AlmostB, is_successful: true)
127
+
128
+ assert_almost_b(result, is_successful: true)
129
+ end
130
+
131
+ # AlmostB with proc, failure.
132
+ it do
133
+ result = SomeNestedWithProc.("params" => {}, use_class: AlmostB, is_successful: false)
134
+
135
+ assert_almost_b(result, is_successful: false)
136
+ end
137
+
138
+ #- test :method
139
+ # B with method, successful
140
+ it do
141
+ result = SomeNestedWithMethod.("params" => {}, use_class: B)
142
+ assert_b(result, is_successful: "whatever")
143
+ end
144
+
145
+ # AlmostB with method, successful
146
+ it do
147
+ result = SomeNestedWithMethod.("params" => {}, use_class: AlmostB, is_successful: true)
148
+
149
+ assert_almost_b(result, is_successful: true)
150
+ end
151
+
152
+ # AlmostB with method, failure.
153
+ it do
154
+ result = SomeNestedWithMethod.("params" => {}, use_class: AlmostB, is_successful: false)
155
+
156
+ assert_almost_b(result, is_successful: false)
157
+ end
158
+
159
+
160
+ def assert_almost_b(result, is_successful:raise)
161
+ result.success?.must_equal is_successful # AlmostB was successful, so A is successful.
162
+
163
+ # everything from A visible
164
+ result["A.class.data"]. must_equal "yes"
165
+ result["mutable.data.from.A"].must_equal "from A!"
166
+
167
+ # AlmostB doesn't look for everything
168
+ result["can.B.see.current_user?"].must_be_nil
169
+ result["can.B.see.params?"].must_be_nil
170
+ if is_successful
171
+ result["can.B.see.A.mutable.data?"].must_equal "from A!"
172
+ result["can.B.see.A.class.data?"].must_be_nil # we don't look for it.
173
+ result["can.A.see.B.mutable.data?"].must_equal "from AlmostB!"
174
+ else
175
+ result["can.B.see.A.mutable.data?"].must_be_nil
176
+ result["can.B.see.A.class.data?"].must_be_nil
177
+ result["can.A.see.B.mutable.data?"].must_be_nil
178
+ end
179
+ result["can.B.see.container.data?"].must_be_nil
180
+
181
+
182
+ # result[:is_successful].must_equal is_successful # FIXME: this is wrong!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! key is symbol
183
+ end
184
+
185
+ def assert_b(result, is_successful:raise)
186
+ # everything from A visible
187
+ result["A.class.data"]. must_equal "yes"
188
+ result["mutable.data.from.A"].must_equal "from A!"
189
+
190
+ # B can see everything
191
+ result["can.B.see.A.mutable.data?"].must_equal "from A!"
192
+ result["can.B.see.current_user?"].must_be_nil
193
+ result["can.B.see.params?"].must_equal({})
194
+ result["can.B.see.A.class.data?"].must_equal "yes"
195
+ result["can.B.see.container.data?"].must_be_nil
196
+
197
+ result["can.A.see.B.mutable.data?"].must_equal "from B!"
198
+
199
+ result[:is_successful].must_be_nil
200
+ result.success?.must_equal true # B was successful, so A is successful.
201
+ end
202
+
203
+
204
+ #---
205
+ #- :exec_context
206
+ class Create < Trailblazer::Operation
207
+ class Edit < Trailblazer::Operation
208
+ step :c!
209
+
210
+ def c!(options, **); options[:c] = 1 end
211
+ end
212
+
213
+ step :a!
214
+ step Nested( Edit )
215
+ step :b!
216
+
217
+ def a!(options, **); options[:a] = 2 end
218
+ def b!(options, **); options[:b] = 3 end
219
+ end
220
+
221
+ it { Create.().inspect(:a, :b, :c).must_equal %{<Result:true [2, 3, 1] >} }
222
+ end
223
+
224
+ class NestedWithFastTrackTest < Minitest::Spec
225
+ module Steps
226
+ def b(options, a:, **)
227
+ options["b"] = a+1
228
+ end
229
+
230
+ def f(options, **)
231
+ options["f"] = 3
232
+ end
233
+ end
234
+
235
+ class Edit < Trailblazer::Operation
236
+ pass :a, pass_fast: true
237
+
238
+ def a(options, **)
239
+ options["a"] = 1
240
+ end
241
+ end
242
+
243
+ class Update < Trailblazer::Operation
244
+ step Nested( Edit )
245
+ step :b
246
+ fail :f
247
+
248
+ include Steps
249
+ end
250
+
251
+ # from Nested straight to End.pass_fast.
252
+ it { Update.({}).inspect("a", "b", "f").must_equal %{<Result:true [1, nil, nil] >} }
253
+
254
+ #- Nested, pass_fast: true
255
+ class Upsert < Trailblazer::Operation
256
+ step Nested( Edit ), pass_fast: true # this option is unnecessary.
257
+ step :b
258
+ fail :f
259
+
260
+ include Steps
261
+ end
262
+
263
+ # from Nested straight to End.pass_fast.
264
+ it { Upsert.({}).inspect("a", "b", "f").must_equal %{<Result:true [1, nil, nil] >} }
265
+
266
+ #- mapping
267
+ #- Nested, :pass_fast => :failure
268
+ it "attaches :pass_fast => :failure" do
269
+ op = Class.new(Trailblazer::Operation) do
270
+ step Nested( Edit ), Output(:pass_fast) => Track(:failure)
271
+ step :b
272
+ fail :f
273
+
274
+ include Steps
275
+ end
276
+
277
+ # from Nested to :failure track.
278
+ op.({}).inspect("a", "b", "c", "f").must_equal %{<Result:false [1, nil, nil, 3] >}
279
+ end
280
+
281
+ it "goes straigt to End.failure" do
282
+ op = Class.new(Trailblazer::Operation) do
283
+ step Nested( Edit ), Output(:pass_fast) => "End.failure"
284
+ step :b
285
+ fail :f
286
+
287
+ include Steps
288
+ end
289
+
290
+ # from Nested straight to End.failure, no fail step will be visited.
291
+ op.({}).inspect("a", "b", "c", "f").must_equal %{<Result:false [1, nil, nil, nil] >}
292
+ end
293
+ end
@@ -0,0 +1,106 @@
1
+ require "test_helper"
2
+ require "trailblazer/operation/policy"
3
+
4
+ class PolicyTest < Minitest::Spec
5
+ Song = Struct.new(:id) do
6
+ def self.find(id); new(id) end
7
+ end
8
+
9
+ class Auth
10
+ def initialize(user, model); @user, @model = user, model end
11
+ def only_user?; @user == Module && @model.nil? end
12
+ def user_object?; @user == Object end
13
+ def user_and_model?; @user == Module && @model.class == Song end
14
+ def inspect; "<Auth: user:#{@user.inspect}, model:#{@model.inspect}>" end
15
+ end
16
+
17
+ #---
18
+ # Instance-level: Only policy, no model
19
+ class Create < Trailblazer::Operation
20
+ step Policy::Pundit( Auth, :only_user? )
21
+ step :process
22
+
23
+ def process(options, **)
24
+ options["process"] = true
25
+ end
26
+ end
27
+
28
+ # successful.
29
+ it do
30
+ result = Create.(params: {}, current_user: Module)
31
+ result["process"].must_equal true
32
+ #- result object, policy
33
+ result["result.policy.default"].success?.must_equal true
34
+ result["result.policy.default"]["message"].must_be_nil
35
+ # result[:valid].must_be_nil
36
+ result["policy.default"].inspect.must_equal %{<Auth: user:Module, model:nil>}
37
+ end
38
+ # breach.
39
+ it do
40
+ result = Create.(params: {}, current_user: nil)
41
+ result["process"].must_be_nil
42
+ #- result object, policy
43
+ result["result.policy.default"].success?.must_equal false
44
+ result["result.policy.default"]["message"].must_equal "Breach"
45
+ end
46
+ # inject different policy.Condition it { Create.(params: {}, current_user: Object, "policy.default.eval" => Trailblazer::Operation::Policy::Pundit::Condition.new(Auth, :user_object?))["process"].must_equal true }
47
+ it { Create.(params: {}, current_user: Module, "policy.default.eval" => Trailblazer::Operation::Policy::Pundit::Condition.new(Auth, :user_object?))["process"].must_be_nil }
48
+
49
+
50
+ #---
51
+ # inheritance, adding Model
52
+ class Show < Create
53
+ step Model( Song, :new ), before: "policy.default.eval"
54
+ end
55
+
56
+ it { Trailblazer::Operation::Inspect.(Show).must_equal %{[>model.build,>policy.default.eval,>process]} }
57
+
58
+ # invalid because user AND model.
59
+ it do
60
+ result = Show.(params: {}, current_user: Module)
61
+ result["process"].must_be_nil
62
+ result[:model].inspect.must_equal %{#<struct PolicyTest::Song id=nil>}
63
+ # result["policy"].inspect.must_equal %{#<struct PolicyTest::Song id=nil>}
64
+ end
65
+
66
+ # valid because new policy.
67
+ it do
68
+ # puts Show["pipetree"].inspect
69
+ result = Show.(params: {}, current_user: Module, "policy.default.eval" => Trailblazer::Operation::Policy::Pundit::Condition.new(Auth, :user_and_model?))
70
+ result["process"].must_equal true
71
+ result[:model].inspect.must_equal %{#<struct PolicyTest::Song id=nil>}
72
+ result["policy.default"].inspect.must_equal %{<Auth: user:Module, model:#<struct PolicyTest::Song id=nil>>}
73
+ end
74
+
75
+ ##--
76
+ # TOOOODOOO: Policy and Model before Build ("External" or almost Resolver)
77
+ class Edit < Trailblazer::Operation
78
+ step Model Song, :find
79
+ step Policy::Pundit( Auth, :user_and_model? )
80
+ step :process
81
+
82
+ def process(options, **)
83
+ options["process"] = true
84
+ end
85
+ end
86
+
87
+ # successful.
88
+ it do
89
+ result = Edit.(params: { id: 1 }, current_user: Module)
90
+ result["process"].must_equal true
91
+ result[:model].inspect.must_equal %{#<struct PolicyTest::Song id=1>}
92
+ result["result.policy.default"].success?.must_equal true
93
+ result["result.policy.default"]["message"].must_be_nil
94
+ # result[:valid].must_be_nil
95
+ result["policy.default"].inspect.must_equal %{<Auth: user:Module, model:#<struct PolicyTest::Song id=1>>}
96
+ end
97
+
98
+ # breach.
99
+ it do
100
+ result = Edit.(params: { id: 4 }, current_user: nil)
101
+ result[:model].inspect.must_equal %{#<struct PolicyTest::Song id=4>}
102
+ result["process"].must_be_nil
103
+ result["result.policy.default"].success?.must_equal false
104
+ result["result.policy.default"]["message"].must_equal "Breach"
105
+ end
106
+ end