trailblazer-macro 2.1.11 → 2.1.13

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,585 @@
1
+ require "test_helper"
2
+ module Autogenerated
3
+
4
+ # step Macro::Each(:report_templates, key: :report_template) {
5
+ # step Subprocess(ReportTemplate::Update), input: :input_report_template
6
+ # fail :set_report_template_errors
7
+ # }
8
+
9
+ # def report_templates(ctx, **) ctx["result.contract.default"].report_templates
10
+ # end
11
+
12
+ class EachTest < Minitest::Spec
13
+ class Composer < Struct.new(:full_name, :email)
14
+ end
15
+
16
+ class Mailer
17
+ def self.send(**options)
18
+ @send_options << options
19
+ end
20
+
21
+ class << self
22
+ attr_accessor :send_options
23
+ end
24
+ end
25
+
26
+ #@ operation has {#composers_for_each}
27
+ module B
28
+ class Song < Struct.new(:id, :title, :band, :composers)
29
+ def self.find_by(id:)
30
+ if id == 2
31
+ return Song.new(id, nil, nil, [Composer.new("Fat Mike", "mike@fat.wreck"), Composer.new("El Hefe")])
32
+ end
33
+
34
+ if id == 3
35
+ return Song.new(id, nil, nil, [Composer.new("Fat Mike", "mike@fat.wreck"), Composer.new("El Hefe", "scammer@spam")])
36
+ end
37
+
38
+ Song.new(id, nil, nil, [Composer.new("Fat Mike"), Composer.new("El Hefe")])
39
+ end
40
+ end
41
+
42
+ #:each
43
+ module Song::Operation
44
+ class Cover < Trailblazer::Operation
45
+ step :model
46
+ #:each-dataset
47
+ step Each(dataset_from: :composers_for_each, collect: true) {
48
+ step :notify_composers
49
+ }
50
+ #:each-dataset end
51
+ step :rearrange
52
+
53
+ # "decider interface"
54
+ def composers_for_each(ctx, model:, **)
55
+ model.composers
56
+ end
57
+
58
+ def notify_composers(ctx, index:, item:, **)
59
+ ctx[:value] = [index, item.full_name]
60
+ end
61
+
62
+ #~meths
63
+ def model(ctx, params:, **)
64
+ ctx[:model] = Song.find_by(id: params[:id])
65
+ end
66
+
67
+ include T.def_steps(:rearrange)
68
+ #~meths end
69
+ end
70
+ end
71
+ #:each end
72
+ end # B
73
+
74
+ it "allows a dataset compute in the hosting activity" do
75
+ #@ {:dataset} is not part of the {ctx}.
76
+ assert_invoke B::Song::Operation::Cover, params: {id: 1},
77
+ expected_ctx_variables: {
78
+ model: B::Song.find_by(id: 1),
79
+ collected_from_each: [[0, "Fat Mike"], [1, "El Hefe"],]
80
+ },
81
+ seq: "[:rearrange]"
82
+
83
+ =begin
84
+ #:collected_from_each
85
+ #~ctx_to_result
86
+ ctx = {params: {id: 1}} # Song 1 has two composers.
87
+
88
+ result = Song::Operation::Cover.(ctx)
89
+
90
+ puts result[:collected_from_each] #=> [[0, "Fat Mike"], [1, "El Hefe"]]
91
+ #~ctx_to_result end
92
+ #:collected_from_each end
93
+ =end
94
+ end
95
+
96
+ module CoverMethods
97
+ def notify_composers(ctx, index:, item:, **)
98
+ ctx[:value] = [index, item.full_name]
99
+ end
100
+
101
+ def model(ctx, params:, **)
102
+ ctx[:model] = EachTest::B::Song.find_by(id: params[:id])
103
+ end
104
+
105
+ include T.def_steps(:rearrange)
106
+ end
107
+
108
+ module ComposersForEach
109
+ def composers_for_each(ctx, model:, **)
110
+ model.composers
111
+ end
112
+ end
113
+
114
+ #@ operation has dedicated step {#find_composers}
115
+ module C
116
+ class Song < B::Song; end
117
+
118
+ module Song::Operation
119
+ class Cover < Trailblazer::Operation
120
+ step :model
121
+ step :find_composers
122
+ step Each(collect: true) {
123
+ step :notify_composers
124
+ }, In() => {:composers => :dataset}
125
+ step :rearrange
126
+
127
+ def find_composers(ctx, model:, **)
128
+ # You could also say {ctx[:dataset] = model.composers},
129
+ # and wouldn't need the In() mapping.
130
+ ctx[:composers] = model.composers
131
+ end
132
+ #~meths
133
+ include CoverMethods
134
+ #~meths end
135
+ end
136
+ end
137
+ end # C
138
+
139
+ it "dataset can come from the hosting activity" do
140
+ #@ {:dataset} is not part of the outgoing {ctx}.
141
+ assert_invoke B::Song::Operation::Cover, params: {id: 1},
142
+ expected_ctx_variables: {
143
+ model: B::Song.find_by(id: 1),
144
+ collected_from_each: [[0, "Fat Mike"], [1, "El Hefe"],]
145
+ }, seq: "[:rearrange]"
146
+ end
147
+
148
+ it "dataset coming via In() from the operation" do
149
+ #@ {:dataset} is not part of the {ctx}.
150
+ assert_invoke C::Song::Operation::Cover, params: {id: 1},
151
+ expected_ctx_variables: {
152
+ model: C::Song.find_by(id: 1),
153
+ composers: [Composer.new("Fat Mike"), Composer.new("El Hefe")],
154
+ collected_from_each: [[0, "Fat Mike"], [1, "El Hefe"],]
155
+ }, seq: "[:rearrange]"
156
+ end
157
+
158
+ #@ {:item_key}
159
+ module E
160
+ class Song < B::Song; end
161
+
162
+ Mailer = Class.new(EachTest::Mailer)
163
+
164
+ #:composer
165
+ module Song::Operation
166
+ class Cover < Trailblazer::Operation
167
+ #~meths
168
+ step :model
169
+ #:item_key
170
+ step Each(dataset_from: :composers_for_each, item_key: :composer) {
171
+ step :notify_composers
172
+ }
173
+ #:item_key end
174
+ step :rearrange
175
+
176
+
177
+ # circuit-step interface! "decider interface"
178
+ def composers_for_each(ctx, model:, **)
179
+ model.composers
180
+ end
181
+ include CoverMethods
182
+ #~meths end
183
+ def notify_composers(ctx, index:, composer:, **)
184
+ Mailer.send(to: composer.email, message: "#{index}) You, #{composer.full_name}, have been warned about your song being copied.")
185
+ end
186
+ end
187
+ end
188
+ #:composer end
189
+ end # E
190
+
191
+ it "{item_key: :composer}" do
192
+ E::Mailer.send_options = []
193
+ assert_invoke E::Song::Operation::Cover, params: {id: 1},
194
+ expected_ctx_variables: {
195
+ model: B::Song.find_by(id: 1),
196
+ # collected_from_each: ["Fat Mike", "El Hefe"]
197
+ },
198
+ seq: "[:rearrange]"
199
+ assert_equal E::Mailer.send_options, [{:to=>nil, :message=>"0) You, Fat Mike, have been warned about your song being copied."}, {:to=>nil, :message=>"1) You, El Hefe, have been warned about your song being copied."}]
200
+ end
201
+
202
+ #@ failure in Each
203
+ module F
204
+ class Song < B::Song; end
205
+
206
+ class Notify
207
+ def self.send_email(email)
208
+ return if email.nil?
209
+ true
210
+ end
211
+ end
212
+
213
+ module Song::Operation
214
+ class Cover < Trailblazer::Operation
215
+ step :model
216
+ step Each(dataset_from: :composers_for_each, collect: true) {
217
+ step :notify_composers
218
+ }
219
+ step :rearrange
220
+
221
+ def notify_composers(ctx, item:, **)
222
+ if Notify.send_email(item.email)
223
+ ctx[:value] = item.email # let's collect all emails that could be sent.
224
+ return true
225
+ else
226
+ return false
227
+ end
228
+ end
229
+ #~meths
230
+
231
+ # circuit-step interface! "decider interface"
232
+ def composers_for_each(ctx, model:, **)
233
+ model.composers
234
+ end
235
+ include CoverMethods
236
+ #~meths end
237
+ end
238
+ end
239
+ end # F
240
+
241
+ it "failure in Each" do
242
+ assert_invoke F::Song::Operation::Cover, params: {id: 2},
243
+ expected_ctx_variables: {
244
+ model: B::Song.find_by(id: 2),
245
+ collected_from_each: ["mike@fat.wreck", nil],
246
+ },
247
+ seq: "[]",
248
+ terminus: :failure
249
+
250
+ Trailblazer::Developer.wtf?(F::Song::Operation::Cover, [{params: {id: 2}, seq: []}])
251
+ end
252
+
253
+
254
+ #@ Each with operation
255
+ module D
256
+ class Song < B::Song; end
257
+ Mailer = Class.new(EachTest::Mailer)
258
+
259
+ #:operation-class
260
+ module Song::Operation
261
+ class Notify < Trailblazer::Operation
262
+ step :send_email
263
+
264
+ def send_email(ctx, index:, item:, **)
265
+ Mailer.send(to: item.email, message: "#{index}) You, #{item.full_name}, have been warned about your song being copied.")
266
+ end
267
+ end
268
+ end
269
+ #:operation-class end
270
+
271
+ #:operation
272
+ module Song::Operation
273
+ class Cover < Trailblazer::Operation
274
+ step :model
275
+ step Each(Notify, dataset_from: :composers_for_each)
276
+ step :rearrange
277
+ #~meths
278
+ def composers_for_each(ctx, model:, **)
279
+ model.composers
280
+ end
281
+ include CoverMethods
282
+ #~meths end
283
+ end
284
+ end
285
+ #:operation end
286
+ end
287
+
288
+ it "Each(Activity::Railway)" do
289
+ D::Mailer.send_options = []
290
+ assert_invoke D::Song::Operation::Cover, params: {id: 1},
291
+ seq: "[:rearrange]",
292
+ expected_ctx_variables: {
293
+ model: D::Song.find_by(id: 1),
294
+ # collected_from_each: [[0, "Fat Mike"], [1, "El Hefe"],]
295
+ }
296
+ assert_equal D::Mailer.send_options, [{:to=>nil, :message=>"0) You, Fat Mike, have been warned about your song being copied."}, {:to=>nil, :message=>"1) You, El Hefe, have been warned about your song being copied."}]
297
+ end
298
+
299
+ #@ Each with operation with three outcomes. Notify terminates on {End.spam_email},
300
+ # which is then routed to End.spam_alert in the hosting activity.
301
+ # NOTE: this is not documented, yet.
302
+ module G
303
+ class Song < B::Song; end
304
+
305
+ module Song::Operation
306
+ class Notify < Trailblazer::Operation
307
+ terminus :spam_email
308
+ # SpamEmail = Class.new(Trailblazer::Operation::Signal)
309
+
310
+ step :send_email, Output(:failure) => Track(:spam_email)
311
+
312
+ def send_email(ctx, index:, item:, **)
313
+ return false if item.email == "scammer@spam"
314
+ ctx[:value] = [index, item.full_name]
315
+ end
316
+ end
317
+ end
318
+
319
+ module Song::Operation
320
+ class Cover < Trailblazer::Operation
321
+ terminus :spam_alert
322
+
323
+ step :model
324
+ step Each(Notify, dataset_from: :composers_for_each, collect: true),
325
+ Output(:spam_email) => Track(:spam_alert)
326
+ step :rearrange
327
+ #~meths
328
+ def composers_for_each(ctx, model:, **)
329
+ model.composers
330
+ end
331
+ include CoverMethods
332
+ #~meths end
333
+ end
334
+ end
335
+ end
336
+
337
+ it "Each(Activity::Railway) with End.spam_email" do
338
+ Trailblazer::Developer.wtf?(G::Song::Operation::Cover, [{params: {id: 3}}, {}])
339
+
340
+ assert_invoke G::Song::Operation::Cover, params: {id: 3},
341
+ terminus: :spam_alert,
342
+ seq: "[]",
343
+ expected_ctx_variables: {
344
+ model: G::Song.find_by(id: 3),
345
+ collected_from_each: [[0, "Fat Mike"], nil,]
346
+ }
347
+ end
348
+ end
349
+
350
+ #@ Iteration doesn't add anything to ctx when {collect: false}.
351
+ class EachCtxDiscardedTest < Minitest::Spec
352
+ Composer = EachTest::Composer
353
+ Song = Class.new(EachTest::B::Song)
354
+
355
+ #@ iterated steps write to ctx, gets discarded.
356
+ module Song::Operation
357
+ class Cover < Trailblazer::Operation
358
+ step :model
359
+ #:write_to_ctx
360
+ step Each(dataset_from: :composers_for_each) {
361
+ step :notify_composers
362
+ step :write_to_ctx
363
+ }
364
+ #:write_to_ctx end
365
+ step :rearrange
366
+
367
+ #:write
368
+ def write_to_ctx(ctx, index:, seq:, **)
369
+ #~meths
370
+ seq << :write_to_ctx
371
+
372
+ #~meths end
373
+ ctx[:variable] = index # this is discarded!
374
+ end
375
+ #:write end
376
+
377
+ #~meths
378
+ include EachTest::CoverMethods
379
+ include EachTest::ComposersForEach
380
+ #~meths end
381
+ end
382
+ end
383
+
384
+ it "discards {ctx[:variable]}" do
385
+ assert_invoke Song::Operation::Cover, params: {id: 1},
386
+ expected_ctx_variables: {
387
+ model: Song.find_by(id: 1),
388
+ # collected_from_each: [[0, "Fat Mike"], [1, "El Hefe"],]
389
+ },
390
+ seq: "[:write_to_ctx, :write_to_ctx, :rearrange]"
391
+ end
392
+ end
393
+
394
+ # We add {:collected_from_each} ourselves.
395
+ class EachCtxAddsCollectedFromEachTest < Minitest::Spec
396
+ Composer = EachTest::Composer
397
+ Song = Class.new(EachTest::B::Song)
398
+
399
+ module Song::Operation
400
+ class Cover < Trailblazer::Operation
401
+ step :model
402
+ step Each(dataset_from: :composers_for_each,
403
+
404
+ # all filters called before/after each iteration!
405
+ Inject(:collected_from_each) => ->(ctx, **) { [] }, # this is called only once.
406
+ Out() => ->(ctx, collected_from_each:, **) { {collected_from_each: collected_from_each += [ctx[:value]] } }
407
+
408
+
409
+
410
+ ) {
411
+ step :notify_composers
412
+ step :write_to_ctx
413
+ }
414
+ step :rearrange
415
+
416
+ def write_to_ctx(ctx, index:, seq:, item:, **)
417
+ seq << :write_to_ctx
418
+
419
+ ctx[:value] = [index, item.full_name]
420
+ end
421
+
422
+ #~meths
423
+ include EachTest::CoverMethods
424
+ include EachTest::ComposersForEach
425
+ #~meths end
426
+ end
427
+ end
428
+
429
+ it "provides {:collected_from_each}" do
430
+ assert_invoke Song::Operation::Cover, params: {id: 1},
431
+ expected_ctx_variables: {
432
+ model: Song.find_by(id: 1),
433
+ collected_from_each: [[0, "Fat Mike"], [1, "El Hefe"],]
434
+ },
435
+ seq: "[:write_to_ctx, :write_to_ctx, :rearrange]"
436
+ end
437
+ end
438
+
439
+ #@ You can use Inject() to compute new variables.
440
+ #@ and Out() to compute what goes into the iterated {ctx}.
441
+ class EachCtxInOutTest < Minitest::Spec
442
+ Composer = EachTest::Composer
443
+ Song = Class.new(EachTest::B::Song)
444
+
445
+ module Song::Operation
446
+ class Cover < Trailblazer::Operation
447
+ step :model
448
+ step Each(dataset_from: :composers_for_each,
449
+ # Inject(always: true) => {
450
+ Inject(:composer_index) => ->(ctx, index:, **) { index },
451
+ # all filters called before/after each iteration!
452
+ Out() => ->(ctx, index:, variable:, **) { {:"composer-#{index}-value" => variable} }
453
+
454
+
455
+
456
+
457
+
458
+ ) {
459
+ step :notify_composers
460
+ step :write_to_ctx
461
+ }
462
+ step :rearrange
463
+
464
+ def write_to_ctx(ctx, composer_index:, model:, **)
465
+ ctx[:variable] = "#{composer_index} + #{model.class.name.split('::').last}"
466
+ end
467
+
468
+ #~meths
469
+ include EachTest::CoverMethods
470
+ include EachTest::ComposersForEach
471
+ #~meths end
472
+ end
473
+ end
474
+
475
+ it "discards {ctx[:variable]}" do
476
+ assert_invoke Song::Operation::Cover, params: {id: 1},
477
+ expected_ctx_variables: {
478
+ model: Song.find_by(id: 1),
479
+ :"composer-0-value" => "0 + Song",
480
+ :"composer-1-value" => "1 + Song",
481
+ },
482
+ seq: "[:rearrange]"
483
+ end
484
+ end
485
+
486
+ class EachOuterCtxTest < Minitest::Spec
487
+
488
+ end
489
+
490
+
491
+ #@ {:errors} is first initialized with a default injection,
492
+ #@ then passed across iterations.
493
+ # TODO: similar test above with {:collected_from_each}.
494
+ class EachSharedIterationVariableTest < Minitest::Spec
495
+ Song = Class.new(EachTest::B::Song)
496
+
497
+ #:inject
498
+ module Song::Operation
499
+ class Cover < Trailblazer::Operation
500
+ step :model
501
+ step Each(dataset_from: :composers_for_each,
502
+ Inject(:messages) => ->(*) { {} },
503
+
504
+ # all filters called before/after each iteration!
505
+ Out() => [:messages]
506
+ ) {
507
+ step :write_to_ctx
508
+ }
509
+ step :rearrange
510
+
511
+ def write_to_ctx(ctx, item:, messages:, index:, **)
512
+ ctx[:messages] = messages.merge(index => item.full_name)
513
+ end
514
+ #~meths
515
+ include EachTest::CoverMethods
516
+ include EachTest::ComposersForEach
517
+ #~meths end
518
+ end
519
+ end
520
+ #:inject end
521
+
522
+ it "passes {ctx[:messages]} across iterations and makes it grow" do
523
+ assert_invoke Song::Operation::Cover, params: {id: 1},
524
+ expected_ctx_variables: {
525
+ model: Song.find_by(id: 1),
526
+ messages: {0=>"Fat Mike", 1=>"El Hefe"}},
527
+ seq: "[:rearrange]"
528
+ end
529
+
530
+ end
531
+
532
+ #@ Each without any option
533
+ class EachPureTest < Minitest::Spec
534
+ Song = Class.new(EachTest::B::Song)
535
+
536
+ Mailer = Class.new(EachTest::Mailer)
537
+
538
+ #:each-pure
539
+ module Song::Operation
540
+ class Cover < Trailblazer::Operation
541
+ step :model
542
+ #:each-pure-macro
543
+ step Each(dataset_from: :composers_for_each) {
544
+ step :notify_composers
545
+ }
546
+ #:each-pure-macro end
547
+ step :rearrange
548
+
549
+ # "decider interface"
550
+ #:dataset_from
551
+ def composers_for_each(ctx, model:, **)
552
+ model.composers
553
+ end
554
+ #:dataset_from end
555
+
556
+ #:iterated
557
+ def notify_composers(ctx, index:, item:, **)
558
+ Mailer.send(to: item.email, message: "#{index}) You, #{item.full_name}, have been warned about your song being copied.")
559
+ end
560
+ #:iterated end
561
+ #~meths
562
+ def model(ctx, params:, **)
563
+ ctx[:model] = Song.find_by(id: params[:id])
564
+ end
565
+
566
+ include T.def_steps(:rearrange)
567
+ #~meths end
568
+ end
569
+ end
570
+ #:each-pure end
571
+
572
+ it "allows a dataset compute in the hosting activity" do
573
+ Mailer.send_options = []
574
+ #@ {:dataset} is not part of the {ctx}.
575
+ assert_invoke Song::Operation::Cover, params: {id: 1},
576
+ expected_ctx_variables: {
577
+ model: Song.find_by(id: 1),
578
+ },
579
+ seq: "[:rearrange]"
580
+
581
+ assert_equal Mailer.send_options, [{:to=>nil, :message=>"0) You, Fat Mike, have been warned about your song being copied."}, {:to=>nil, :message=>"1) You, El Hefe, have been warned about your song being copied."}]
582
+ end
583
+ end
584
+
585
+ end