@@ -29,46 +29,207 @@ import XCTest
29
29
import CoreStore
30
30
31
31
32
+ // MARK: - MigrationTests
33
+
32
34
final class MigrationTests : BaseTestCase {
33
- func test_ThatCustomSchemaMappingProvider_CanInferTransformation( ) {
34
- struct V1 {
35
- class Animal : CoreStoreObject {
36
- var name = Value . Required< String> ( " name " , initial: " " )
37
- }
35
+
36
+ func test_ThatEntityDescriptionExtension_CanMapAttributes( ) {
37
+
38
+ // Should match attributes by renaming identifier.
39
+ do {
40
+ let src = NSEntityDescription ( [ NSAttributeDescription ( " foo " ) ] )
41
+ let dst = NSEntityDescription ( [ NSAttributeDescription ( " bar " , renamingIdentifier: " foo " ) ] )
42
+
43
+ var map : [ NSAttributeDescription : NSAttributeDescription ] = [ : ]
44
+ XCTAssertNoThrow ( map = try dst. mapAttributes ( in: src) )
45
+ XCTAssertEqual ( map. count, 1 )
46
+ XCTAssertEqual ( map. keys. first? . renamingIdentifier, " foo " )
47
+ XCTAssertEqual ( map. values. first? . name, " foo " )
48
+ }
49
+
50
+ // Should match attributes by name when matching by renaming identifier fails.
51
+ do {
52
+ let src = NSEntityDescription ( [ NSAttributeDescription ( " bar " ) ] )
53
+ let dst = NSEntityDescription ( [ NSAttributeDescription ( " bar " , renamingIdentifier: " foo " ) ] )
54
+
55
+ var map : [ NSAttributeDescription : NSAttributeDescription ] = [ : ]
56
+ XCTAssertNoThrow ( map = try dst. mapAttributes ( in: src) )
57
+ XCTAssertEqual ( map. count, 1 )
58
+ XCTAssertEqual ( map. keys. first? . renamingIdentifier, " foo " )
59
+ XCTAssertEqual ( map. keys. first? . name, " bar " )
60
+ XCTAssertEqual ( map. values. first? . name, " bar " )
61
+ }
62
+
63
+ // Should not throw exception when optional attributes cannot be matched.
64
+ do {
65
+ let src = NSEntityDescription ( [ NSAttributeDescription ( " foo " ) ] )
66
+ let dst = NSEntityDescription ( [ NSAttributeDescription ( " bar " ) ] )
67
+
68
+ var map : [ NSAttributeDescription : NSAttributeDescription ] = [ : ]
69
+ XCTAssertNoThrow ( map = try dst. mapAttributes ( in: src) )
70
+ XCTAssertEqual ( map. count, 0 )
71
+ }
72
+
73
+ // Should not throw exception when required attributes with default value cannot be matched.
74
+ do {
75
+ let src = NSEntityDescription ( [ NSAttributeDescription ( " foo " ) ] )
76
+ let dst = NSEntityDescription ( [ NSAttributeDescription ( " bar " , optional: false , defaultValue: " baz " ) ] )
77
+
78
+ var map : [ NSAttributeDescription : NSAttributeDescription ] = [ : ]
79
+ XCTAssertNoThrow ( map = try dst. mapAttributes ( in: src) )
80
+ XCTAssertEqual ( map. count, 0 )
81
+ }
82
+
83
+ // Should throw exception when required attributes without default value cannot be matched.
84
+ do {
85
+ let src = NSEntityDescription ( [ NSAttributeDescription ( " foo " ) ] )
86
+ let dst = NSEntityDescription ( [ NSAttributeDescription ( " bar " , optional: false ) ] )
87
+ XCTAssertThrowsError ( try dst. mapAttributes ( in: src) )
88
+ }
89
+ }
90
+
91
+ func test_ThatCustomSchemaMappingProvider_CanDeleteAndInsertEntitiesWithCustomEntityMapping( ) {
92
+ class Foo : CoreStoreObject {
93
+ var name = Value . Optional< String> ( " name " )
94
+ }
95
+
96
+ class Bar : CoreStoreObject {
97
+ var nickname = Value . Optional< String> ( " nickname " , renamingIdentifier: " name " )
38
98
}
39
99
40
- struct V2 {
41
- class Animal : CoreStoreObject {
42
- var nickname = Value . Required< String> ( " nickname " , initial: " " , renamingIdentifier: " name " )
43
- }
100
+ let src : CoreStoreSchema = CoreStoreSchema ( modelVersion: " 1 " , entities: [ Entity < Foo > ( " Foo " ) ] )
101
+ let dst : CoreStoreSchema = CoreStoreSchema ( modelVersion: " 2 " , entities: [ Entity < Bar > ( " Bar " ) ] )
102
+
103
+ let migration : CustomSchemaMappingProvider = CustomSchemaMappingProvider ( from: " 1 " , to: " 2 " , entityMappings: [
104
+ . deleteEntity( sourceEntity: " Foo " ) ,
105
+ . insertEntity( destinationEntity: " Bar " )
106
+ ] )
107
+
108
+ /// Create the source store and data set.
109
+ withExtendedLifetime ( DataStack ( src) , { stack in
110
+ try ! stack. addStorageAndWait ( SQLiteStore ( ) )
111
+ try ! stack. perform ( synchronous: { $0. create ( Into < Foo > ( ) ) . name. value = " Willy " } )
112
+ } )
113
+
114
+ let expectation : XCTestExpectation = self . expectation ( description: " migration-did-complete " )
115
+
116
+ withExtendedLifetime ( DataStack ( src, dst, migrationChain: [ " 1 " , " 2 " ] ) , { stack in
117
+ _ = stack. addStorage ( SQLiteStore ( fileURL: SQLiteStore . defaultFileURL, migrationMappingProviders: [ migration] ) , completion: {
118
+ switch $0 {
119
+ case . success( _) :
120
+ XCTAssertEqual ( stack. modelSchema. rawModel ( ) . entities. count, 1 )
121
+ try ! stack. perform ( synchronous: { $0. create ( Into < Bar > ( ) ) . nickname. value = " Bobby " } )
122
+ case . failure( let error) :
123
+ XCTFail ( " \( error) " )
124
+ }
125
+ expectation. fulfill ( )
126
+ } )
127
+ } )
128
+
129
+ self . waitAndCheckExpectations ( )
130
+ }
131
+
132
+ func test_ThatCustomSchemaMappingProvider_CanCopyEntityWithCustomEntityMapping( ) {
133
+ class Foo : CoreStoreObject {
134
+ var name = Value . Required< String> ( " name " , initial: " " )
44
135
}
45
136
46
- let schemaV1 : CoreStoreSchema = CoreStoreSchema ( modelVersion: " V1 " , entities: [ Entity < V1 . Animal > ( " Animal " ) ] )
47
- let schemaV2 : CoreStoreSchema = CoreStoreSchema ( modelVersion: " V2 " , entities: [ Entity < V2 . Animal > ( " Animal " ) ] )
48
- let migration : CustomSchemaMappingProvider = CustomSchemaMappingProvider ( from: " V1 " , to: " V2 " , entityMappings: [ ] )
137
+ // Todo: The way this handles different version locks is flaky… It fails face on the ground in debug, but seems
138
+ // todo: to work fine in production, yet it's not clear if it transforms everything as expected.
139
+
140
+ let src : CoreStoreSchema = CoreStoreSchema ( modelVersion: " 1 " , entities: [ Entity < Foo > ( " Foo " ) ] )
141
+ let dst : CoreStoreSchema = CoreStoreSchema ( modelVersion: " 2 " , entities: [ Entity < Foo > ( " Foo " ) ] )
142
+
143
+ XCTAssertEqual ( dst. rawModel ( ) . entities. first!. versionHash, src. rawModel ( ) . entities. first!. versionHash)
144
+
145
+ let migration : CustomSchemaMappingProvider = CustomSchemaMappingProvider ( from: " 1 " , to: " 2 " , entityMappings: [
146
+ . copyEntity( sourceEntity: " Foo " , destinationEntity: " Foo " )
147
+ ] )
49
148
50
149
/// Create the source store and data set.
51
- withExtendedLifetime ( DataStack ( schemaV1 ) , { stack in
150
+ withExtendedLifetime ( DataStack ( src ) , { stack in
52
151
try ! stack. addStorageAndWait ( SQLiteStore ( ) )
53
- try ! stack. perform ( synchronous: { $0. create ( Into < V1 . Animal > ( ) ) . name. value = " Willy " } )
54
- try ! stack. perform ( synchronous: { XCTAssertEqual ( try ! $0. fetchOne ( From < V1 . Animal > ( ) ) ? . name. value, " Willy " ) } )
152
+ try ! stack. perform ( synchronous: { $0. create ( Into < Foo > ( ) ) . name. value = " Willy " } )
55
153
} )
56
154
57
- let stack : DataStack = DataStack ( schemaV1, schemaV2, migrationChain: [ " V1 " , " V2 " ] )
58
- let store : SQLiteStore = SQLiteStore ( fileURL: SQLiteStore . defaultFileURL, migrationMappingProviders: [ migration] )
155
+ let expectation : XCTestExpectation = self . expectation ( description: " migration-did-complete " )
156
+
157
+ withExtendedLifetime ( DataStack ( src, dst, migrationChain: [ " 1 " , " 2 " ] ) , { stack in
158
+ _ = stack. addStorage ( SQLiteStore ( fileURL: SQLiteStore . defaultFileURL, migrationMappingProviders: [ migration] ) , completion: {
159
+ switch $0 {
160
+ case . success( _) :
161
+ XCTAssertEqual ( stack. modelSchema. rawModel ( ) . entities. count, 1 )
162
+ XCTAssertEqual ( try ! stack. fetchCount ( From < Foo > ( ) ) , 1 )
163
+ try ! stack. perform ( synchronous: { $0. create ( Into < Foo > ( ) ) . name. value = " Bobby " } )
164
+ case . failure( let error) :
165
+ XCTFail ( " \( error) " )
166
+ }
167
+ expectation. fulfill ( )
168
+ } )
169
+ } )
170
+
171
+ self . waitAndCheckExpectations ( )
172
+ }
173
+
174
+ func test_ThatCustomSchemaMappingProvider_CanTransformEntityWithCustomEntityMapping( ) {
175
+ class Foo : CoreStoreObject {
176
+ var name = Value . Required< String> ( " name " , initial: " " )
177
+ var futile = Value . Required< String> ( " futile " , initial: " " )
178
+ }
179
+
180
+ class Bar : CoreStoreObject {
181
+ var firstName = Value . Required< String> ( " firstName " , initial: " " , renamingIdentifier: " name " )
182
+ var lastName = Value . Required< String> ( " lastName " , initial: " " , renamingIdentifier: " placeholder " )
183
+ var age = Value . Required< Int> ( " age " , initial: 18 )
184
+ var gender = Value . Optional< String> ( " gender " )
185
+ }
186
+
187
+ let src : CoreStoreSchema = CoreStoreSchema ( modelVersion: " 1 " , entities: [ Entity < Foo > ( " Foo " ) ] )
188
+ let dst : CoreStoreSchema = CoreStoreSchema ( modelVersion: " 2 " , entities: [ Entity < Bar > ( " Bar " ) ] )
189
+
190
+ let migration : CustomSchemaMappingProvider = CustomSchemaMappingProvider ( from: " 1 " , to: " 2 " , entityMappings: [
191
+ . transformEntity( sourceEntity: " Foo " , destinationEntity: " Bar " , transformer: CustomSchemaMappingProvider . CustomMapping. inferredTransformation)
192
+ ] )
193
+
194
+ /// Create the source store and data set.
195
+ withExtendedLifetime ( DataStack ( src) , { stack in
196
+ try ! stack. addStorageAndWait ( SQLiteStore ( ) )
197
+ try ! stack. perform ( synchronous: { $0. create ( Into < Foo > ( ) ) . name. value = " Willy " } )
198
+ } )
59
199
60
200
let expectation : XCTestExpectation = self . expectation ( description: " migration-did-complete " )
61
201
62
- _ = stack. addStorage ( store, completion: {
63
- switch $0 {
64
- case . success( _) :
65
- XCTAssertEqual ( try ! stack. perform ( synchronous: { try $0. fetchOne ( From < V2 . Animal > ( ) ) ? . nickname. value } ) , " Willy " )
202
+ withExtendedLifetime ( DataStack ( src, dst, migrationChain: [ " 1 " , " 2 " ] ) , { stack in
203
+ _ = stack. addStorage ( SQLiteStore ( fileURL: SQLiteStore . defaultFileURL, migrationMappingProviders: [ migration] ) , completion: {
204
+ switch $0 {
205
+ case . success( _) :
206
+ XCTAssertEqual ( stack. modelSchema. rawModel ( ) . entities. count, 1 )
207
+ XCTAssertEqual ( try ! stack. fetchCount ( From < Bar > ( ) ) , 1 )
208
+ try ! stack. perform ( synchronous: { $0. create ( Into < Bar > ( ) ) . firstName. value = " Bobby " } )
209
+ case . failure( let error) :
210
+ XCTFail ( " \( error) " )
211
+ }
66
212
expectation. fulfill ( )
67
- case . failure( let error) :
68
- XCTFail ( " \( error) " )
69
- }
213
+ } )
70
214
} )
71
215
72
216
self . waitAndCheckExpectations ( )
73
217
}
74
218
}
219
+
220
+ extension NSEntityDescription {
221
+ fileprivate convenience init ( _ properties: [ NSPropertyDescription ] ) {
222
+ self . init ( )
223
+ self . properties = properties
224
+ }
225
+ }
226
+
227
+ extension NSAttributeDescription {
228
+ fileprivate convenience init ( _ name: String , renamingIdentifier: String ? = nil , optional: Bool ? = nil , defaultValue: Any ? = nil ) {
229
+ self . init ( )
230
+ self . name = name
231
+ self . renamingIdentifier = renamingIdentifier
232
+ self . isOptional = optional ?? true
233
+ self . defaultValue = defaultValue
234
+ }
235
+ }
0 commit comments