trailblazer-macro 2.1.0.beta1

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.
@@ -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