-
Notifications
You must be signed in to change notification settings - Fork 116
/
Copy pathMigrationTests.swift
2935 lines (2316 loc) · 125 KB
/
MigrationTests.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import XCTest
import CoreData
@testable import Storage
/// Tests for migrating from a specific model version to another.
///
/// Ideally, we should have a test for every new model version. There can also be more than
/// one test between 2 versions if there are many cases being tested.
///
/// ## Notes
///
/// In general, we should avoid using the entity classes like `Product` or `Order`. These classes
/// may **change** in the future. And if they do, the migration tests would have to be changed.
/// There's a risk that the migration tests would no longer be correct if this happens.
///
/// That said, it is understandable that we are sometimes under pressure to finish features that
/// this may not be economical.
///
final class MigrationTests: XCTestCase {
private var modelsInventory: ManagedObjectModelsInventory!
/// URLs of SQLite stores created using `makePersistentStore()`.
///
/// These will be deleted during tear down.
private var createdStoreURLs = Set<URL>()
override func setUpWithError() throws {
try super.setUpWithError()
modelsInventory = try .from(packageName: "WooCommerce", bundle: Bundle(for: CoreDataManager.self))
}
override func tearDownWithError() throws {
let fileManager = FileManager.default
let knownExtensions = ["sqlite-shm", "sqlite-wal"]
try createdStoreURLs.forEach { url in
try fileManager.removeItem(at: url)
try knownExtensions.forEach { ext in
if fileManager.fileExists(atPath: url.appendingPathExtension(ext).path) {
try fileManager.removeItem(at: url.appendingPathExtension(ext))
}
}
}
modelsInventory = nil
try super.tearDownWithError()
}
func test_migrating_from_26_to_27_deletes_ProductCategory_objects() throws {
// Arrange
let sourceContainer = try startPersistentContainer("Model 26")
let sourceContext = sourceContainer.viewContext
insertAccount(to: sourceContext)
let product = insertProduct(to: sourceContext, forModel: 26)
let productCategory = insertProductCategory(to: sourceContext)
product.mutableSetValue(forKey: "categories").add(productCategory)
try sourceContext.save()
XCTAssertEqual(try sourceContext.count(entityName: "Account"), 1)
XCTAssertEqual(try sourceContext.count(entityName: "Product"), 1)
XCTAssertEqual(try sourceContext.count(entityName: "ProductCategory"), 1)
// Action
let targetContainer = try migrate(sourceContainer, to: "Model 27")
let targetContext = targetContainer.viewContext
// Assert
XCTAssertEqual(try targetContext.count(entityName: "Account"), 1)
XCTAssertEqual(try targetContext.count(entityName: "Product"), 1)
// Product categories should be deleted.
XCTAssertEqual(try targetContext.count(entityName: "ProductCategory"), 0)
}
func test_migrating_from_28_to_29_deletes_ProductTag_objects() throws {
// Arrange
let sourceContainer = try startPersistentContainer("Model 28")
let sourceContext = sourceContainer.viewContext
insertAccount(to: sourceContext)
let product = insertProduct(to: sourceContext, forModel: 28)
let productTag = insertProductTag(to: sourceContext)
product.mutableSetValue(forKey: "tags").add(productTag)
try sourceContext.save()
XCTAssertEqual(try sourceContext.count(entityName: "Account"), 1)
XCTAssertEqual(try sourceContext.count(entityName: "Product"), 1)
XCTAssertEqual(try sourceContext.count(entityName: "ProductTag"), 1)
// Action
let targetContainer = try migrate(sourceContainer, to: "Model 29")
// Assert
let targetContext = targetContainer.viewContext
XCTAssertEqual(try targetContext.count(entityName: "Account"), 1)
XCTAssertEqual(try targetContext.count(entityName: "Product"), 1)
// Product tags should be deleted.
XCTAssertEqual(try targetContext.count(entityName: "ProductTag"), 0)
}
func test_migrating_from_20_to_28_will_keep_transformable_attributes() throws {
// Arrange
let sourceContainer = try startPersistentContainer("Model 20")
let sourceContext = sourceContainer.viewContext
let product = insertProduct(to: sourceContext, forModel: 20)
// Populates transformable attributes.
let productCrossSellIDs: [Int64] = [630, 688]
let groupedProductIDs: [Int64] = [94, 134]
let productRelatedIDs: [Int64] = [270, 37]
let productUpsellIDs: [Int64] = [1126, 1216]
let productVariationIDs: [Int64] = [927, 1110]
product.setValue(productCrossSellIDs, forKey: "crossSellIDs")
product.setValue(groupedProductIDs, forKey: "groupedProducts")
product.setValue(productRelatedIDs, forKey: "relatedIDs")
product.setValue(productUpsellIDs, forKey: "upsellIDs")
product.setValue(productVariationIDs, forKey: "variations")
let productAttribute = insertProductAttribute(to: sourceContext)
// Populates transformable attributes.
let attributeOptions = ["Woody", "Andy Panda"]
productAttribute.setValue(attributeOptions, forKey: "options")
product.mutableSetValue(forKey: "attributes").add(productAttribute)
try sourceContext.save()
XCTAssertEqual(try sourceContext.count(entityName: "Product"), 1)
XCTAssertEqual(try sourceContext.count(entityName: "ProductAttribute"), 1)
// Action
let targetContainer = try migrate(sourceContainer, to: "Model 28")
// Assert
let targetContext = targetContainer.viewContext
let persistedProduct = try XCTUnwrap(targetContext.first(entityName: "Product"))
XCTAssertEqual(persistedProduct.value(forKey: "crossSellIDs") as? [Int64], productCrossSellIDs)
XCTAssertEqual(persistedProduct.value(forKey: "groupedProducts") as? [Int64], groupedProductIDs)
XCTAssertEqual(persistedProduct.value(forKey: "relatedIDs") as? [Int64], productRelatedIDs)
XCTAssertEqual(persistedProduct.value(forKey: "upsellIDs") as? [Int64], productUpsellIDs)
XCTAssertEqual(persistedProduct.value(forKey: "variations") as? [Int64], productVariationIDs)
let persistedAttribute = try XCTUnwrap(targetContext.first(entityName: "ProductAttribute"))
XCTAssertEqual(persistedAttribute.value(forKey: "options") as? [String], attributeOptions)
}
func test_migrating_from_31_to_32_renames_Attribute_to_GenericAttribute() throws {
// Given
let sourceContainer = try startPersistentContainer("Model 31")
let sourceContext = sourceContainer.viewContext
let attribute = sourceContext.insert(entityName: "Attribute", properties: [
"id": 9_753_134,
"key": "voluptatem",
"value": "veritatis"
])
let variation = insertProductVariation(to: sourceContainer.viewContext)
variation.mutableOrderedSetValue(forKey: "attributes").add(attribute)
try sourceContext.save()
XCTAssertEqual(try sourceContext.count(entityName: "Attribute"), 1)
XCTAssertEqual(try sourceContext.count(entityName: "ProductVariation"), 1)
// When
let targetContainer = try migrate(sourceContainer, to: "Model 32")
// Then
let targetContext = targetContainer.viewContext
XCTAssertNil(NSEntityDescription.entity(forEntityName: "Attribute", in: targetContext))
XCTAssertEqual(try targetContext.count(entityName: "GenericAttribute"), 1)
XCTAssertEqual(try targetContext.count(entityName: "ProductVariation"), 1)
let migratedAttribute = try XCTUnwrap(targetContext.allObjects(entityName: "GenericAttribute").first)
XCTAssertEqual(migratedAttribute.value(forKey: "id") as? Int, 9_753_134)
XCTAssertEqual(migratedAttribute.value(forKey: "key") as? String, "voluptatem")
XCTAssertEqual(migratedAttribute.value(forKey: "value") as? String, "veritatis")
// The "attributes" relationship should have been migrated too
let migratedVariation = try XCTUnwrap(targetContext.allObjects(entityName: "ProductVariation").first)
let migratedVariationAttributes = migratedVariation.mutableOrderedSetValue(forKey: "attributes")
XCTAssertEqual(migratedVariationAttributes.count, 1)
XCTAssertEqual(migratedVariationAttributes.firstObject as? NSManagedObject, migratedAttribute)
// The migrated attribute can be accessed using the newly renamed `GenericAttribute` class.
let genericAttribute = try XCTUnwrap(targetContext.firstObject(ofType: GenericAttribute.self))
XCTAssertEqual(genericAttribute.id, 9_753_134)
XCTAssertEqual(genericAttribute.key, "voluptatem")
XCTAssertEqual(genericAttribute.value, "veritatis")
}
func test_migrating_from_32_to_33_sets_new_Product_attribute_date_to_dateCreated() throws {
// Given
let sourceContainer = try startPersistentContainer("Model 32")
let sourceContext = sourceContainer.viewContext
let product = insertProduct(to: sourceContext, forModel: 32)
let dateCreated = Date(timeIntervalSince1970: 1603250786)
product.setValue(dateCreated, forKey: "dateCreated")
try sourceContext.save()
XCTAssertEqual(try sourceContext.count(entityName: "Product"), 1)
// When
let targetContainer = try migrate(sourceContainer, to: "Model 33")
// Then
let targetContext = targetContainer.viewContext
XCTAssertEqual(try targetContext.count(entityName: "Product"), 1)
let migratedProduct = try XCTUnwrap(targetContext.first(entityName: "Product"))
XCTAssertEqual(migratedProduct.value(forKey: "date") as? Date, dateCreated)
XCTAssertEqual(migratedProduct.value(forKey: "dateCreated") as? Date, dateCreated)
}
func test_migrating_from_34_to_35_enables_creating_new_OrderItemAttribute_and_adding_to_OrderItem_attributes_relationship() throws {
// Given
let sourceContainer = try startPersistentContainer("Model 34")
let sourceContext = sourceContainer.viewContext
let order = insertOrder(to: sourceContext)
let orderItem = insertOrderItem(to: sourceContext)
orderItem.setValue(order, forKey: "order")
try sourceContext.save()
XCTAssertEqual(try sourceContext.count(entityName: "Order"), 1)
XCTAssertEqual(try sourceContext.count(entityName: "OrderItem"), 1)
// When
let targetContainer = try migrate(sourceContainer, to: "Model 35")
// Then
let targetContext = targetContainer.viewContext
XCTAssertEqual(try targetContext.count(entityName: "Order"), 1)
XCTAssertEqual(try targetContext.count(entityName: "OrderItem"), 1)
XCTAssertEqual(try targetContext.count(entityName: "OrderItemAttribute"), 0)
let migratedOrderItem = try XCTUnwrap(targetContext.first(entityName: "OrderItem"))
// Creates an `OrderItemAttribute` and adds it to `OrderItem`.
let orderItemAttribute = insertOrderItemAttribute(to: targetContext)
migratedOrderItem.setValue(NSOrderedSet(array: [orderItemAttribute]), forKey: "attributes")
try targetContext.save()
XCTAssertEqual(try targetContext.count(entityName: "OrderItemAttribute"), 1)
XCTAssertEqual(migratedOrderItem.value(forKey: "attributes") as? NSOrderedSet, NSOrderedSet(array: [orderItemAttribute]))
}
func test_migrating_from_35_to_36_mantains_values_for_transformable_properties() throws {
// Given
let sourceContainer = try startPersistentContainer("Model 35")
let sourceContext = sourceContainer.viewContext
let product = insertProduct(to: sourceContext, forModel: 35)
product.setValue([1, 2, 3], forKey: "crossSellIDs")
product.setValue([4, 5, 6], forKey: "groupedProducts")
product.setValue([7, 8, 9], forKey: "relatedIDs")
product.setValue([10, 11, 12], forKey: "upsellIDs")
product.setValue([13, 14, 15], forKey: "variations")
try sourceContext.save()
// When
let targetContainer = try migrate(sourceContainer, to: "Model 36")
// Then
let targetContext = targetContainer.viewContext
let migratedProduct = try XCTUnwrap(targetContext.first(entityName: "Product"))
XCTAssertEqual(try targetContext.count(entityName: "Product"), 1)
XCTAssertEqual(migratedProduct.value(forKey: "crossSellIDs") as? [Int64], [1, 2, 3])
XCTAssertEqual(migratedProduct.value(forKey: "groupedProducts") as? [Int64], [4, 5, 6])
XCTAssertEqual(migratedProduct.value(forKey: "relatedIDs") as? [Int64], [7, 8, 9])
XCTAssertEqual(migratedProduct.value(forKey: "upsellIDs") as? [Int64], [10, 11, 12])
XCTAssertEqual(migratedProduct.value(forKey: "variations") as? [Int64], [13, 14, 15])
}
func test_migrating_from_36_to_37_creates_new_paymentMethodID_property_on_order_with_nil_value() throws {
// Given
let sourceContainer = try startPersistentContainer("Model 36")
let sourceContext = sourceContainer.viewContext
let order = insertOrder(to: sourceContainer.viewContext)
try sourceContext.save()
XCTAssertNil(order.entity.attributesByName["paymentMethodID"])
// When
let targetContainer = try migrate(sourceContainer, to: "Model 37")
// Then
let targetContext = targetContainer.viewContext
let migratedOrder = try XCTUnwrap(targetContext.first(entityName: "Order"))
XCTAssertNotNil(migratedOrder.entity.attributesByName["paymentMethodID"])
XCTAssertNil(migratedOrder.value(forKey: "paymentMethodID"))
}
func test_migrating_from_37_to_38_enables_creating_new_shipping_labels_entities() throws {
// Given
let sourceContainer = try startPersistentContainer("Model 37")
let sourceContext = sourceContainer.viewContext
_ = insertOrder(to: sourceContainer.viewContext)
try sourceContext.save()
// When
let targetContainer = try migrate(sourceContainer, to: "Model 38")
// Then
let targetContext = targetContainer.viewContext
XCTAssertEqual(try targetContext.count(entityName: "Order"), 1)
XCTAssertEqual(try targetContext.count(entityName: "ShippingLabel"), 0)
XCTAssertEqual(try targetContext.count(entityName: "ShippingLabelAddress"), 0)
XCTAssertEqual(try targetContext.count(entityName: "ShippingLabelRefund"), 0)
XCTAssertEqual(try targetContext.count(entityName: "ShippingLabelSettings"), 0)
let order = try XCTUnwrap(targetContext.first(entityName: "Order"))
// Creates a `ShippingLabel` with all relationships.
let originAddress = insertShippingLabelAddress(to: targetContext)
let destinationAddress = insertShippingLabelAddress(to: targetContext)
let shippingLabelRefund = insertShippingLabelRefund(to: targetContext)
let shippingLabel = insertShippingLabel(to: targetContext)
shippingLabel.setValue(originAddress, forKey: "originAddress")
shippingLabel.setValue(destinationAddress, forKey: "destinationAddress")
shippingLabel.setValue(shippingLabelRefund, forKey: "refund")
shippingLabel.setValue(order, forKey: "order")
// Creates a `ShippingLabelSettings`.
let shippingLabelSettings = insertShippingLabelSettings(to: targetContext)
shippingLabelSettings.setValue(order, forKey: "order")
XCTAssertNoThrow(try targetContext.save())
XCTAssertEqual(try targetContext.count(entityName: "Order"), 1)
XCTAssertEqual(try targetContext.count(entityName: "ShippingLabel"), 1)
XCTAssertEqual(try targetContext.count(entityName: "ShippingLabelAddress"), 2)
XCTAssertEqual(try targetContext.count(entityName: "ShippingLabelRefund"), 1)
XCTAssertEqual(try targetContext.count(entityName: "ShippingLabelSettings"), 1)
let savedOrder = try XCTUnwrap(targetContext.first(entityName: "Order"))
XCTAssertEqual(savedOrder.value(forKey: "shippingLabels") as? Set<NSManagedObject>, [shippingLabel])
XCTAssertEqual(savedOrder.value(forKey: "shippingLabelSettings") as? NSManagedObject, shippingLabelSettings)
}
func test_migrating_from_38_to_39_creates_new_shipping_lines_relationship_on_refund() throws {
// Given
let sourceContainer = try startPersistentContainer("Model 38")
let sourceContext = sourceContainer.viewContext
let order = insertRefund(to: sourceContext)
try sourceContext.save()
XCTAssertNil(order.entity.relationshipsByName["shippingLines"])
// When
let targetContainer = try migrate(sourceContainer, to: "Model 39")
// Then
let targetContext = targetContainer.viewContext
let migratedRefund = try XCTUnwrap(targetContext.first(entityName: "Refund"))
XCTAssertNotNil(migratedRefund.entity.relationshipsByName["shippingLines"])
XCTAssertEqual(migratedRefund.value(forKey: "supportShippingRefunds") as? Bool, false)
}
func test_migrating_from_39_to_40_deletes_ProductAttribute_objects() throws {
// Arrange
let sourceContainer = try startPersistentContainer("Model 39")
let sourceContext = sourceContainer.viewContext
insertAccount(to: sourceContext)
let product = insertProduct(to: sourceContext, forModel: 39)
let productAttribute = insertProductAttribute(to: sourceContext)
product.mutableSetValue(forKey: "attributes").add(productAttribute)
try sourceContext.save()
XCTAssertEqual(try sourceContext.count(entityName: "Account"), 1)
XCTAssertEqual(try sourceContext.count(entityName: "Product"), 1)
XCTAssertEqual(try sourceContext.count(entityName: "ProductAttribute"), 1)
// Action
let targetContainer = try migrate(sourceContainer, to: "Model 40")
let targetContext = targetContainer.viewContext
// Assert
XCTAssertEqual(try targetContext.count(entityName: "Account"), 1)
XCTAssertEqual(try targetContext.count(entityName: "Product"), 1)
// Product attributes should be deleted.
XCTAssertEqual(try targetContext.count(entityName: "ProductAttribute"), 0)
// The Product.attributes inverse relationship should be gone too.
let migratedProduct = try XCTUnwrap(targetContext.first(entityName: "Product"))
XCTAssertEqual(migratedProduct.mutableSetValue(forKey: "attributes").count, 0)
// We should still be able to add new attributes.
let anotherAttribute = insertProductAttribute(to: targetContext)
migratedProduct.mutableSetValue(forKey: "attributes").add(anotherAttribute)
XCTAssertNoThrow(try targetContext.save())
}
func test_migrating_from_40_to_41_allow_use_to_create_ProductAttribute_terms() throws {
// Given
let sourceContainer = try startPersistentContainer("Model 40")
let sourceContext = sourceContainer.viewContext
insertProductAttribute(to: sourceContext)
try sourceContext.save()
// When
let targetContainer = try migrate(sourceContainer, to: "Model 41")
// Then
let targetContext = targetContainer.viewContext
// Confidence-check
XCTAssertEqual(try targetContext.count(entityName: "ProductAttribute"), 1)
// Test we can add a term to a migrated `ProductAttribute`.
let migratedAttribute = try XCTUnwrap(targetContext.first(entityName: "ProductAttribute"))
let term = insertProductAttributeTerm(to: targetContext)
migratedAttribute.mutableSetValue(forKey: "terms").add(term)
XCTAssertNoThrow(try targetContext.save())
// The ProductAttribute.attribute inverse relationship should be updated.
XCTAssertEqual(term.value(forKey: "attribute") as? NSManagedObject, migratedAttribute)
}
func test_migrating_from_41_to_42_allow_use_to_create_Order_feeLines() throws {
// Given
let sourceContainer = try startPersistentContainer("Model 41")
let sourceContext = sourceContainer.viewContext
insertOrder(to: sourceContext)
try sourceContext.save()
// When
let targetContainer = try migrate(sourceContainer, to: "Model 42")
// Then
let targetContext = targetContainer.viewContext
// Confidence-check
XCTAssertEqual(try targetContext.count(entityName: "Order"), 1)
XCTAssertEqual(try targetContext.count(entityName: "OrderFeeLine"), 0)
// Test we can add a fee to a migrated `Order`.
let migratedOrder = try XCTUnwrap(targetContext.first(entityName: "Order"))
let fee = insertOrderFeeLine(to: targetContext)
migratedOrder.mutableSetValue(forKey: "fees").add(fee)
XCTAssertNoThrow(try targetContext.save())
// Confidence-check
XCTAssertEqual(try targetContext.count(entityName: "OrderFeeLine"), 1)
// The relationship between Order and OrderFeeLine should be updated.
XCTAssertEqual(migratedOrder.value(forKey: "fees") as? Set<NSManagedObject>, [fee])
// The OrderFeeLine.order inverse relationship should be updated.
XCTAssertEqual(fee.value(forKey: "order") as? NSManagedObject, migratedOrder)
}
func test_migrating_from_42_to_43_deletes_SiteVisitStats_and_TopEarnerStats_objects_and_requires_siteID_for_new_objects() throws {
// Arrange
let sourceContainer = try startPersistentContainer("Model 42")
let sourceContext = sourceContainer.viewContext
insertSiteVisitStats(to: sourceContext)
insertTopEarnerStats(to: sourceContext)
try sourceContext.save()
XCTAssertEqual(try sourceContext.count(entityName: "SiteVisitStats"), 1)
XCTAssertEqual(try sourceContext.count(entityName: "TopEarnerStats"), 1)
// Action
let targetContainer = try migrate(sourceContainer, to: "Model 43")
let targetContext = targetContainer.viewContext
// Assert
// Pre-existing `SiteVisitStats` and `TopEarnerStats` objects should be deleted since model version 43 starts requiring a `siteID`.
XCTAssertEqual(try targetContext.count(entityName: "SiteVisitStats"), 0)
XCTAssertEqual(try targetContext.count(entityName: "TopEarnerStats"), 0)
// We should be able to add a new `SiteVisitStats` and `TopEarnerStats` with `siteID`.
let siteVisitStats = insertSiteVisitStats(to: targetContext)
siteVisitStats.setValue(66, forKey: "siteID")
let topEarnerStats = insertTopEarnerStats(to: targetContext)
topEarnerStats.setValue(66, forKey: "siteID")
XCTAssertNoThrow(try targetContext.save())
}
func test_migrating_from_43_to_44_migrates_SiteVisitStats_with_empty_timeRange() throws {
// Arrange
let sourceContainer = try startPersistentContainer("Model 43")
let sourceContext = sourceContainer.viewContext
insertSiteVisitStats(to: sourceContext)
try sourceContext.save()
XCTAssertEqual(try sourceContext.count(entityName: "SiteVisitStats"), 1)
// Action
let targetContainer = try migrate(sourceContainer, to: "Model 44")
let targetContext = targetContainer.viewContext
// Assert
XCTAssertEqual(try targetContext.count(entityName: "SiteVisitStats"), 1)
let migratedSiteVisitStats = try XCTUnwrap(targetContext.first(entityName: "SiteVisitStats"))
XCTAssertEqual(migratedSiteVisitStats.value(forKey: "timeRange") as? String, "")
// We should be able to set `SiteVisitStats`'s `timeRange` to a different value.
migratedSiteVisitStats.setValue("today", forKey: "timeRange")
XCTAssertNoThrow(try targetContext.save())
}
func test_migrating_from_44_to_45_migrates_AccountSettings_with_empty_firstName_and_lastName() throws {
// Arrange
let sourceContainer = try startPersistentContainer("Model 44")
let sourceContext = sourceContainer.viewContext
insertAccountSettingsWithoutFirstNameAndLastName(to: sourceContext)
try sourceContext.save()
XCTAssertEqual(try sourceContext.count(entityName: "AccountSettings"), 1)
// Action
let targetContainer = try migrate(sourceContainer, to: "Model 45")
let targetContext = targetContainer.viewContext
// Assert
XCTAssertEqual(try targetContext.count(entityName: "AccountSettings"), 1)
let migratedSiteVisitStats = try XCTUnwrap(targetContext.first(entityName: "AccountSettings"))
XCTAssertNil(migratedSiteVisitStats.value(forKey: "firstName") as? String)
XCTAssertNil(migratedSiteVisitStats.value(forKey: "lastName") as? String)
// We should be able to set `AccountSetttings`'s `firstName` and `lastName` to a different value.
migratedSiteVisitStats.setValue("Mario", forKey: "firstName")
migratedSiteVisitStats.setValue("Rossi", forKey: "lastName")
XCTAssertNoThrow(try targetContext.save())
}
func test_migrating_from_45_to_46_migrates_ProductVariation_stockQuantity_from_Int64_to_Decimal() throws {
// Arrange
let sourceContainer = try startPersistentContainer("Model 45")
let sourceContext = sourceContainer.viewContext
let productVariation = insertProductVariation(to: sourceContext)
productVariation.setValue(10, forKey: "stockQuantity")
try sourceContext.save()
XCTAssertEqual(try sourceContext.count(entityName: "ProductVariation"), 1)
XCTAssertEqual(productVariation.entity.attributesByName["stockQuantity"]?.attributeType, .integer64AttributeType)
// Action
let targetContainer = try migrate(sourceContainer, to: "Model 46")
let targetContext = targetContainer.viewContext
// Assert
XCTAssertEqual(try targetContext.count(entityName: "ProductVariation"), 1)
// Make sure stock quantity value is migrated as Decimal attribute type
let migratedVariation = try XCTUnwrap(targetContext.first(entityName: "ProductVariation"))
XCTAssertEqual(migratedVariation.entity.attributesByName["stockQuantity"]?.attributeType, .decimalAttributeType)
}
func test_migrating_from_49_to_50_enables_creating_new_sitePlugin_entities() throws {
// Arrange
let sourceContainer = try startPersistentContainer("Model 49")
let sourceContext = sourceContainer.viewContext
try sourceContext.save()
// Action
let targetContainer = try migrate(sourceContainer, to: "Model 50")
let targetContext = targetContainer.viewContext
// Assert
XCTAssertEqual(try targetContext.count(entityName: "SitePlugin"), 0)
let plugin = insertSitePlugin(to: targetContext)
let insertedPlugin = try XCTUnwrap(targetContext.firstObject(ofType: SitePlugin.self))
XCTAssertEqual(try targetContext.count(entityName: "SitePlugin"), 1)
XCTAssertEqual(insertedPlugin, plugin)
}
func test_migrating_from_50_to_51_removes_OrderCount_entities() throws {
// Arrange
let sourceContainer = try startPersistentContainer("Model 50")
let sourceContext = sourceContainer.viewContext
let orderCount = insertOrderCount(to: sourceContext)
let orderCountItem1 = insertOrderCountItem(slug: "processing", to: sourceContext)
let orderCountItem2 = insertOrderCountItem(slug: "completed", to: sourceContext)
orderCount.mutableSetValue(forKey: "items").add(orderCountItem1)
orderCount.mutableSetValue(forKey: "items").add(orderCountItem2)
try sourceContext.save()
XCTAssertEqual(try sourceContext.count(entityName: "OrderCount"), 1)
XCTAssertEqual(try sourceContext.count(entityName: "OrderCountItem"), 2)
let sourceEntitiesNames = sourceContainer.managedObjectModel.entitiesByName.keys
XCTAssertTrue(sourceEntitiesNames.contains("OrderCount"))
XCTAssertTrue(sourceEntitiesNames.contains("OrderCountItem"))
// Action
let targetContainer = try migrate(sourceContainer, to: "Model 51")
let targetEntitiesNames = targetContainer.managedObjectModel.entitiesByName.keys
// Assert
XCTAssertFalse(targetEntitiesNames.contains("OrderCount"))
XCTAssertFalse(targetEntitiesNames.contains("OrderCountItem"))
}
func test_migrating_from_51_to_52_enables_creating_new_paymentGatewayAccount_entities() throws {
// Arrange
let sourceContainer = try startPersistentContainer("Model 51")
let sourceContext = sourceContainer.viewContext
try sourceContext.save()
// Action
let targetContainer = try migrate(sourceContainer, to: "Model 52")
let targetContext = targetContainer.viewContext
// Assert
XCTAssertEqual(try targetContext.count(entityName: "PaymentGatewayAccount"), 0)
let paymentGatewayAccount = insertPaymentGatewayAccount(to: targetContext)
let insertedAccount = try XCTUnwrap(targetContext.firstObject(ofType: PaymentGatewayAccount.self))
XCTAssertEqual(try targetContext.count(entityName: "PaymentGatewayAccount"), 1)
XCTAssertEqual(insertedAccount, paymentGatewayAccount)
}
func test_migrating_from_52_to_53_enables_creating_new_StateOfACountry_and_adding_to_Country_attributes_relationship() throws {
// Arrange
let sourceContainer = try startPersistentContainer("Model 52")
let sourceContext = sourceContainer.viewContext
try sourceContext.save()
// Action
let targetContainer = try migrate(sourceContainer, to: "Model 53")
let targetContext = targetContainer.viewContext
// Assert
XCTAssertEqual(try targetContext.count(entityName: "Country"), 0)
let stateOfCountry1 = insertStateOfACountry(code: "DZ-01", name: "Adrar", to: targetContext)
let stateOfCountry2 = insertStateOfACountry(code: "DZ-02", name: "Chlef", to: targetContext)
let country = insertCountry(to: targetContext)
country.mutableSetValue(forKey: "states").add(stateOfCountry1)
country.mutableSetValue(forKey: "states").add(stateOfCountry2)
let insertedCountry = try XCTUnwrap(targetContext.firstObject(ofType: Country.self))
XCTAssertEqual(try targetContext.count(entityName: "Country"), 1)
XCTAssertEqual(try targetContext.count(entityName: "StateOfACountry"), 2)
XCTAssertEqual(insertedCountry, country)
}
func test_migrating_from_53_to_54_enables_creating_new_systemPlugin_entities() throws {
// Arrange
let sourceContainer = try startPersistentContainer("Model 53")
let sourceContext = sourceContainer.viewContext
try sourceContext.save()
// Action
let targetContainer = try migrate(sourceContainer, to: "Model 54")
let targetContext = targetContainer.viewContext
// Assert
XCTAssertEqual(try targetContext.count(entityName: "SystemPlugin"), 0)
let systemPlugin = insertSystemPlugin(to: targetContext)
let insertedSystemPlugin = try XCTUnwrap(targetContext.firstObject(ofType: SystemPlugin.self))
XCTAssertEqual(try targetContext.count(entityName: "SystemPlugin"), 1)
XCTAssertEqual(insertedSystemPlugin, systemPlugin)
}
func test_migrating_from_54_to_55_adds_new_attribute_commercialInvoiceURL_with_nil_value() throws {
// Given
let sourceContainer = try startPersistentContainer("Model 54")
let sourceContext = sourceContainer.viewContext
let shippingLabel = insertShippingLabel(to: sourceContainer.viewContext)
try sourceContext.save()
XCTAssertNil(shippingLabel.entity.attributesByName["commercialInvoiceURL"])
// When
let targetContainer = try migrate(sourceContainer, to: "Model 55")
// Then
let targetContext = targetContainer.viewContext
let migratedShippingLabel = try XCTUnwrap(targetContext.first(entityName: "ShippingLabel"))
XCTAssertNotNil(migratedShippingLabel.entity.attributesByName["commercialInvoiceURL"])
XCTAssertNil(migratedShippingLabel.value(forKey: "commercialInvoiceURL"))
}
func test_migrating_from_55_to_56_adds_new_systemplugin_attribute_active_with_true_value() throws {
// Given
let sourceContainer = try startPersistentContainer("Model 55")
let sourceContext = sourceContainer.viewContext
let systemPlugin = insertSystemPlugin(to: sourceContainer.viewContext)
try sourceContext.save()
XCTAssertNil(systemPlugin.entity.attributesByName["active"])
// When
let targetContainer = try migrate(sourceContainer, to: "Model 56")
// Then
let targetContext = targetContainer.viewContext
let migratedSystemPlugin = try XCTUnwrap(targetContext.first(entityName: "SystemPlugin"))
XCTAssertNotNil(migratedSystemPlugin.entity.attributesByName["active"])
XCTAssertEqual(migratedSystemPlugin.value(forKey: "active") as? Bool, true)
}
func test_migrating_from_56_to_57_adds_new_PaymentGatewayAccount_attributes() throws {
// Given
let sourceContainer = try startPersistentContainer("Model 56")
let sourceContext = sourceContainer.viewContext
let paymentGatewayAccount = insertPaymentGatewayAccount(to: sourceContainer.viewContext)
try sourceContext.save()
XCTAssertNil(paymentGatewayAccount.entity.attributesByName["isLive"])
XCTAssertNil(paymentGatewayAccount.entity.attributesByName["isInTestMode"])
// When
let targetContainer = try migrate(sourceContainer, to: "Model 57")
// Then
let targetContext = targetContainer.viewContext
let migratedPaymentGatewayAccount = try XCTUnwrap(targetContext.first(entityName: "PaymentGatewayAccount"))
XCTAssertNotNil(migratedPaymentGatewayAccount.entity.attributesByName["isLive"])
XCTAssertEqual(migratedPaymentGatewayAccount.value(forKey: "isLive") as? Bool, true)
XCTAssertNotNil(migratedPaymentGatewayAccount.entity.attributesByName["isInTestMode"])
XCTAssertEqual(migratedPaymentGatewayAccount.value(forKey: "isInTestMode") as? Bool, false)
}
func test_migrating_from_57_to_58_adds_new_site_jetpack_attributes() throws {
// Given
let sourceContainer = try startPersistentContainer("Model 57")
let sourceContext = sourceContainer.viewContext
let site = insertSite(to: sourceContainer.viewContext)
try sourceContext.save()
XCTAssertNil(site.entity.attributesByName["isJetpackConnected"])
XCTAssertNil(site.entity.attributesByName["isJetpackThePluginInstalled"])
// When
let targetContainer = try migrate(sourceContainer, to: "Model 58")
// Then
let targetContext = targetContainer.viewContext
let migratedSite = try XCTUnwrap(targetContext.first(entityName: "Site"))
let isJetpackConnected = try XCTUnwrap(migratedSite.value(forKey: "isJetpackConnected") as? Bool)
XCTAssertFalse(isJetpackConnected)
let isJetpackThePluginInstalled = try XCTUnwrap(migratedSite.value(forKey: "isJetpackThePluginInstalled") as? Bool)
XCTAssertFalse(isJetpackThePluginInstalled)
}
func test_migrating_from_58_to_59_adds_site_jetpack_connection_active_plugins_attribute() throws {
// Given
let sourceContainer = try startPersistentContainer("Model 58")
let sourceContext = sourceContainer.viewContext
let site = insertSite(to: sourceContainer.viewContext)
try sourceContext.save()
XCTAssertNil(site.entity.attributesByName["jetpackConnectionActivePlugins"])
// When
let targetContainer = try migrate(sourceContainer, to: "Model 59")
let targetContext = targetContainer.viewContext
let migratedSite = try XCTUnwrap(targetContext.first(entityName: "Site"))
let defaultJetpackConnectionActivePlugins = migratedSite.value(forKey: "jetpackConnectionActivePlugins")
let plugins = ["jetpack", "woocommerce-payments"]
migratedSite.setValue(plugins, forKey: "jetpackConnectionActivePlugins")
// Then
// Default value is nil.
XCTAssertNil(defaultJetpackConnectionActivePlugins)
let jetpackConnectionActivePlugins = try XCTUnwrap(migratedSite.value(forKey: "jetpackConnectionActivePlugins") as? [String])
XCTAssertEqual(jetpackConnectionActivePlugins, plugins)
}
func test_migrating_from_58_to_59_adds_adminURL_attribute() throws {
// Given
let sourceContainer = try startPersistentContainer("Model 58")
let sourceContext = sourceContainer.viewContext
let site = insertSite(to: sourceContainer.viewContext)
try sourceContext.save()
XCTAssertNil(site.entity.attributesByName["adminURL"])
// When
let targetContainer = try migrate(sourceContainer, to: "Model 59")
let targetContext = targetContainer.viewContext
let migratedSite = try XCTUnwrap(targetContext.first(entityName: "Site"))
let defaultAdminURL = migratedSite.value(forKey: "adminURL")
let adminURL = "https://test.blog/wp-admin"
migratedSite.setValue(adminURL, forKey: "adminURL")
// Then
// Default value is nil.
XCTAssertNil(defaultAdminURL)
let newAdminURL = try XCTUnwrap(migratedSite.value(forKey: "adminURL") as? String)
XCTAssertEqual(newAdminURL, adminURL)
}
func test_migrating_from_59_to_60_adds_order_orderKey_attribute() throws {
// Given
let sourceContainer = try startPersistentContainer("Model 59")
let sourceContext = sourceContainer.viewContext
let site = insertOrder(to: sourceContainer.viewContext)
try sourceContext.save()
XCTAssertNil(site.entity.attributesByName["orderKey"])
// When
let targetContainer = try migrate(sourceContainer, to: "Model 60")
let targetContext = targetContainer.viewContext
let migratedOrder = try XCTUnwrap(targetContext.first(entityName: "Order"))
let defaultOrderKey = migratedOrder.value(forKey: "orderKey")
let orderValue = "frtgyh87654567"
migratedOrder.setValue(orderValue, forKey: "orderKey")
// Then
// Default value is empty
XCTAssertEqual(defaultOrderKey as? String, "")
let newOrderKey = try XCTUnwrap(migratedOrder.value(forKey: "orderKey") as? String)
XCTAssertEqual(newOrderKey, orderValue)
}
func test_migrating_from_59_to_60_enables_creating_new_Coupon() throws {
// Given
let sourceContainer = try startPersistentContainer("Model 59")
let sourceContext = sourceContainer.viewContext
try sourceContext.save()
// When
let targetContainer = try migrate(sourceContainer, to: "Model 60")
// Then
let targetContext = targetContainer.viewContext
XCTAssertEqual(try targetContext.count(entityName: "Coupon"), 0)
// Creates an `Coupon`
let coupon = insertCoupon(to: targetContext)
XCTAssertEqual(try targetContext.count(entityName: "Coupon"), 1)
XCTAssertEqual(try XCTUnwrap(targetContext.firstObject(ofType: Coupon.self)), coupon)
}
func test_migrating_from_60_to_61_adds_tax_lines_as_a_property_to_order() throws {
// Given
let sourceContainer = try startPersistentContainer("Model 60")
let sourceContext = sourceContainer.viewContext
let order = insertOrder(to: sourceContext)
try sourceContext.save()
// `taxes` should not be present before migration
XCTAssertNil(order.entity.relationshipsByName["taxes"])
// When
let targetContainer = try migrate(sourceContainer, to: "Model 61")
// Then
let targetContext = targetContainer.viewContext
// Confidence-check
XCTAssertEqual(try targetContext.count(entityName: "Order"), 1)
XCTAssertEqual(try targetContext.count(entityName: "OrderTaxLine"), 0)
let migratedOrder = try XCTUnwrap(targetContext.first(entityName: "Order"))
// `taxes` should be present in `migratedOrder`
XCTAssertNotNil(migratedOrder.entity.relationshipsByName["taxes"])
// Test adding tax to a migrated `Order`.
let tax = insertOrderTaxLine(to: targetContext)
migratedOrder.mutableSetValue(forKey: "taxes").add(tax)
XCTAssertNoThrow(try targetContext.save())
// Confidence-check
XCTAssertEqual(try targetContext.count(entityName: "OrderTaxLine"), 1)
// The relationship between Order and OrderTaxLine should be updated.
XCTAssertEqual(migratedOrder.value(forKey: "taxes") as? Set<NSManagedObject>, [tax])
// The OrderTaxLine.order inverse relationship should be updated.
XCTAssertEqual(tax.value(forKey: "order") as? NSManagedObject, migratedOrder)
}
func test_migrating_from_61_to_62_adds_new_attribute_searchResults_to_coupon() throws {
// Given
let sourceContainer = try startPersistentContainer("Model 61")
let sourceContext = sourceContainer.viewContext
// `searchResults` should not be present before the migration
let coupon = insertCoupon(to: sourceContext)
XCTAssertNil(coupon.entity.relationshipsByName["searchResults"])
XCTAssertNoThrow(try sourceContext.save())
// When
let targetContainer = try migrate(sourceContainer, to: "Model 62")
// Then
let targetContext = targetContainer.viewContext
let migratedCoupon = try XCTUnwrap(targetContext.first(entityName: "Coupon"))
XCTAssertNotNil(migratedCoupon.entity.relationshipsByName["searchResults"])
// Creates a `CouponSearchResult`
let searchResult = insertCouponSearchResult(to: targetContext)
migratedCoupon.mutableSetValue(forKey: "searchResults").add(searchResult)
XCTAssertNoThrow(try targetContext.save())
XCTAssertEqual(try XCTUnwrap(targetContext.firstObject(ofType: CouponSearchResult.self)), searchResult)
// The relationship between Coupon and CouponSearchResult should be updated.
XCTAssertEqual(migratedCoupon.value(forKey: "searchResults") as? Set<NSManagedObject>, [searchResult])
// The CouponSearchResult.coupons inverse relationship should be updated.
XCTAssertEqual(searchResult.value(forKey: "coupons") as? Set<NSManagedObject>, [migratedCoupon])
}
func test_migrating_from_62_to_63_adds_new_attribute_chargeID_to_order() throws {
// Given
let sourceContainer = try startPersistentContainer("Model 62")
let sourceContext = sourceContainer.viewContext
let site = insertOrder(to: sourceContainer.viewContext)
try sourceContext.save()
XCTAssertNil(site.entity.attributesByName["chargeID"])
// When
let targetContainer = try migrate(sourceContainer, to: "Model 63")
let targetContext = targetContainer.viewContext
let migratedOrder = try XCTUnwrap(targetContext.first(entityName: "Order"))
let defaultChargeID = migratedOrder.value(forKey: "chargeID")
let orderValue = "ch_3KMtak2EdyGr1FMV02G9Qqq1"
migratedOrder.setValue(orderValue, forKey: "chargeID")
// Then
// Default value is nil
XCTAssertNil(defaultChargeID)
let newOrderKey = try XCTUnwrap(migratedOrder.value(forKey: "chargeID") as? String)
XCTAssertEqual(newOrderKey, orderValue)
}
func test_migrating_from_63_to_64_enables_creating_new_InboxNote() throws {
// Given
let sourceContainer = try startPersistentContainer("Model 63")
let sourceContext = sourceContainer.viewContext