@@ -29,46 +29,207 @@ import XCTest
2929import CoreStore
3030
3131
32+ // MARK: - MigrationTests
33+
3234final 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 " )
3898 }
3999
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: " " )
44135 }
45136
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+ ] )
49148
50149 /// Create the source store and data set.
51- withExtendedLifetime ( DataStack ( schemaV1 ) , { stack in
150+ withExtendedLifetime ( DataStack ( src ) , { stack in
52151 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 " } )
55153 } )
56154
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+ } )
59199
60200 let expectation : XCTestExpectation = self . expectation ( description: " migration-did-complete " )
61201
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+ }
66212 expectation. fulfill ( )
67- case . failure( let error) :
68- XCTFail ( " \( error) " )
69- }
213+ } )
70214 } )
71215
72216 self . waitAndCheckExpectations ( )
73217 }
74218}
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