// // RKManagedObjectStoreTest.m // RestKit // // Created by Blake Watters on 7/2/11. // Copyright (c) 2009-2012 RestKit. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #import "RKTestEnvironment.h" #import "RKHuman.h" #import "RKPathUtilities.h" #import "RKSearchIndexer.h" // TODO: Does this become `RKManagedObjectStore managedObjectModelWithName:version:inBundle:` ??? URLForManagedObjectModel static NSURL *RKURLForManagedObjectModelWithNameAtVersion(NSString *modelName, NSUInteger version) { NSString *modelNameAndVersion = (version == 1) ? modelName : [NSString stringWithFormat:@"%@ %ld", modelName, (unsigned long) version]; return [[RKTestFixture fixtureBundle] URLForResource:[NSString stringWithFormat:@"%@.momd/%@", modelName, modelNameAndVersion] withExtension:@"mom"]; } static NSManagedObjectModel *RKManagedObjectModelWithNameAtVersion(NSString *modelName, NSUInteger version) { NSURL *URL = RKURLForManagedObjectModelWithNameAtVersion(modelName, version); NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:URL]; return model; } @interface RKManagedObjectStoreTest : RKTestCase @end @implementation RKManagedObjectStoreTest - (void)setUp { [RKTestFactory setUp]; // Delete any sqlite files in the app data directory NSError *error; NSArray *paths = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:RKApplicationDataDirectory() error:&error]; for (NSString *path in paths) { if ([[path pathExtension] isEqualToString:@"sqlite"]) { NSString *fullPath = [RKApplicationDataDirectory() stringByAppendingPathComponent:path]; BOOL success = [[NSFileManager defaultManager] removeItemAtPath:fullPath error:&error]; NSAssert(success, @"Failed to remove SQLite file at path: %@", fullPath); } } } - (void)tearDown { [RKTestFactory tearDown]; } - (void)testAdditionOfSQLiteStoreRetainsPathOfSeedDatabase { // Create a store with a SQLite database to use as our store NSURL *modelURL = [[RKTestFixture fixtureBundle] URLForResource:@"Data Model" withExtension:@"mom"]; NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; RKManagedObjectStore *seedStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:model]; NSString *seedPath = [RKApplicationDataDirectory() stringByAppendingPathComponent:@"Seed.sqlite"]; NSError *error; NSPersistentStore *persistentStore = [seedStore addSQLitePersistentStoreAtPath:seedPath fromSeedDatabaseAtPath:nil withConfiguration:nil options:nil error:&error]; expect(persistentStore).notTo.beNil(); BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:seedPath]; expect(fileExists).to.beTruthy(); // Create a secondary store using the seed RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:model]; NSString *storePath = [RKApplicationDataDirectory() stringByAppendingPathComponent:@"Test.sqlite"]; persistentStore = [managedObjectStore addSQLitePersistentStoreAtPath:storePath fromSeedDatabaseAtPath:seedPath withConfiguration:nil options:nil error:&error]; expect(persistentStore).notTo.beNil(); fileExists = [[NSFileManager defaultManager] fileExistsAtPath:storePath]; expect(fileExists).to.beTruthy(); // Check that the store has a reference to the seed file option NSString *seedDatabasePath = [[persistentStore options] valueForKey:RKSQLitePersistentStoreSeedDatabasePathOption]; expect(seedDatabasePath).to.equal(seedPath); } - (void)testAddingPersistentSQLiteStoreFromSeedDatabase { // Create a store with an object to serve as our seed database NSURL *modelURL = [[RKTestFixture fixtureBundle] URLForResource:@"Data Model" withExtension:@"mom"]; NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; RKManagedObjectStore *seedStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:model]; NSError *error; NSString *seedPath = [RKApplicationDataDirectory() stringByAppendingPathComponent:@"Seed.sqlite"]; NSPersistentStore *seedPersistentStore = [seedStore addSQLitePersistentStoreAtPath:seedPath fromSeedDatabaseAtPath:nil withConfiguration:nil options:nil error:&error]; assertThat(seedPersistentStore, is(notNilValue())); [seedStore createManagedObjectContexts]; RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:seedStore.persistentStoreManagedObjectContext]; human.name = @"Blake"; BOOL success = [seedStore.mainQueueManagedObjectContext saveToPersistentStore:&error]; assertThatBool(success, is(equalToBool(YES))); NSManagedObjectID *seedObjectID = human.objectID; // Create a secondary store using the first store as the seed NSString *storePath = [RKApplicationDataDirectory() stringByAppendingPathComponent:@"SeededStore.sqlite"]; RKManagedObjectStore *seededStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:model]; NSPersistentStore *persistentStore = [seededStore addSQLitePersistentStoreAtPath:storePath fromSeedDatabaseAtPath:seedPath withConfiguration:nil options:nil error:&error]; assertThat(persistentStore, is(notNilValue())); [seededStore createManagedObjectContexts]; // Get back the seeded object NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Human"]; fetchRequest.predicate = [NSPredicate predicateWithFormat:@"name = %@", @"Blake"]; NSArray *array = [seededStore.persistentStoreManagedObjectContext executeFetchRequest:fetchRequest error:&error]; assertThat(array, isNot(empty())); RKHuman *seededHuman = [array objectAtIndex:0]; assertThat([[seededHuman.objectID URIRepresentation] URLByDeletingLastPathComponent], is(equalTo([[seedObjectID URIRepresentation] URLByDeletingLastPathComponent]))); } - (void)testResetPersistentStoresRecreatesInMemoryStoreThusDeletingAllManagedObjects { NSURL *modelURL = [[RKTestFixture fixtureBundle] URLForResource:@"Data Model" withExtension:@"mom"]; NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:model]; NSError *error; NSPersistentStore *persistentStore = [managedObjectStore addInMemoryPersistentStore:&error]; assertThat(persistentStore, is(notNilValue())); [managedObjectStore createManagedObjectContexts]; RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; human.name = @"Blake"; BOOL success = [managedObjectStore.mainQueueManagedObjectContext saveToPersistentStore:&error]; assertThatBool(success, is(equalToBool(YES))); // Spin the run loop to allow the did save notifications to propogate [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]]; success = [managedObjectStore resetPersistentStores:&error]; assertThatBool(success, is(equalToBool(YES))); // Check that the persistent store has changed NSPersistentStore *newPersistentStore = [managedObjectStore.persistentStoreCoordinator.persistentStores objectAtIndex:0]; assertThat(newPersistentStore, isNot(equalTo(persistentStore))); // Check that the object is gone NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Human"]; fetchRequest.predicate = [NSPredicate predicateWithFormat:@"name = %@", @"Blake"]; NSArray *array = [managedObjectStore.mainQueueManagedObjectContext executeFetchRequest:fetchRequest error:&error]; assertThat(array, is(empty())); } - (void)testResetPersistentStoresRecreatesSQLiteStoreThusDeletingAllManagedObjects { NSURL *modelURL = [[RKTestFixture fixtureBundle] URLForResource:@"Data Model" withExtension:@"mom"]; NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:model]; NSError *error; NSString *storePath = [RKApplicationDataDirectory() stringByAppendingPathComponent:@"Test.sqlite"]; NSPersistentStore *persistentStore = [managedObjectStore addSQLitePersistentStoreAtPath:storePath fromSeedDatabaseAtPath:nil withConfiguration:nil options:nil error:&error]; assertThat(persistentStore, is(notNilValue())); [managedObjectStore createManagedObjectContexts]; RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; human.name = @"Blake"; BOOL success = [managedObjectStore.mainQueueManagedObjectContext saveToPersistentStore:&error]; assertThatBool(success, is(equalToBool(YES))); // Spin the run loop to allow the did save notifications to propogate [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]]; success = [managedObjectStore resetPersistentStores:&error]; assertThatBool(success, is(equalToBool(YES))); // Check that the object is gone NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Human"]; fetchRequest.predicate = [NSPredicate predicateWithFormat:@"name = %@", @"Blake"]; NSArray *array = [managedObjectStore.mainQueueManagedObjectContext executeFetchRequest:fetchRequest error:&error]; assertThat(array, is(empty())); } - (void)testResetPersistentStoreRecreatesSQLiteStoreThusRecreatingTheStoreFileOnDisk { NSURL *modelURL = [[RKTestFixture fixtureBundle] URLForResource:@"Data Model" withExtension:@"mom"]; NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:model]; NSError *error; NSString *storePath = [RKApplicationDataDirectory() stringByAppendingPathComponent:@"Test.sqlite"]; NSPersistentStore *persistentStore = [managedObjectStore addSQLitePersistentStoreAtPath:storePath fromSeedDatabaseAtPath:nil withConfiguration:nil options:nil error:&error]; assertThat(persistentStore, is(notNilValue())); [managedObjectStore createManagedObjectContexts]; NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:storePath error:&error]; assertThat(attributes, is(notNilValue())); NSDate *modificationDate = [attributes objectForKey:NSFileModificationDate]; BOOL success = [managedObjectStore resetPersistentStores:&error]; assertThatBool(success, is(equalToBool(YES))); // Check that the persistent store has changed NSPersistentStore *newPersistentStore = [managedObjectStore.persistentStoreCoordinator.persistentStores objectAtIndex:0]; assertThat(newPersistentStore, isNot(equalTo(persistentStore))); attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:storePath error:&error]; assertThat(attributes, is(notNilValue())); NSDate *newModificationDate = [attributes objectForKey:NSFileModificationDate]; NSDate *laterDate = [modificationDate laterDate:newModificationDate]; assertThat(laterDate, is(equalTo(newModificationDate))); } - (void)testResetPersistentStoreForSQLiteStoreSeededWithDatabaseReclonesTheSeedDatabaseToTheStoreLocation { // Create a store with an object to serve as our seed database NSURL *modelURL = [[RKTestFixture fixtureBundle] URLForResource:@"Data Model" withExtension:@"mom"]; NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; RKManagedObjectStore *seedStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:model]; NSError *error; NSString *seedPath = [RKApplicationDataDirectory() stringByAppendingPathComponent:@"Seed.sqlite"]; NSPersistentStore *seedPersistentStore = [seedStore addSQLitePersistentStoreAtPath:seedPath fromSeedDatabaseAtPath:nil withConfiguration:nil options:nil error:&error]; assertThat(seedPersistentStore, is(notNilValue())); [seedStore createManagedObjectContexts]; RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:seedStore.persistentStoreManagedObjectContext]; human.name = @"Blake"; BOOL success = [seedStore.mainQueueManagedObjectContext saveToPersistentStore:&error]; assertThatBool(success, is(equalToBool(YES))); NSManagedObjectID *seedObjectID = human.objectID; // Create a secondary store using the first store as the seed NSString *storePath = [RKApplicationDataDirectory() stringByAppendingPathComponent:@"SeededStore.sqlite"]; RKManagedObjectStore *seededStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:model]; NSPersistentStore *persistentStore = [seededStore addSQLitePersistentStoreAtPath:storePath fromSeedDatabaseAtPath:seedPath withConfiguration:nil options:nil error:&error]; assertThat(persistentStore, is(notNilValue())); [seededStore createManagedObjectContexts]; // Create a second object in the seeded store RKHuman *human2 = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:seedStore.persistentStoreManagedObjectContext]; human2.name = @"Sarah"; success = [seededStore.mainQueueManagedObjectContext saveToPersistentStore:&error]; assertThatBool(success, is(equalToBool(YES))); // Reset the persistent stores, causing the seed database to be recopied and orphaning the second object success = [seededStore resetPersistentStores:&error]; assertThatBool(success, is(equalToBool(YES))); // Get back the seeded object and check against the seeded object ID NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Human"]; fetchRequest.predicate = [NSPredicate predicateWithFormat:@"name = %@", @"Blake"]; NSArray *array = [seededStore.persistentStoreManagedObjectContext executeFetchRequest:fetchRequest error:&error]; assertThat(array, isNot(empty())); RKHuman *seededHuman = [array objectAtIndex:0]; assertThat([[seededHuman.objectID URIRepresentation] URLByDeletingLastPathComponent], is(equalTo([[seedObjectID URIRepresentation] URLByDeletingLastPathComponent]))); // Check that the secondary object does not exist fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Human"]; fetchRequest.predicate = [NSPredicate predicateWithFormat:@"name = %@", @"Sarah"]; array = [seededStore.persistentStoreManagedObjectContext executeFetchRequest:fetchRequest error:&error]; assertThat(array, is(empty())); } #if __IPHONE_OS_VERSION_MIN_REQUIRED - (void)testThatAddingASQLiteStoreExcludesThePathFromiCloudBackups { NSURL *modelURL = [[RKTestFixture fixtureBundle] URLForResource:@"Data Model" withExtension:@"mom"]; NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:model]; NSError *error; NSString *storePath = [RKApplicationDataDirectory() stringByAppendingPathComponent:@"TestBackupExclusion.sqlite"]; [managedObjectStore addSQLitePersistentStoreAtPath:storePath fromSeedDatabaseAtPath:nil withConfiguration:nil options:nil error:&error]; NSURL *storeURL = [NSURL fileURLWithPath:storePath]; id resourceValue = nil; BOOL success = [storeURL getResourceValue:&resourceValue forKey:NSURLIsExcludedFromBackupKey error:&error]; expect(success).to.beTruthy(); expect(resourceValue).to.equal(@(YES)); } - (void)testResetPersistentStoresDoesNotTriggerDeadlock { RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore]; NSManagedObject *managedObject1 = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.mainQueueManagedObjectContext]; NSManagedObject *managedObject2 = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext]; NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Human"]; [fetchRequest setSortDescriptors:@[ [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES] ]]; NSFetchedResultsController *fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectStore.mainQueueManagedObjectContext sectionNameKeyPath:nil cacheName:nil]; NSError *error = nil; [fetchedResultsController performFetch:&error]; BOOL success = [managedObjectStore resetPersistentStores:&error]; expect(success).to.equal(YES); expect(error).to.beNil(); [fetchedResultsController performFetch:&error]; [managedObject1 setValue:@"Blake" forKey:@"name"]; [managedObject2.managedObjectContext performBlockAndWait:^{ [managedObject2 setValue:@"Blake" forKey:@"name"]; }]; } #endif - (void)testCleanupOfExternalStorageDirectoryOnReset { NSURL *modelURL = [[RKTestFixture fixtureBundle] URLForResource:@"Data Model" withExtension:@"mom"]; NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:model]; NSEntityDescription *humanEntity = [managedObjectStore.managedObjectModel entitiesByName][@"Human"]; NSAttributeDescription *photoAttribute = [[NSAttributeDescription alloc] init]; [photoAttribute setName:@"photo"]; [photoAttribute setAttributeType:NSTransformableAttributeType]; [photoAttribute setAllowsExternalBinaryDataStorage:YES]; NSArray *newProperties = [[humanEntity properties] arrayByAddingObject:photoAttribute]; [humanEntity setProperties:newProperties]; NSError *error = nil; NSString *storePath = [RKApplicationDataDirectory() stringByAppendingPathComponent:@"RKTestsStore.sqlite"]; NSPersistentStore *persistentStore = [managedObjectStore addSQLitePersistentStoreAtPath:storePath fromSeedDatabaseAtPath:nil withConfiguration:nil options:nil error:&error]; assertThat(persistentStore, is(notNilValue())); [managedObjectStore createManagedObjectContexts]; // Check that there is a support directory NSString *supportDirectoryName = [NSString stringWithFormat:@".%@_SUPPORT", [[persistentStore.URL lastPathComponent] stringByDeletingPathExtension]]; NSURL *supportDirectoryFileURL = [NSURL URLWithString:supportDirectoryName relativeToURL:[persistentStore.URL URLByDeletingLastPathComponent]]; BOOL isDirectory = NO; BOOL supportDirectoryExists = [[NSFileManager defaultManager] fileExistsAtPath:[supportDirectoryFileURL path] isDirectory:&isDirectory]; expect(supportDirectoryExists).to.equal(YES); expect(isDirectory).to.equal(YES); NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[supportDirectoryFileURL path] error:&error]; NSDate *creationDate = attributes[NSFileCreationDate]; BOOL success = [managedObjectStore resetPersistentStores:&error]; expect(success).to.equal(YES); attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[supportDirectoryFileURL path] error:&error]; NSDate *newCreationDate = attributes[NSFileCreationDate]; expect([creationDate laterDate:newCreationDate]).to.equal(newCreationDate); } - (void)testThatPersistentStoreWithLongNameHasExternalStorageResetCorrectly { // Create a store with an object to serve as our seed database NSURL *modelURL = [[RKTestFixture fixtureBundle] URLForResource:@"Data Model" withExtension:@"mom"]; NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:model]; NSEntityDescription *humanEntity = [managedObjectStore.managedObjectModel entitiesByName][@"Human"]; NSAttributeDescription *photoAttribute = [[NSAttributeDescription alloc] init]; [photoAttribute setName:@"photo"]; [photoAttribute setAttributeType:NSTransformableAttributeType]; [photoAttribute setAllowsExternalBinaryDataStorage:YES]; NSArray *newProperties = [[humanEntity properties] arrayByAddingObject:photoAttribute]; [humanEntity setProperties:newProperties]; NSError *error = nil; NSString *storePath = [RKApplicationDataDirectory() stringByAppendingPathComponent:@"This is the Store.sqlite"]; NSPersistentStore *persistentStore = [managedObjectStore addSQLitePersistentStoreAtPath:storePath fromSeedDatabaseAtPath:nil withConfiguration:nil options:nil error:&error]; assertThat(persistentStore, is(notNilValue())); [managedObjectStore createManagedObjectContexts]; // Check that there is a support directory NSString *supportDirectoryName = [NSString stringWithFormat:@".%@_SUPPORT", [[persistentStore.URL lastPathComponent] stringByDeletingPathExtension]]; NSString *supportDirectoryPath = [[[[persistentStore URL] path] stringByDeletingLastPathComponent] stringByAppendingPathComponent:supportDirectoryName]; BOOL isDirectory = NO; BOOL supportDirectoryExists = [[NSFileManager defaultManager] fileExistsAtPath:supportDirectoryPath isDirectory:&isDirectory]; expect(supportDirectoryExists).to.equal(YES); expect(isDirectory).to.equal(YES); NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:supportDirectoryPath error:&error]; NSDate *creationDate = attributes[NSFileCreationDate]; BOOL success = [managedObjectStore resetPersistentStores:&error]; expect(success).to.equal(YES); attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:supportDirectoryPath error:&error]; NSDate *newCreationDate = attributes[NSFileCreationDate]; expect([creationDate laterDate:newCreationDate]).to.equal(newCreationDate); } #pragma mark - Versioning Tests - (void)testThatAttemptToMigrateStoreAtNonExistantFileURLReturnsError { NSString *storePath = [RKApplicationDataDirectory() stringByAppendingPathComponent:@"NonExistantStore.sqlite"]; NSURL *storeURL = [NSURL fileURLWithPath:storePath]; NSError *error = nil; NSURL *modelURL = RKURLForManagedObjectModelWithNameAtVersion(@"VersionedModel", 1); BOOL success = [RKManagedObjectStore migratePersistentStoreOfType:NSSQLiteStoreType atURL:storeURL toModelAtURL:modelURL error:&error configuringModelsWithBlock:nil]; expect(success).to.equal(NO); expect(error.code).to.equal(0); } - (void)testThatAttemptingToMigrateToANonVersionedModelReturnsError { NSURL *modelURL = [[RKTestFixture fixtureBundle] URLForResource:@"Data Model" withExtension:@"mom"]; NSManagedObjectModel *model_v1 = RKManagedObjectModelWithNameAtVersion(@"VersionedModel", 1); RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:model_v1]; NSString *storePath = [RKApplicationDataDirectory() stringByAppendingPathComponent:@"TestStore.sqlite"]; NSError *error = nil; NSDictionary *options = @{ NSMigratePersistentStoresAutomaticallyOption: @(NO), NSInferMappingModelAutomaticallyOption: @(NO) }; [managedObjectStore addSQLitePersistentStoreAtPath:storePath fromSeedDatabaseAtPath:nil withConfiguration:nil options:options error:&error]; [managedObjectStore createManagedObjectContexts]; managedObjectStore = nil; // Attempting to update to `modelURL` will fail because this is an unversioned model NSURL *storeURL = [NSURL fileURLWithPath:storePath]; BOOL success = [RKManagedObjectStore migratePersistentStoreOfType:NSSQLiteStoreType atURL:storeURL toModelAtURL:modelURL error:&error configuringModelsWithBlock:nil]; expect(success).to.equal(NO); expect(error).notTo.beNil(); expect(error.code).to.equal(NSMigrationError); assertThat([error localizedDescription], startsWith(@"Migration failed: Migrations can only be performed to versioned destination models contained in a .momd package. Incompatible destination model given at path")); } - (void)testThatAttemptingToMigrateFromAnUnidentifiableSourceModelReturnsError { NSManagedObjectModel *model_v1 = RKManagedObjectModelWithNameAtVersion(@"VersionedModel", 1); NSEntityDescription *extraEntity = [NSEntityDescription new]; [extraEntity setName:@"Extra"]; [model_v1 setEntities:[[model_v1 entities] arrayByAddingObject:extraEntity]]; RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:model_v1]; NSString *storePath = [RKApplicationDataDirectory() stringByAppendingPathComponent:@"TestStore.sqlite"]; NSError *error = nil; NSDictionary *options = @{ NSMigratePersistentStoresAutomaticallyOption: @(NO), NSInferMappingModelAutomaticallyOption: @(NO) }; [managedObjectStore addSQLitePersistentStoreAtPath:storePath fromSeedDatabaseAtPath:nil withConfiguration:nil options:options error:&error]; [managedObjectStore createManagedObjectContexts]; managedObjectStore = nil; // Attempting to upgrade to v2 returns NO, because it can't find the source model (due to the added entity) NSURL *storeURL = [NSURL fileURLWithPath:storePath]; NSURL *modelURL = RKURLForManagedObjectModelWithNameAtVersion(@"VersionedModel", 2); BOOL success = [RKManagedObjectStore migratePersistentStoreOfType:NSSQLiteStoreType atURL:storeURL toModelAtURL:modelURL error:&error configuringModelsWithBlock:nil]; expect(success).to.equal(NO); expect(error).notTo.beNil(); expect(error.code).to.equal(NSMigrationMissingSourceModelError); assertThat([error localizedDescription], startsWith(@"Migration failed: Unable to find the source managed object model used to create the SQLite store at path")); } - (void)testMigrationToCompatibleVersionReturnsYes { NSManagedObjectModel *model_v1 = RKManagedObjectModelWithNameAtVersion(@"VersionedModel", 1); RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:model_v1]; NSString *storePath = [RKApplicationDataDirectory() stringByAppendingPathComponent:@"TestStore.sqlite"]; NSError *error = nil; NSDictionary *options = @{ NSMigratePersistentStoresAutomaticallyOption: @(NO), NSInferMappingModelAutomaticallyOption: @(NO) }; [managedObjectStore addSQLitePersistentStoreAtPath:storePath fromSeedDatabaseAtPath:nil withConfiguration:nil options:options error:&error]; [managedObjectStore createManagedObjectContexts]; managedObjectStore = nil; // Attempting to upgrade to v1 returns YES, because its already compatible NSURL *storeURL = [NSURL fileURLWithPath:storePath]; NSURL *modelURL = RKURLForManagedObjectModelWithNameAtVersion(@"VersionedModel", 1); BOOL success = [RKManagedObjectStore migratePersistentStoreOfType:NSSQLiteStoreType atURL:storeURL toModelAtURL:modelURL error:&error configuringModelsWithBlock:nil]; expect(success).to.equal(YES); expect(error).to.beNil(); } - (void)testUpgradingFromVersionedModelAt1_0to2_0 { // Create a v1 Store NSManagedObjectModel *model_v1 = RKManagedObjectModelWithNameAtVersion(@"VersionedModel", 1); RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:model_v1]; NSString *storePath = [RKApplicationDataDirectory() stringByAppendingPathComponent:@"TestStore.sqlite"]; NSError *error = nil; NSDictionary *options = @{ NSMigratePersistentStoresAutomaticallyOption: @(NO), NSInferMappingModelAutomaticallyOption: @(NO) }; [managedObjectStore addSQLitePersistentStoreAtPath:storePath fromSeedDatabaseAtPath:nil withConfiguration:nil options:options error:&error]; [managedObjectStore createManagedObjectContexts]; managedObjectStore = nil; // Now upgrade it to v2 NSURL *storeURL = [NSURL fileURLWithPath:storePath]; NSURL *modelURL = RKURLForManagedObjectModelWithNameAtVersion(@"VersionedModel", 2); BOOL success = [RKManagedObjectStore migratePersistentStoreOfType:NSSQLiteStoreType atURL:storeURL toModelAtURL:modelURL error:&error configuringModelsWithBlock:nil]; expect(success).to.equal(YES); expect(error).to.beNil(); } - (void)testUpgradingFromVersionedModelAt1_0to_4_0 { // Create a v1 Store NSManagedObjectModel *model_v1 = RKManagedObjectModelWithNameAtVersion(@"VersionedModel", 1); RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:model_v1]; NSString *storePath = [RKApplicationDataDirectory() stringByAppendingPathComponent:@"TestStore.sqlite"]; NSError *error = nil; NSDictionary *options = @{ NSMigratePersistentStoresAutomaticallyOption: @(NO), NSInferMappingModelAutomaticallyOption: @(NO) }; [managedObjectStore addSQLitePersistentStoreAtPath:storePath fromSeedDatabaseAtPath:nil withConfiguration:nil options:options error:&error]; [managedObjectStore createManagedObjectContexts]; managedObjectStore = nil; // Now upgrade it to v4 NSURL *storeURL = [NSURL fileURLWithPath:storePath]; NSURL *modelURL = RKURLForManagedObjectModelWithNameAtVersion(@"VersionedModel", 4); BOOL success = [RKManagedObjectStore migratePersistentStoreOfType:NSSQLiteStoreType atURL:storeURL toModelAtURL:modelURL error:&error configuringModelsWithBlock:nil]; expect(success).to.equal(YES); expect(error).to.beNil(); } - (void)testUpgradingFromVersionedModelWithSearchAttributesAt2_0to_3_0 { // Create a v2 Store NSManagedObjectModel *model_v2 = RKManagedObjectModelWithNameAtVersion(@"VersionedModel", 2); // Add search indexing on the title attribute NSEntityDescription *articleEntity = [[model_v2 entitiesByName] objectForKey:@"Article"]; [RKSearchIndexer addSearchIndexingToEntity:articleEntity onAttributes:@[ @"title" ]]; RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:model_v2]; NSString *storePath = [RKApplicationDataDirectory() stringByAppendingPathComponent:@"TestStore.sqlite"]; NSError *error = nil; NSDictionary *options = @{ NSMigratePersistentStoresAutomaticallyOption: @(NO), NSInferMappingModelAutomaticallyOption: @(NO) }; [managedObjectStore addSQLitePersistentStoreAtPath:storePath fromSeedDatabaseAtPath:nil withConfiguration:nil options:options error:&error]; [managedObjectStore createManagedObjectContexts]; managedObjectStore = nil; // Now upgrade it to v3 NSURL *storeURL = [NSURL fileURLWithPath:storePath]; NSURL *modelURL = RKURLForManagedObjectModelWithNameAtVersion(@"VersionedModel", 4); BOOL success = [RKManagedObjectStore migratePersistentStoreOfType:NSSQLiteStoreType atURL:storeURL toModelAtURL:modelURL error:&error configuringModelsWithBlock:^(NSManagedObjectModel *model, NSURL *sourceURL) { if ([[model versionIdentifiers] isEqualToSet:[NSSet setWithObject:@"2.0"]]) { NSEntityDescription *articleEntity = [[model entitiesByName] objectForKey:@"Article"]; [RKSearchIndexer addSearchIndexingToEntity:articleEntity onAttributes:@[ @"title" ]]; } }]; expect(success).to.equal(YES); expect(error).to.beNil(); } - (void)testUpgradingFromVersionedModelWithSearchAttributesAt_1_0toLatest { // Create a v1 Store NSManagedObjectModel *model_v1 = RKManagedObjectModelWithNameAtVersion(@"VersionedModel", 1); // Add search indexing on the title attribute NSEntityDescription *articleEntity = [[model_v1 entitiesByName] objectForKey:@"Article"]; NSEntityDescription *tagEntity = [[model_v1 entitiesByName] objectForKey:@"Tag"]; [RKSearchIndexer addSearchIndexingToEntity:articleEntity onAttributes:@[ @"title" ]]; [RKSearchIndexer addSearchIndexingToEntity:tagEntity onAttributes:@[ @"name" ]]; RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:model_v1]; NSString *storePath = [RKApplicationDataDirectory() stringByAppendingPathComponent:@"TestStore.sqlite"]; NSError *error = nil; NSDictionary *options = @{ NSMigratePersistentStoresAutomaticallyOption: @(NO), NSInferMappingModelAutomaticallyOption: @(NO) }; [managedObjectStore addSQLitePersistentStoreAtPath:storePath fromSeedDatabaseAtPath:nil withConfiguration:nil options:options error:&error]; [managedObjectStore createManagedObjectContexts]; managedObjectStore = nil; // Now upgrade it to the latest version NSURL *storeURL = [NSURL fileURLWithPath:storePath]; NSURL *modelURL = [[RKTestFixture fixtureBundle] URLForResource:@"VersionedModel" withExtension:@"momd"]; BOOL success = [RKManagedObjectStore migratePersistentStoreOfType:NSSQLiteStoreType atURL:storeURL toModelAtURL:modelURL error:&error configuringModelsWithBlock:^(NSManagedObjectModel *model, NSURL *sourceURL) { if ([[model versionIdentifiers] isEqualToSet:[NSSet setWithObject:@"1.0"]]) { NSEntityDescription *articleEntity = [[model entitiesByName] objectForKey:@"Article"]; NSEntityDescription *tagEntity = [[model entitiesByName] objectForKey:@"Tag"]; [RKSearchIndexer addSearchIndexingToEntity:articleEntity onAttributes:@[ @"title" ]]; [RKSearchIndexer addSearchIndexingToEntity:tagEntity onAttributes:@[ @"name" ]]; } else if ([[model versionIdentifiers] isEqualToSet:[NSSet setWithObject:@"2.0"]]) { NSEntityDescription *articleEntity = [[model entitiesByName] objectForKey:@"Article"]; NSEntityDescription *tagEntity = [[model entitiesByName] objectForKey:@"Tag"]; [RKSearchIndexer addSearchIndexingToEntity:articleEntity onAttributes:@[ @"title", @"body" ]]; [RKSearchIndexer addSearchIndexingToEntity:tagEntity onAttributes:@[ @"name" ]]; } else if ([[model versionIdentifiers] containsObject:@"3.0"] || [[model versionIdentifiers] containsObject:@"4.0"]) { // We index the same attributes on v3 and v4 NSEntityDescription *articleEntity = [[model entitiesByName] objectForKey:@"Article"]; NSEntityDescription *tagEntity = [[model entitiesByName] objectForKey:@"Tag"]; [RKSearchIndexer addSearchIndexingToEntity:articleEntity onAttributes:@[ @"title", @"body", @"authorName" ]]; [RKSearchIndexer addSearchIndexingToEntity:tagEntity onAttributes:@[ @"name" ]]; } }]; expect(success).to.equal(YES); expect(error).to.beNil(); } @end