iknow_view_models 3.4.4 → 3.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,599 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest/autorun'
4
+ require 'minitest/unit'
5
+ require 'minitest/hooks'
6
+
7
+ require 'view_model'
8
+ require 'view_model/active_record'
9
+
10
+ require_relative '../../../helpers/controller_test_helpers'
11
+ require_relative '../../../helpers/callback_tracer'
12
+
13
+ class ViewModel::ActiveRecord::ControllerNestedTest < ActiveSupport::TestCase
14
+ include ARVMTestUtilities
15
+ include ControllerTestModels
16
+ include ControllerTestControllers
17
+
18
+ def before_all
19
+ super
20
+
21
+ build_controller_test_models(externalize: [:label, :child, :target])
22
+ end
23
+
24
+ def setup
25
+ super
26
+ @parent = make_parent
27
+ @parent_view = ParentView.new(@parent)
28
+
29
+ enable_logging!
30
+ end
31
+
32
+ #### Controller for nested model
33
+
34
+ def test_nested_collection_index_associated
35
+ _distractor = Parent.create(name: 'p2', children: [Child.new(name: 'c3', position: 1)])
36
+
37
+ childcontroller = ChildController.new(params: {
38
+ owner_viewmodel: 'parent',
39
+ association_name: 'children',
40
+ parent_id: @parent.id
41
+ })
42
+ childcontroller.invoke(:index_associated)
43
+
44
+ assert_equal(200, childcontroller.status)
45
+
46
+ expected_children = @parent.children
47
+ assert_equal({ 'data' => expected_children.map { |c| ChildView.new(c).to_hash } },
48
+ childcontroller.hash_response)
49
+
50
+ assert_all_hooks_nested_inside_parent_hook(childcontroller.hook_trace)
51
+ end
52
+
53
+ def test_nested_collection_index
54
+ distractor = Parent.create(name: 'p2', children: [Child.new(name: 'c3', position: 1)])
55
+ childcontroller = ChildController.new
56
+
57
+ childcontroller.invoke(:index)
58
+
59
+ assert_equal(200, childcontroller.status)
60
+
61
+ expected_children = @parent.children + distractor.children
62
+ assert_equal({ 'data' => expected_children.map { |c| ChildView.new(c).to_hash } },
63
+ childcontroller.hash_response)
64
+ end
65
+
66
+ def test_nested_collection_append_one
67
+ data = { '_type' => 'Child', 'name' => 'c3' }
68
+ childcontroller = ChildController.new(params: {
69
+ owner_viewmodel: 'parent',
70
+ association_name: 'children',
71
+ parent_id: @parent.id,
72
+ data: data,
73
+ })
74
+
75
+ childcontroller.invoke(:append)
76
+
77
+ assert_equal(200, childcontroller.status, childcontroller.hash_response)
78
+
79
+ @parent.reload
80
+
81
+ assert_equal(%w[c1 c2 c3], @parent.children.order(:position).pluck(:name))
82
+ assert_equal({ 'data' => ChildView.new(@parent.children.last).to_hash },
83
+ childcontroller.hash_response)
84
+
85
+ assert_all_hooks_nested_inside_parent_hook(childcontroller.hook_trace)
86
+ end
87
+
88
+ def test_nested_collection_append_many
89
+ data = [{ '_type' => 'Child', 'name' => 'c3' },
90
+ { '_type' => 'Child', 'name' => 'c4' },]
91
+
92
+ childcontroller = ChildController.new(params: {
93
+ owner_viewmodel: 'parent',
94
+ association_name: 'children',
95
+ parent_id: @parent.id,
96
+ data: data,
97
+ })
98
+ childcontroller.invoke(:append)
99
+
100
+ assert_equal(200, childcontroller.status, childcontroller.hash_response)
101
+
102
+ @parent.reload
103
+
104
+ assert_equal(%w[c1 c2 c3 c4], @parent.children.order(:position).pluck(:name))
105
+ new_children_hashes = @parent.children.last(2).map { |c| ChildView.new(c).to_hash }
106
+ assert_equal({ 'data' => new_children_hashes },
107
+ childcontroller.hash_response)
108
+
109
+ assert_all_hooks_nested_inside_parent_hook(childcontroller.hook_trace)
110
+ end
111
+
112
+ # FIXME: nested controllers really need to be to other roots; children aren't roots.
113
+ def test_nested_collection_replace
114
+ # Parent.children
115
+ old_children = @parent.children
116
+
117
+ data = [{ '_type' => 'Child', 'name' => 'newc1' },
118
+ { '_type' => 'Child', 'name' => 'newc2' },]
119
+
120
+ childcontroller = ChildController.new(params: {
121
+ owner_viewmodel: 'parent',
122
+ association_name: 'children',
123
+ parent_id: @parent.id,
124
+ data: data,
125
+ })
126
+ childcontroller.invoke(:replace)
127
+
128
+ assert_equal(200, childcontroller.status, childcontroller.hash_response)
129
+
130
+ @parent.reload
131
+
132
+ assert_equal(%w[newc1 newc2], @parent.children.order(:position).pluck(:name))
133
+ assert_predicate(Child.where(id: old_children.map(&:id)), :empty?)
134
+
135
+ assert_all_hooks_nested_inside_parent_hook(childcontroller.hook_trace)
136
+ end
137
+
138
+ def test_nested_collection_replace_bad_data
139
+ data = [{ 'name' => 'nc' }]
140
+ childcontroller = ChildController.new(params: {
141
+ owner_viewmodel: 'parent',
142
+ association_name: 'children',
143
+ parent_id: @parent.id,
144
+ data: data,
145
+ })
146
+
147
+ childcontroller.invoke(:replace)
148
+
149
+ assert_equal(400, childcontroller.status)
150
+
151
+ assert_all_hooks_nested_inside_parent_hook(childcontroller.hook_trace)
152
+ end
153
+
154
+ def test_nested_collection_replace_bulk
155
+ other_parent = make_parent(name: 'p_other', child_names: ['other_c1', 'other_c2'])
156
+
157
+ old_children = other_parent.children + @parent.children
158
+
159
+ data = {
160
+ '_type' => '_bulk_update',
161
+ 'updates' => [
162
+ {
163
+ 'id' => @parent.id,
164
+ 'update' => [
165
+ { '_type' => 'Child', 'name' => 'newc1' },
166
+ { '_type' => 'Child', 'name' => 'newc2' },],
167
+ },
168
+ {
169
+ 'id' => other_parent.id,
170
+ 'update' => [
171
+ { '_type' => 'Child', 'name' => 'other_newc1' },
172
+ { '_type' => 'Child', 'name' => 'other_newc2' },],
173
+ }
174
+ ],
175
+ }
176
+
177
+ childcontroller = ChildController.new(params: {
178
+ owner_viewmodel: 'parent',
179
+ association_name: 'children',
180
+ data: data,
181
+ })
182
+
183
+ childcontroller.invoke(:replace_bulk)
184
+
185
+ assert_equal(200, childcontroller.status, childcontroller.hash_response)
186
+
187
+ @parent.reload
188
+ other_parent.reload
189
+
190
+ assert_equal(%w[newc1 newc2], @parent.children.order(:position).pluck(:name))
191
+ assert_equal(%w[other_newc1 other_newc2], other_parent.children.order(:position).pluck(:name))
192
+
193
+ assert_predicate(Child.where(id: old_children.map(&:id)), :empty?)
194
+
195
+ assert_all_hooks_nested_inside_parent_hook(childcontroller.hook_trace)
196
+ end
197
+
198
+
199
+ def test_nested_collection_disassociate_one
200
+ old_child = @parent.children.first
201
+ childcontroller = ChildController.new(params: {
202
+ owner_viewmodel: 'parent',
203
+ association_name: 'children',
204
+ parent_id: @parent.id,
205
+ id: old_child.id,
206
+ })
207
+ childcontroller.invoke(:disassociate)
208
+
209
+ assert_equal(200, childcontroller.status, childcontroller.hash_response)
210
+
211
+ @parent.reload
212
+
213
+ assert_equal(%w[c2], @parent.children.order(:position).pluck(:name))
214
+ assert_predicate(Child.where(id: old_child.id), :empty?)
215
+
216
+ assert_all_hooks_nested_inside_parent_hook(childcontroller.hook_trace)
217
+ end
218
+
219
+ def test_nested_collection_disassociate_many
220
+ old_children = @parent.children
221
+
222
+ childcontroller = ChildController.new(params: {
223
+ owner_viewmodel: 'parent',
224
+ association_name: 'children',
225
+ parent_id: @parent.id,
226
+ })
227
+ childcontroller.invoke(:disassociate_all)
228
+
229
+ assert_equal(200, childcontroller.status, childcontroller.hash_response)
230
+
231
+ @parent.reload
232
+
233
+ assert_predicate(@parent.children, :empty?)
234
+ assert_predicate(Child.where(id: old_children.map(&:id)), :empty?)
235
+
236
+ assert_all_hooks_nested_inside_parent_hook(childcontroller.hook_trace)
237
+ end
238
+
239
+ # direct methods on nested controller
240
+ def test_nested_collection_destroy
241
+ old_child = @parent.children.first
242
+ childcontroller = ChildController.new(params: { id: old_child.id })
243
+ childcontroller.invoke(:destroy)
244
+
245
+ assert_equal(200, childcontroller.status, childcontroller.hash_response)
246
+
247
+ @parent.reload
248
+
249
+ assert_equal(%w[c2], @parent.children.order(:position).pluck(:name))
250
+ assert_predicate(Child.where(id: old_child.id), :empty?)
251
+ end
252
+
253
+ def test_nested_collection_update
254
+ old_child = @parent.children.first
255
+
256
+ data = { 'id' => old_child.id,
257
+ '_type' => 'Child',
258
+ 'name' => 'new_name' }
259
+
260
+ childcontroller = ChildController.new(params: { data: data })
261
+ childcontroller.invoke(:create)
262
+
263
+ assert_equal(200, childcontroller.status, childcontroller.hash_response)
264
+
265
+ old_child.reload
266
+
267
+ assert_equal('new_name', old_child.name)
268
+ assert_equal({ 'data' => ChildView.new(old_child).to_hash },
269
+ childcontroller.hash_response)
270
+ end
271
+
272
+ def test_nested_collection_show
273
+ old_child = @parent.children.first
274
+
275
+ childcontroller = ChildController.new(params: { id: old_child.id })
276
+ childcontroller.invoke(:show)
277
+
278
+ assert_equal({ 'data' => ChildView.new(old_child).to_hash },
279
+ childcontroller.hash_response)
280
+
281
+ assert_equal(200, childcontroller.status)
282
+ end
283
+
284
+ ## Single association
285
+
286
+ def test_nested_singular_replace_from_parent
287
+ old_label = @parent.label
288
+
289
+ data = { '_type' => 'Label', 'text' => 'new label' }
290
+ labelcontroller = LabelController.new(params: {
291
+ owner_viewmodel: 'parent',
292
+ association_name: 'label',
293
+ parent_id: @parent.id,
294
+ data: data,
295
+ })
296
+ labelcontroller.invoke(:create_associated)
297
+
298
+ assert_equal(200, labelcontroller.status, labelcontroller.hash_response)
299
+
300
+ @parent.reload
301
+
302
+ assert_equal({ 'data' => { '_type' => 'Label',
303
+ '_version' => 1,
304
+ 'id' => @parent.label.id,
305
+ 'text' => 'new label' } },
306
+ labelcontroller.hash_response)
307
+
308
+ refute_equal(old_label, @parent.label)
309
+ assert_equal('new label', @parent.label.text)
310
+
311
+ assert_all_hooks_nested_inside_parent_hook(labelcontroller.hook_trace)
312
+ end
313
+
314
+ def test_nested_singular_show_from_parent
315
+ old_label = @parent.label
316
+
317
+ labelcontroller = LabelController.new(params: {
318
+ owner_viewmodel: 'parent',
319
+ association_name: 'label',
320
+ parent_id: @parent.id,
321
+ })
322
+ labelcontroller.invoke(:show_associated)
323
+
324
+ assert_equal(200, labelcontroller.status, labelcontroller.hash_response)
325
+
326
+ assert_equal({ 'data' => LabelView.new(old_label).to_hash },
327
+ labelcontroller.hash_response)
328
+
329
+ assert_all_hooks_nested_inside_parent_hook(labelcontroller.hook_trace)
330
+ end
331
+
332
+ def test_nested_singular_destroy_from_parent
333
+ old_target = @parent.target
334
+
335
+ targetcontroller = TargetController.new(params: {
336
+ owner_viewmodel: 'parent',
337
+ association_name: 'target',
338
+ parent_id: @parent.id,
339
+ })
340
+ targetcontroller.invoke(:destroy_associated)
341
+
342
+ @parent.reload
343
+
344
+ assert_equal(200, targetcontroller.status, targetcontroller.hash_response)
345
+ assert_equal({ 'data' => nil }, targetcontroller.hash_response)
346
+
347
+ assert_nil(@parent.target)
348
+ assert_predicate(Target.where(id: old_target.id), :empty?)
349
+
350
+ assert_all_hooks_nested_inside_parent_hook(targetcontroller.hook_trace)
351
+ end
352
+
353
+ def test_nested_singular_update_from_parent
354
+ old_label = @parent.label
355
+
356
+ data = { '_type' => 'Label', 'id' => old_label.id, 'text' => 'new label' }
357
+ labelcontroller = LabelController.new(params: {
358
+ owner_viewmodel: 'parent',
359
+ association_name: 'label',
360
+ parent_id: @parent.id,
361
+ data: data,
362
+ })
363
+ labelcontroller.invoke(:create_associated)
364
+
365
+ assert_equal(200, labelcontroller.status, labelcontroller.hash_response)
366
+
367
+ old_label.reload
368
+
369
+ assert_equal('new label', old_label.text)
370
+ assert_equal({ 'data' => LabelView.new(old_label).to_hash },
371
+ labelcontroller.hash_response)
372
+
373
+ assert_all_hooks_nested_inside_parent_hook(labelcontroller.hook_trace)
374
+ end
375
+
376
+ def test_nested_singular_show_from_id
377
+ old_label = @parent.label
378
+
379
+ labelcontroller = LabelController.new(params: { id: old_label.id })
380
+ labelcontroller.invoke(:show)
381
+
382
+ assert_equal(200, labelcontroller.status, labelcontroller.hash_response)
383
+
384
+ assert_equal({ 'data' => LabelView.new(old_label).to_hash },
385
+ labelcontroller.hash_response)
386
+ end
387
+
388
+ def test_nested_singular_destroy_from_id
389
+ # can't directly destroy pointed-to label that's referenced from parent:
390
+ # foreign key violation. Destroy target instead.
391
+ old_target = @parent.target
392
+
393
+ targetcontroller = TargetController.new(params: { id: old_target.id })
394
+ targetcontroller.invoke(:destroy)
395
+
396
+ @parent.reload
397
+
398
+ assert_equal(200, targetcontroller.status, targetcontroller.hash_response)
399
+ assert_equal({ 'data' => nil }, targetcontroller.hash_response)
400
+
401
+ assert_nil(@parent.target)
402
+ assert_predicate(Target.where(id: old_target.id), :empty?)
403
+ end
404
+
405
+ def test_nested_singular_update
406
+ old_label = @parent.label
407
+
408
+ data = { '_type' => 'Label', 'id' => old_label.id, 'text' => 'new label' }
409
+ labelcontroller = LabelController.new(params: { data: data })
410
+ labelcontroller.invoke(:create)
411
+
412
+ assert_equal(200, labelcontroller.status, labelcontroller.hash_response)
413
+
414
+ old_label.reload
415
+
416
+ assert_equal('new label', old_label.text)
417
+ assert_equal({ 'data' => LabelView.new(old_label).to_hash },
418
+ labelcontroller.hash_response)
419
+ end
420
+
421
+ def test_nested_singular_replace_bulk
422
+ other_parent = make_parent(name: 'p_other', child_names: ['other_c1', 'other_c2'])
423
+
424
+ target = @parent.target
425
+ other_target = other_parent.target
426
+
427
+ data = {
428
+ '_type' => '_bulk_update',
429
+ 'updates' => [
430
+ {
431
+ 'id' => @parent.id,
432
+ 'update' => {
433
+ '_type' => 'Target',
434
+ 'id' => @parent.target.id,
435
+ 'text' => 'parent, new target text'
436
+ }
437
+ },
438
+ {
439
+ 'id' => other_parent.id,
440
+ 'update' => {
441
+ '_type' => 'Target',
442
+ 'id' => other_parent.target.id,
443
+ 'text' => 'other parent, new target text'
444
+ }
445
+ }
446
+ ],
447
+ }
448
+
449
+ targetcontroller = TargetController.new(params: {
450
+ owner_viewmodel: 'parent',
451
+ association_name: 'target',
452
+ data: data,
453
+ })
454
+
455
+ targetcontroller.invoke(:create_associated_bulk)
456
+
457
+ assert_equal(200, targetcontroller.status, targetcontroller.hash_response)
458
+
459
+ target.reload
460
+ other_target.reload
461
+
462
+ assert_equal('parent, new target text', target.text)
463
+ assert_equal('other parent, new target text', other_target.text)
464
+
465
+ response = targetcontroller.hash_response
466
+ response['data']['updates'].sort_by! { |x| x.fetch('id') }
467
+
468
+ assert_equal(
469
+ {
470
+ 'data' => {
471
+ '_type' => '_bulk_update',
472
+ 'updates' => [
473
+ {
474
+ 'id' => @parent.id,
475
+ 'update' => TargetView.new(target).to_hash,
476
+ },
477
+ {
478
+ 'id' => other_parent.id,
479
+ 'update' => TargetView.new(other_target).to_hash,
480
+ },
481
+ ].sort_by { |x| x.fetch('id') }
482
+ }
483
+ },
484
+ response,
485
+ )
486
+ end
487
+
488
+ # Singular shared
489
+
490
+ def test_nested_shared_singular_replace_bulk
491
+ data = {
492
+ '_type' => '_bulk_update',
493
+ 'updates' => [
494
+ {
495
+ 'id' => @parent.id,
496
+ 'update' => { '_ref' => 'new_cat' },
497
+ }
498
+ ]
499
+ }
500
+
501
+ references = {
502
+ 'new_cat' => {
503
+ '_type' => 'Category',
504
+ '_new' => true,
505
+ 'name' => 'cat name'
506
+ }
507
+ }
508
+
509
+ category_controller = CategoryController.new(params: {
510
+ owner_viewmodel: 'parent',
511
+ association_name: 'category',
512
+ data: data,
513
+ references: references,
514
+ })
515
+
516
+ category_controller.invoke(:replace_bulk)
517
+
518
+ response = category_controller.hash_response
519
+
520
+ data, references = response.values_at('data', 'references')
521
+ ref_key = references.keys.first
522
+
523
+ assert_equal(
524
+ {
525
+ '_type' => '_bulk_update',
526
+ 'updates' => [{
527
+ 'id' => @parent.id,
528
+ 'update' => { '_ref' => ref_key }
529
+ }],
530
+ },
531
+ data,
532
+ )
533
+
534
+ @parent.reload
535
+
536
+ assert_equal(
537
+ {
538
+ ref_key => CategoryView.new(@parent.category).to_hash,
539
+ },
540
+ references,
541
+ )
542
+ end
543
+
544
+ # Collection shared
545
+
546
+ def test_nested_shared_collection_replace_bulk
547
+ data = {
548
+ '_type' => '_bulk_update',
549
+ 'updates' => [
550
+ {
551
+ 'id' => @parent.id,
552
+ 'update' => [{ '_ref' => 'new_tag' }],
553
+ }
554
+ ]
555
+ }
556
+
557
+ references = {
558
+ 'new_tag' => {
559
+ '_type' => 'Tag',
560
+ '_new' => true,
561
+ 'name' => 'tag name'
562
+ }
563
+ }
564
+
565
+ tags_controller = TagController.new(params: {
566
+ owner_viewmodel: 'parent',
567
+ association_name: 'tags',
568
+ data: data,
569
+ references: references,
570
+ })
571
+
572
+ tags_controller.invoke(:replace_bulk)
573
+
574
+ response = tags_controller.hash_response
575
+
576
+ data, references = response.values_at('data', 'references')
577
+ ref_key = references.keys.first
578
+
579
+ assert_equal(
580
+ {
581
+ '_type' => '_bulk_update',
582
+ 'updates' => [{
583
+ 'id' => @parent.id,
584
+ 'update' => [{ '_ref' => ref_key }]
585
+ }],
586
+ },
587
+ data,
588
+ )
589
+
590
+ @parent.reload
591
+
592
+ assert_equal(
593
+ {
594
+ ref_key => TagView.new(@parent.parent_tags.first.tag).to_hash,
595
+ },
596
+ references,
597
+ )
598
+ end
599
+ end