diff --git a/Tests/DataFilter/DataFilter.xcdatamodeld/DataFilter.xcdatamodel/contents b/Tests/DataFilter/DataFilter.xcdatamodeld/DataFilter.xcdatamodel/contents new file mode 100755 index 00000000..9c08bfa9 --- /dev/null +++ b/Tests/DataFilter/DataFilter.xcdatamodeld/DataFilter.xcdatamodel/contents @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/DataFilter/DataFilterTests.swift b/Tests/DataFilter/DataFilterTests.swift new file mode 100755 index 00000000..fc6b63b6 --- /dev/null +++ b/Tests/DataFilter/DataFilterTests.swift @@ -0,0 +1,345 @@ +import XCTest +import CoreData +import Sync + +class DataFilterTests: XCTestCase { + @discardableResult func user(remoteID: Int, firstName: String, lastName: String, age: Int, context: NSManagedObjectContext) -> NSManagedObject { + let user = NSEntityDescription.insertNewObject(forEntityName: "User", into: context) + user.setValue(remoteID, forKey: "remoteID") + user.setValue(firstName, forKey: "firstName") + user.setValue(lastName, forKey: "lastName") + user.setValue(age, forKey: "age") + + try! context.save() + + return user + } + + @discardableResult func note(remoteID: String, text: String, context: NSManagedObjectContext) -> NSManagedObject { + let note = NSEntityDescription.insertNewObject(forEntityName: "Note", into: context) + note.setValue(remoteID, forKey: "remoteID") + note.setValue(text, forKey: "text") + + try! context.save() + + return note + } + + func createUsers(context: NSManagedObjectContext) { + self.user(remoteID: 0, firstName: "Amy", lastName: "Juergens", age: 21, context: context) + self.user(remoteID: 1, firstName: "Ben", lastName: "Boykewich", age: 23, context: context) + self.user(remoteID: 2, firstName: "Ricky", lastName: "Underwood", age: 19, context: context) + self.user(remoteID: 3, firstName: "Grace", lastName: "Bowman", age: 20, context: context) + self.user(remoteID: 4, firstName: "Adrian", lastName: "Lee", age: 20, context: context) + } + + func testUsersCount() { + let dataStack = DataStack(modelName: "DataFilter", bundle: Bundle(for: DataFilterTests.self), storeType: .inMemory) + self.createUsers(context: dataStack.mainContext) + + let request = NSFetchRequest(entityName: "User") + let count = try! dataStack.mainContext.count(for: request) + XCTAssertEqual(count, 5) + } + + /* + /* + 5 pre-defined users are inserted, IDs: 0, 1, 2, 3, 4 + In users.json: + - Inserted: 6 and 7 + - Updated: 0, 1, 2 and 3 + - Deleted: 4 + */ + func testMapChangesA() { + let dataStack = DataStack(modelName: "DataFilter", bundle: Bundle(for: DataFilterTests.self), storeType: .inMemory) + dataStack.performInNewBackgroundContext { backgroundContext in + self.createUsers(context: backgroundContext) + + let before = DataObjectIDs.objectIDs(inEntityNamed: "User", withAttributesNamed: "remoteID", context: backgroundContext) + let JSONObjects = try! JSON.from("users.json", bundle: Bundle(for: DataFilterTests.self)) as! [[String: Any]] + var inserted = 0 + var updated = 0 + var deleted = before.count + DataFilter.changes(JSONObjects, inEntityNamed: "User", localPrimaryKey: "remoteID", remotePrimaryKey: "id", context: backgroundContext, inserted: { objectJSON in + inserted += 1 + }, updated: { objectJSON, updatedObject in + updated += 1 + deleted -= 1 + }) + XCTAssertEqual(inserted, 2) + XCTAssertEqual(updated, 4) + XCTAssertEqual(deleted, 1) + } + } + + /* + 5 pre-defined users are inserted, IDs: 0, 1, 2, 3, 4 + In users.json: + - Inserted: 6 and 7 + - Updated: 0, 1, 2 and 3 + - Deleted: 4 + */ + func testMapChangesAWitNull() { + let dataStack = DataStack(modelName: "DataFilter", bundle: Bundle(for: DataFilterTests.self), storeType: .inMemory) + dataStack.performInNewBackgroundContext { backgroundContext in + self.createUsers(context: backgroundContext) + + let before = DataObjectIDs.objectIDs(inEntityNamed: "User", withAttributesNamed: "remoteID", context: backgroundContext) + let JSONObjects = try! JSON.from("users-with-null.json", bundle: Bundle(for: DataFilterTests.self)) as! [[String: Any]] + var inserted = 0 + var updated = 0 + var deleted = before.count + DataFilter.changes(JSONObjects, inEntityNamed: "User", localPrimaryKey: "remoteID", remotePrimaryKey: "id", context: backgroundContext, inserted: { objectJSON in + inserted += 1 + }, updated: { objectJSON, updatedObject in + updated += 1 + deleted -= 1 + }) + XCTAssertEqual(inserted, 2) + XCTAssertEqual(updated, 4) + XCTAssertEqual(deleted, 1) + } + } + + /* + 5 pre-defined users are inserted, IDs: 0, 1, 2, 3, 4 + In users.json: + - Inserted: 6 and 7 + - Updated: 0, 1, 2 and 3 + - Deleted: 4 + */ + func testMapChangesAWithNil() { + let dataStack = DataStack(modelName: "DataFilter", bundle: Bundle(for: DataFilterTests.self), storeType: .inMemory) + dataStack.performInNewBackgroundContext { backgroundContext in + self.createUsers(context: backgroundContext) + + let before = DataObjectIDs.objectIDs(inEntityNamed: "User", withAttributesNamed: "remoteID", context: backgroundContext) + let JSONObjects = try! JSON.from("users-with-nil.json", bundle: Bundle(for: DataFilterTests.self)) as! [[String: Any]] + var inserted = 0 + var updated = 0 + var deleted = before.count + DataFilter.changes(JSONObjects, inEntityNamed: "User", localPrimaryKey: "remoteID", remotePrimaryKey: "id", context: backgroundContext, inserted: { objectJSON in + inserted += 1 + }, updated: { objectJSON, updatedObject in + updated += 1 + deleted -= 1 + }) + XCTAssertEqual(inserted, 2) + XCTAssertEqual(updated, 3) + XCTAssertEqual(deleted, 2) + } + } + + /* + 5 pre-defined users are inserted, IDs: 0, 1, 2, 3, 4 + In users.json: + - Inserted: None + - Updated: 0, 1, 2, 3 and 4 + - Deleted: None + */ + func testMapChangesB() { + let dataStack = DataStack(modelName: "DataFilter", bundle: Bundle(for: DataFilterTests.self), storeType: .inMemory) + dataStack.performInNewBackgroundContext { backgroundContext in + self.createUsers(context: backgroundContext) + + let before = DataObjectIDs.objectIDs(inEntityNamed: "User", withAttributesNamed: "remoteID", context: backgroundContext) + let JSONObjects = try! JSON.from("users2.json", bundle: Bundle(for: DataFilterTests.self)) as! [[String: Any]] + var inserted = 0 + var updated = 0 + var deleted = before.count + DataFilter.changes(JSONObjects, inEntityNamed: "User", localPrimaryKey: "remoteID", remotePrimaryKey: "id", context: backgroundContext, inserted: { objectJSON in + inserted += 1 + }, updated: { objectJSON, updatedObject in + updated += 1 + deleted -= 1 + }) + XCTAssertEqual(inserted, 0) + XCTAssertEqual(updated, 5) + XCTAssertEqual(deleted, 0) + } + } + + /* + 5 pre-defined users are inserted, IDs: 0, 1, 2, 3, 4 + In users.json: + - Inserted: None + - Updated: None + - Deleted: None + */ + func testMapChangesC() { + let dataStack = DataStack(modelName: "DataFilter", bundle: Bundle(for: DataFilterTests.self), storeType: .inMemory) + dataStack.performInNewBackgroundContext { backgroundContext in + self.createUsers(context: backgroundContext) + + let before = DataObjectIDs.objectIDs(inEntityNamed: "User", withAttributesNamed: "remoteID", context: backgroundContext) + let JSONObjects = try! JSON.from("users3.json", bundle: Bundle(for: DataFilterTests.self)) as! [[String: Any]] + var inserted = 0 + var updated = 0 + var deleted = before.count + DataFilter.changes(JSONObjects, inEntityNamed: "User", localPrimaryKey: "remoteID", remotePrimaryKey: "id", context: backgroundContext, inserted: { objectJSON in + inserted += 1 + }, updated: { objectJSON, updatedObject in + updated += 1 + deleted -= 1 + }) + XCTAssertEqual(inserted, 0) + XCTAssertEqual(updated, 0) + XCTAssertEqual(deleted, 5) + } + } + + /* + 5 pre-defined users are inserted, IDs: 0, 1, 2, 3, 4 + After the pre-defined ones, we try to insert the user 0 many times. + In users.json: + - Inserted: 6 and 7 + - Updated: 0, 1, 2 and 3 + - Deleted: 4 + */ + func testUniquing() { + let dataStack = DataStack(modelName: "DataFilter", bundle: Bundle(for: DataFilterTests.self), storeType: .inMemory) + dataStack.performInNewBackgroundContext { backgroundContext in + self.createUsers(context: backgroundContext) + + self.user(remoteID: 0, firstName: "Amy", lastName: "Juergens", age: 21, context: backgroundContext) + self.user(remoteID: 0, firstName: "Amy", lastName: "Juergens", age: 21, context: backgroundContext) + self.user(remoteID: 0, firstName: "Amy", lastName: "Juergens", age: 21, context: backgroundContext) + try! backgroundContext.save() + + let request = NSFetchRequest(entityName: "User") + let numberOfUsers = try! backgroundContext.count(for: request) + XCTAssertEqual(numberOfUsers, 8) + + let JSONObjects = try! JSON.from("users.json", bundle: Bundle(for: DataFilterTests.self)) as! [[String: Any]] + DataFilter.changes(JSONObjects, inEntityNamed: "User", localPrimaryKey: "remoteID", remotePrimaryKey: "id", context: backgroundContext, inserted: { objectJSON in + }, updated: { objectJSON, updatedObject in + }) + + let deletedNumberOfUsers = try! backgroundContext.count(for: request) + XCTAssertEqual(deletedNumberOfUsers, 4) + } + } + + /* + 1 pre-defined none is inserted with id "123" + In notes.json: + - Inserted: 0 + - Updated: "123" + - Deleted: 0 + */ + func testStringID() { + let dataStack = DataStack(modelName: "DataFilter", bundle: Bundle(for: DataFilterTests.self), storeType: .inMemory) + dataStack.performInNewBackgroundContext { backgroundContext in + self.note(remoteID: "123", text: "text", context: backgroundContext) + try! backgroundContext.save() + + let request = NSFetchRequest(entityName: "Note") + let count = try! backgroundContext.count(for: request) + XCTAssertEqual(count, 1) + + let JSONObjects = try! JSON.from("note.json", bundle: Bundle(for: DataFilterTests.self)) as! [[String: Any]] + DataFilter.changes(JSONObjects, inEntityNamed: "Note", localPrimaryKey: "remoteID", remotePrimaryKey: "id", context: backgroundContext, inserted: { objectJSON in + XCTAssertFalse(true) + }, updated: { objectJSON, updatedObject in + XCTAssertEqual(objectJSON["id"] as? String, "123") + }) + } + } + + func testInsertOnly() { + let dataStack = DataStack(modelName: "DataFilter", bundle: Bundle(for: DataFilterTests.self), storeType: .inMemory) + dataStack.performInNewBackgroundContext { backgroundContext in + self.user(remoteID: 0, firstName: "Amy", lastName: "Juergens", age: 21, context: backgroundContext) + self.user(remoteID: 1, firstName: "Ben", lastName: "Boykewich", age: 23, context: backgroundContext) + + let before = DataObjectIDs.objectIDs(inEntityNamed: "User", withAttributesNamed: "remoteID", context: backgroundContext) + let JSONObjects = try! JSON.from("simple.json", bundle: Bundle(for: DataFilterTests.self)) as! [[String: Any]] + var inserted = 0 + var updated = 0 + var deleted = before.count + DataFilter.changes(JSONObjects, inEntityNamed: "User", predicate: nil, operations: [.Insert], localPrimaryKey: "remoteID", remotePrimaryKey: "id", context: backgroundContext, inserted: { objectJSON in + inserted += 1 + }, updated: { objectJSON, updatedObject in + updated += 1 + deleted -= 1 + }) + XCTAssertEqual(inserted, 1) + XCTAssertEqual(updated, 0) + XCTAssertEqual(deleted, 2) + } + } + + func testUpdateOnly() { + let dataStack = DataStack(modelName: "DataFilter", bundle: Bundle(for: DataFilterTests.self), storeType: .inMemory) + dataStack.performInNewBackgroundContext { backgroundContext in + self.user(remoteID: 0, firstName: "Amy", lastName: "Juergens", age: 21, context: backgroundContext) + self.user(remoteID: 1, firstName: "Ben", lastName: "Boykewich", age: 23, context: backgroundContext) + + let before = DataObjectIDs.objectIDs(inEntityNamed: "User", withAttributesNamed: "remoteID", context: backgroundContext) + let JSONObjects = try! JSON.from("simple.json", bundle: Bundle(for: DataFilterTests.self)) as! [[String: Any]] + var inserted = 0 + var updated = 0 + var deleted = before.count + DataFilter.changes(JSONObjects, inEntityNamed: "User", predicate: nil, operations: [.Update], localPrimaryKey: "remoteID", remotePrimaryKey: "id", context: backgroundContext, inserted: { objectJSON in + inserted += 1 + }, updated: { objectJSON, updatedObject in + updated += 1 + deleted -= 1 + }) + XCTAssertEqual(inserted, 0) + XCTAssertEqual(updated, 1) + XCTAssertEqual(deleted, 1) + } + } + + func testDeleteOnly() { + let dataStack = DataStack(modelName: "DataFilter", bundle: Bundle(for: DataFilterTests.self), storeType: .inMemory) + dataStack.performInNewBackgroundContext { backgroundContext in + self.user(remoteID: 0, firstName: "Amy", lastName: "Juergens", age: 21, context: backgroundContext) + self.user(remoteID: 1, firstName: "Ben", lastName: "Boykewich", age: 23, context: backgroundContext) + + let before = DataObjectIDs.objectIDs(inEntityNamed: "User", withAttributesNamed: "remoteID", context: backgroundContext) + let JSONObjects = try! JSON.from("simple.json", bundle: Bundle(for: DataFilterTests.self)) as! [[String: Any]] + var inserted = 0 + var updated = 0 + var deleted = before.count + DataFilter.changes(JSONObjects, inEntityNamed: "User", predicate: nil, operations: [.Delete], localPrimaryKey: "remoteID", remotePrimaryKey: "id", context: backgroundContext, inserted: { objectJSON in + inserted += 1 + }, updated: { objectJSON, updatedObject in + updated += 1 + deleted -= 1 + }) + XCTAssertEqual(inserted, 0) + XCTAssertEqual(updated, 0) + XCTAssertEqual(deleted, 2) + } + } + + /* + 5 pre-defined users are inserted, IDs: 0, 1, 2, 3, 4 + The predicate "remoteID == 1" means that we will only compare the users.json with + the set existing ID: 1, meaning that if an item with ID: 2 appears, then this item will be inserted. + */ + func testPredicate() { + let dataStack = DataStack(modelName: "DataFilter", bundle: Bundle(for: DataFilterTests.self), storeType: .inMemory) + dataStack.performInNewBackgroundContext { backgroundContext in + self.createUsers(context: backgroundContext) + + let before = DataObjectIDs.objectIDs(inEntityNamed: "User", withAttributesNamed: "remoteID", context: backgroundContext) + let JSONObjects = try! JSON.from("users.json", bundle: Bundle(for: DataFilterTests.self)) as! [[String: Any]] + var inserted = 0 + var updated = 0 + var deleted = before.count + DataFilter.changes(JSONObjects, inEntityNamed: "User", predicate: NSPredicate(format: "remoteID == \(0)"), operations: [.All], localPrimaryKey: "remoteID", remotePrimaryKey: "id", context: backgroundContext, inserted: { objectJSON in + inserted += 1 + }, updated: { objectJSON, updatedObject in + updated += 1 + deleted -= 1 + }) + XCTAssertEqual(inserted, 5) + XCTAssertEqual(updated, 1) + XCTAssertEqual(deleted, 4) + } + } + */ +} diff --git a/Tests/DataFilter/note.json b/Tests/DataFilter/note.json new file mode 100755 index 00000000..6f781673 --- /dev/null +++ b/Tests/DataFilter/note.json @@ -0,0 +1,6 @@ +[ + { + "id": "123", + "text": "text" + } +] diff --git a/Tests/DataFilter/simple.json b/Tests/DataFilter/simple.json new file mode 100755 index 00000000..1d5705b2 --- /dev/null +++ b/Tests/DataFilter/simple.json @@ -0,0 +1,14 @@ +[ + { + "id": 0, + "first_name": "Amy", + "last_name": "Juergens", + "age": 21 + }, + { + "id": 7, + "first_name": "Lauren", + "last_name": "Treacy", + "age": 28 + }, +] diff --git a/Tests/DataFilter/users-with-nil.json b/Tests/DataFilter/users-with-nil.json new file mode 100755 index 00000000..c6769d0d --- /dev/null +++ b/Tests/DataFilter/users-with-nil.json @@ -0,0 +1,34 @@ +[ + { + "id": 0, + "first_name": "Amy", + "last_name": "Juergens", + "age": 21 + }, + { + "id": 1, + "first_name": "Ben", + "last_name": "Boykewich", + "age": 23 + }, + { + "id": 2, + "first_name": "Ricky", + "last_name": "Underwood", + "age": 19 + }, + { + }, + { + "id": 6, + "first_name": "Ashley", + "last_name": "Juergens", + "age": 18 + }, + { + "id": 7, + "first_name": "Lauren", + "last_name": "Treacy", + "age": 28 + }, +] diff --git a/Tests/DataFilter/users-with-null.json b/Tests/DataFilter/users-with-null.json new file mode 100755 index 00000000..aec78409 --- /dev/null +++ b/Tests/DataFilter/users-with-null.json @@ -0,0 +1,41 @@ +[ + { + "id": null + }, + { + "id": 0, + "first_name": "Amy", + "last_name": "Juergens", + "age": 21 + }, + { + "id": 1, + "first_name": "Ben", + "last_name": "Boykewich", + "age": 23 + }, + { + "id": 2, + "first_name": "Ricky", + "last_name": "Underwood", + "age": 19 + }, + { + "id": 3, + "first_name": "Grace", + "last_name": "Bowman", + "age": 20 + }, + { + "id": 6, + "first_name": "Ashley", + "last_name": "Juergens", + "age": 18 + }, + { + "id": 7, + "first_name": "Lauren", + "last_name": "Treacy", + "age": 28 + }, +] diff --git a/Tests/DataFilter/users.json b/Tests/DataFilter/users.json new file mode 100755 index 00000000..c626de66 --- /dev/null +++ b/Tests/DataFilter/users.json @@ -0,0 +1,38 @@ +[ + { + "id": 0, + "first_name": "Amy", + "last_name": "Juergens", + "age": 21 + }, + { + "id": 1, + "first_name": "Ben", + "last_name": "Boykewich", + "age": 23 + }, + { + "id": 2, + "first_name": "Ricky", + "last_name": "Underwood", + "age": 19 + }, + { + "id": 3, + "first_name": "Grace", + "last_name": "Bowman", + "age": 20 + }, + { + "id": 6, + "first_name": "Ashley", + "last_name": "Juergens", + "age": 18 + }, + { + "id": 7, + "first_name": "Lauren", + "last_name": "Treacy", + "age": 28 + }, +] diff --git a/Tests/DataFilter/users2.json b/Tests/DataFilter/users2.json new file mode 100755 index 00000000..12b96319 --- /dev/null +++ b/Tests/DataFilter/users2.json @@ -0,0 +1,32 @@ +[ + { + "id": 0, + "first_name": "Amy", + "last_name": "Juergens", + "age": 21 + }, + { + "id": 1, + "first_name": "Ben", + "last_name": "Boykewich", + "age": 23 + }, + { + "id": 2, + "first_name": "Ricky", + "last_name": "Underwood", + "age": 19 + }, + { + "id": 3, + "first_name": "Grace", + "last_name": "Bowman", + "age": 20 + }, + { + "id": 4, + "first_name": "Name", + "last_name": "Last", + "age": 20 + } +] diff --git a/Tests/DataFilter/users3.json b/Tests/DataFilter/users3.json new file mode 100755 index 00000000..0d4f101c --- /dev/null +++ b/Tests/DataFilter/users3.json @@ -0,0 +1,2 @@ +[ +] diff --git a/Tests/DataStack/LightweightMigrationModel.xcdatamodel/contents b/Tests/DataStack/LightweightMigrationModel.xcdatamodel/contents new file mode 100755 index 00000000..e63b23e9 --- /dev/null +++ b/Tests/DataStack/LightweightMigrationModel.xcdatamodel/contents @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/DataStack/ModelGroup.xcdatamodeld/ModelGroup.xcdatamodel/contents b/Tests/DataStack/ModelGroup.xcdatamodeld/ModelGroup.xcdatamodel/contents new file mode 100755 index 00000000..01cb57e5 --- /dev/null +++ b/Tests/DataStack/ModelGroup.xcdatamodeld/ModelGroup.xcdatamodel/contents @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/DataStack/SimpleModel.xcdatamodel/contents b/Tests/DataStack/SimpleModel.xcdatamodel/contents new file mode 100755 index 00000000..8a2f689d --- /dev/null +++ b/Tests/DataStack/SimpleModel.xcdatamodel/contents @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/DataStack/Tests.swift b/Tests/DataStack/Tests.swift new file mode 100755 index 00000000..29269561 --- /dev/null +++ b/Tests/DataStack/Tests.swift @@ -0,0 +1,176 @@ +import XCTest +import CoreData +@testable import Sync + +extension XCTestCase { + func createDataStack(_ storeType: DataStackStoreType = .inMemory) -> DataStack { + let dataStack = DataStack(modelName: "ModelGroup", bundle: Bundle(for: Tests.self), storeType: storeType) + + return dataStack + } + + @discardableResult + func insertUser(in context: NSManagedObjectContext) -> NSManagedObject { + let user = NSEntityDescription.insertNewObject(forEntityName: "User", into: context) + user.setValue(NSNumber(value: 1), forKey: "remoteID") + user.setValue("Joshua Ivanof", forKey: "name") + try! context.save() + + return user + } + + func fetch(in context: NSManagedObjectContext) -> [NSManagedObject] { + let request = NSFetchRequest(entityName: "User") + let objects = try! context.fetch(request) + + return objects + } +} + +class InitializerTests: XCTestCase { + func testInitializeUsingXCDataModel() { + let dataStack = DataStack(modelName: "SimpleModel", bundle: Bundle(for: Tests.self), storeType: .inMemory) + + self.insertUser(in: dataStack.mainContext) + let objects = self.fetch(in: dataStack.mainContext) + XCTAssertEqual(objects.count, 1) + } + + // xcdatamodeld is a container for .xcdatamodel files. It's used for versioning and migration. + // When moving from v1 of the model to v2, you add a new xcdatamodel to it that has v2 along with the mapping model. + func testInitializeUsingXCDataModeld() { + let dataStack = self.createDataStack() + + self.insertUser(in: dataStack.mainContext) + let objects = self.fetch(in: dataStack.mainContext) + XCTAssertEqual(objects.count, 1) + } + + func testInitializingUsingNSManagedObjectModel() { + let model = NSManagedObjectModel(bundle: Bundle(for: Tests.self), name: "ModelGroup") + let dataStack = DataStack(model: model, storeType: .inMemory) + + self.insertUser(in: dataStack.mainContext) + let objects = self.fetch(in: dataStack.mainContext) + XCTAssertEqual(objects.count, 1) + } +} + +class Tests: XCTestCase { + func testSynchronousBackgroundContext() { + let dataStack = self.createDataStack() + + var synchronous = false + dataStack.performInNewBackgroundContext { _ in + synchronous = true + } + + XCTAssertTrue(synchronous) + } + + func testBackgroundContextSave() { + let dataStack = self.createDataStack() + + dataStack.performInNewBackgroundContext { backgroundContext in + self.insertUser(in: backgroundContext) + + let objects = self.fetch(in: backgroundContext) + XCTAssertEqual(objects.count, 1) + } + + let objects = self.fetch(in: dataStack.mainContext) + XCTAssertEqual(objects.count, 1) + } + + func testNewBackgroundContextSave() { + var synchronous = false + let dataStack = self.createDataStack() + let backgroundContext = dataStack.newBackgroundContext() + backgroundContext.performAndWait { + synchronous = true + self.insertUser(in: backgroundContext) + let objects = self.fetch(in: backgroundContext) + XCTAssertEqual(objects.count, 1) + } + + let objects = self.fetch(in: dataStack.mainContext) + XCTAssertEqual(objects.count, 1) + + XCTAssertTrue(synchronous) + } + + func testRequestWithDictionaryResultType() { + let dataStack = self.createDataStack() + self.insertUser(in: dataStack.mainContext) + + let request = NSFetchRequest(entityName: "User") + let objects = try! dataStack.mainContext.fetch(request) + XCTAssertEqual(objects.count, 1) + + let expression = NSExpressionDescription() + expression.name = "objectID" + expression.expression = NSExpression.expressionForEvaluatedObject() + expression.expressionResultType = .objectIDAttributeType + + let dictionaryRequest = NSFetchRequest(entityName: "User") + dictionaryRequest.resultType = .dictionaryResultType + dictionaryRequest.propertiesToFetch = [expression, "remoteID"] + + let dictionaryObjects = try! dataStack.mainContext.fetch(dictionaryRequest) + XCTAssertEqual(dictionaryObjects.count, 1) + } + + func testDisposableContextSave() { + let dataStack = self.createDataStack() + + let disposableContext = dataStack.newDisposableMainContext() + self.insertUser(in: disposableContext) + let objects = self.fetch(in: disposableContext) + XCTAssertEqual(objects.count, 0) + } + + func testDrop() { + let dataStack = self.createDataStack(.sqLite) + + dataStack.performInNewBackgroundContext { backgroundContext in + self.insertUser(in: backgroundContext) + } + + let objectsA = self.fetch(in: dataStack.mainContext) + XCTAssertEqual(objectsA.count, 1) + + dataStack.drop() + + let objects = self.fetch(in: dataStack.mainContext) + XCTAssertEqual(objects.count, 0) + + dataStack.performInNewBackgroundContext { backgroundContext in + self.insertUser(in: backgroundContext) + } + + let objectsB = self.fetch(in: dataStack.mainContext) + XCTAssertEqual(objectsB.count, 1) + + dataStack.drop() + } + + func testAutomaticMigration() { + let firstDataStack = DataStack(modelName: "SimpleModel", bundle: Bundle(for: Tests.self), storeType: .sqLite, storeName: "Shared") + self.insertUser(in: firstDataStack.mainContext) + let objects = self.fetch(in: firstDataStack.mainContext) + XCTAssertEqual(objects.count, 1) + + // LightweightMigrationModel is a copy of DataModel with the main difference that adds the updatedDate attribute. + let secondDataStack = DataStack(modelName: "LightweightMigrationModel", bundle: Bundle(for: Tests.self), storeType: .sqLite, storeName: "Shared") + let fetchRequest = NSFetchRequest(entityName: "User") + fetchRequest.predicate = NSPredicate(format: "remoteID = %@", NSNumber(value: 1)) + let user = try! secondDataStack.mainContext.fetch(fetchRequest).first + XCTAssertNotNil(user) + XCTAssertEqual(user?.value(forKey: "name") as? String, "Joshua Ivanof") + user?.setValue(Date().addingTimeInterval(16000), forKey: "updatedDate") + try! secondDataStack.mainContext.save() + + firstDataStack.drop() + secondDataStack.drop() + } +} diff --git a/Tests/DateParser/DateTests.swift b/Tests/DateParser/DateTests.swift new file mode 100755 index 00000000..751e1dd6 --- /dev/null +++ b/Tests/DateParser/DateTests.swift @@ -0,0 +1,171 @@ +import XCTest +import Sync + +class DateTests: XCTestCase { + + func testDateA() { + let date = Date.dateWithHourAndTimeZoneString(dateString: "2015-06-23T12:40:08.000") + let resultDate = NSDate(fromDateString: "2015-06-23T14:40:08.000+02:00") as! Date + + XCTAssertNotNil(resultDate) + XCTAssertEqual(date.timeIntervalSince1970, resultDate.timeIntervalSince1970) + } + + func testDateB() { + let date = Date.dateWithDayString(dateString: "2014-01-01") + let resultDate = NSDate(fromDateString: "2014-01-01T00:00:00+00:00") as! Date + + XCTAssertNotNil(resultDate) + XCTAssertEqual(date, resultDate) + } + + func testDateC() { + let date = Date.dateWithDayString(dateString: "2014-01-02") + let resultDate = NSDate(fromDateString: "2014-01-02") as! Date + + XCTAssertNotNil(resultDate) + XCTAssertEqual(date, resultDate) + } + + func testDateD() { + let date = Date.dateWithDayString(dateString: "2014-01-02") + let resultDate = NSDate(fromDateString: "2014-01-02T00:00:00.000000+00:00") as! Date + + XCTAssertNotNil(resultDate) + XCTAssertEqual(date, resultDate) + } + + func testDateE() { + let date = Date.dateWithDayString(dateString: "2015-09-10") + let resultDate = NSDate(fromDateString: "2015-09-10T00:00:00.116+0000") as! Date + + XCTAssertNotNil(resultDate) + XCTAssertEqual(date, resultDate) + } + + func testDateF() { + let date = Date.dateWithDayString(dateString: "2015-09-10") + let resultDate = NSDate(fromDateString: "2015-09-10T00:00:00.184968Z") as! Date + + XCTAssertNotNil(resultDate) + XCTAssertEqual(date, resultDate) + } + + func testDateG() { + let date = Date.dateWithHourAndTimeZoneString(dateString: "2015-06-23T19:04:19.911Z") + let resultDate = NSDate(fromDateString: "2015-06-23T19:04:19.911Z") as! Date + print(date.timeIntervalSince1970) + print(resultDate.timeIntervalSince1970) + date.prettyPrint() + + XCTAssertNotNil(resultDate) + XCTAssertEqual(date, resultDate) + } + + func testDateH() { + let date = Date.dateWithHourAndTimeZoneString(dateString: "2014-03-30T09:13:00.000Z") + let resultDate = NSDate(fromDateString: "2014-03-30T09:13:00Z") as! Date + XCTAssertNotNil(resultDate) + XCTAssertEqual(date, resultDate) + } + + func testDateI() { + let resultDate = NSDate(fromDateString: "2014-01-02T00:monsterofthelakeI'mhere00:00.007450+00:00") + XCTAssertNil(resultDate) + } + + func testDateJ() { + let date = Date.dateWithDayString(dateString: "2016-01-09") + let resultDate = NSDate(fromDateString: "2016-01-09T00:00:00.00") as! Date + XCTAssertNotNil(resultDate) + XCTAssertEqual(date, resultDate) + } + + func testDateK() { + let date = Date.dateWithDayString(dateString: "2016-01-09") + let resultDate = NSDate(fromDateString: "2016-01-09T00:00:00") as! Date + XCTAssertNotNil(resultDate) + XCTAssertEqual(date, resultDate) + } + + func testDateL() { + let date = Date.dateWithDayString(dateString: "2009-10-09") + let resultDate = NSDate(fromDateString: "2009-10-09 00:00:00") as! Date + XCTAssertNotNil(resultDate) + XCTAssertEqual(date, resultDate) + } +} + +class TimestampDateTests: XCTestCase { + + func testTimestampA() { + let date = Date.dateWithDayString(dateString: "2015-09-10") + let resultDate = NSDate(fromDateString: "1441843200") as! Date + + XCTAssertNotNil(resultDate) + XCTAssertEqual(date, resultDate) + } + + func testTimestampB() { + let date = Date.dateWithDayString(dateString: "2015-09-10") + let resultDate = NSDate(fromDateString: "1441843200000000") as! Date + + XCTAssertNotNil(resultDate) + XCTAssertEqual(date, resultDate) + } + + func testTimestampC() { + let date = Date.dateWithDayString(dateString: "2015-09-10") + let resultDate = NSDate(fromUnixTimestampNumber: 1441843200) as! Date + + XCTAssertNotNil(resultDate) + XCTAssertEqual(date, resultDate) + } + + func testTimestampD() { + let date = Date.dateWithDayString(dateString: "2015-09-10") + let resultDate = NSDate(fromUnixTimestampNumber: NSNumber(value: 1441843200000000.0)) as! Date + + XCTAssertNotNil(resultDate) + XCTAssertEqual(date, resultDate) + } +} + +class OtherDateTests: XCTestCase { + + func testDateType() { + let isoDateType = "2014-01-02T00:00:00.007450+00:00".dateType() + XCTAssertEqual(isoDateType, DateType.iso8601) + + let timestampDateType = "1441843200000000".dateType() + XCTAssertEqual(timestampDateType, DateType.unixTimestamp) + } +} + +extension Date { + + static func dateWithDayString(dateString: String) -> Date { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd" + formatter.timeZone = TimeZone(identifier: "UTC") + let date = formatter.date(from: dateString)! + + return date + } + + static func dateWithHourAndTimeZoneString(dateString: String) -> Date { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" + formatter.timeZone = TimeZone(identifier: "UTC") + let date = formatter.date(from: dateString)! + + return date + } + + func prettyPrint() { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" + let string = formatter.string(from: self) + print(string) + } +} diff --git a/Tests/Info.plist b/Tests/Info.plist new file mode 100644 index 00000000..ba72822e --- /dev/null +++ b/Tests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/Tests/NSEntityDescription-SyncPrimaryKey/PrimaryKeyTests.m b/Tests/NSEntityDescription-SyncPrimaryKey/PrimaryKeyTests.m new file mode 100755 index 00000000..d3be57d9 --- /dev/null +++ b/Tests/NSEntityDescription-SyncPrimaryKey/PrimaryKeyTests.m @@ -0,0 +1,99 @@ +@import CoreData; +@import XCTest; +@import Sync; + +#import "NSEntityDescription+SyncPrimaryKey.h" + +@interface PrimaryKeyTests : XCTestCase + +@end + +@implementation PrimaryKeyTests + +- (NSEntityDescription *)entityForName:(NSString *)name { + DataStack *dataStack = [[DataStack alloc] initWithModelName:@"SyncPrimaryKey" + bundle:[NSBundle bundleForClass:[self class]] + storeType:DataStackStoreTypeInMemory]; + + return [NSEntityDescription entityForName:name + inManagedObjectContext:dataStack.mainContext]; + +} + +- (void)testPrimaryKeyAttribute { + NSEntityDescription *entity = [self entityForName:@"User"]; + + NSAttributeDescription *attribute = [entity sync_primaryKeyAttribute]; + XCTAssertEqualObjects(attribute.attributeValueClassName, @"NSNumber"); + XCTAssertEqual(attribute.attributeType, NSInteger32AttributeType); + + entity = [self entityForName:@"SimpleID"]; + attribute = [entity sync_primaryKeyAttribute]; + XCTAssertEqualObjects(attribute.attributeValueClassName, @"NSString"); + XCTAssertEqual(attribute.attributeType, NSStringAttributeType); + XCTAssertEqualObjects(attribute.name, @"id"); + + entity = [self entityForName:@"Note"]; + attribute = [entity sync_primaryKeyAttribute]; + XCTAssertEqualObjects(attribute.attributeValueClassName, @"NSNumber"); + XCTAssertEqual(attribute.attributeType, NSInteger16AttributeType); + XCTAssertEqualObjects(attribute.name, @"uniqueID"); + + entity = [self entityForName:@"Tag"]; + attribute = [entity sync_primaryKeyAttribute]; + XCTAssertEqualObjects(attribute.attributeValueClassName, @"NSString"); + XCTAssertEqual(attribute.attributeType, NSStringAttributeType); + XCTAssertEqualObjects(attribute.name, @"randomId"); + + entity = [self entityForName:@"NoID"]; + attribute = [entity sync_primaryKeyAttribute]; + XCTAssertNil(attribute); + + entity = [self entityForName:@"AlternativeID"]; + attribute = [entity sync_primaryKeyAttribute]; + XCTAssertEqualObjects(attribute.attributeValueClassName, @"NSString"); + XCTAssertEqual(attribute.attributeType, NSStringAttributeType); + XCTAssertEqualObjects(attribute.name, @"alternativeID"); +} + +- (void)testLocalKey { + NSEntityDescription *entity = [self entityForName:@"User"]; + XCTAssertEqualObjects([entity sync_localPrimaryKey], @"remoteID"); + + entity = [self entityForName:@"SimpleID"]; + XCTAssertEqualObjects([entity sync_localPrimaryKey], @"id"); + + entity = [self entityForName:@"Note"]; + XCTAssertEqualObjects([entity sync_localPrimaryKey], @"uniqueID"); + + entity = [self entityForName:@"Tag"]; + XCTAssertEqualObjects([entity sync_localPrimaryKey], @"randomId"); + + entity = [self entityForName:@"NoID"]; + XCTAssertNil([entity sync_localPrimaryKey]); + + entity = [self entityForName:@"AlternativeID"]; + XCTAssertEqualObjects([entity sync_localPrimaryKey], @"alternativeID"); +} + +- (void)testRemoteKey { + NSEntityDescription *entity = [self entityForName:@"User"]; + XCTAssertEqualObjects([entity sync_remotePrimaryKey], @"id"); + + entity = [self entityForName:@"SimpleID"]; + XCTAssertEqualObjects([entity sync_remotePrimaryKey], @"id"); + + entity = [self entityForName:@"Note"]; + XCTAssertEqualObjects([entity sync_remotePrimaryKey], @"unique_id"); + + entity = [self entityForName:@"Tag"]; + XCTAssertEqualObjects([entity sync_remotePrimaryKey], @"id"); + + entity = [self entityForName:@"NoID"]; + XCTAssertNil([entity sync_remotePrimaryKey]); + + entity = [self entityForName:@"AlternativeID"]; + XCTAssertEqualObjects([entity sync_remotePrimaryKey], @"alternative_id"); +} + +@end diff --git a/Tests/NSEntityDescription-SyncPrimaryKey/SyncPrimaryKey.xcdatamodeld/.xccurrentversion b/Tests/NSEntityDescription-SyncPrimaryKey/SyncPrimaryKey.xcdatamodeld/.xccurrentversion new file mode 100755 index 00000000..62cddee8 --- /dev/null +++ b/Tests/NSEntityDescription-SyncPrimaryKey/SyncPrimaryKey.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + SyncPrimaryKey.xcdatamodel + + diff --git a/Tests/NSEntityDescription-SyncPrimaryKey/SyncPrimaryKey.xcdatamodeld/SyncPrimaryKey.xcdatamodel/contents b/Tests/NSEntityDescription-SyncPrimaryKey/SyncPrimaryKey.xcdatamodeld/SyncPrimaryKey.xcdatamodel/contents new file mode 100755 index 00000000..c6c3ccbd --- /dev/null +++ b/Tests/NSEntityDescription-SyncPrimaryKey/SyncPrimaryKey.xcdatamodeld/SyncPrimaryKey.xcdatamodel/contents @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/NSString-SyncInflections/NSString_SyncInflectionsTests.m b/Tests/NSString-SyncInflections/NSString_SyncInflectionsTests.m new file mode 100755 index 00000000..975ad329 --- /dev/null +++ b/Tests/NSString-SyncInflections/NSString_SyncInflectionsTests.m @@ -0,0 +1,156 @@ +@import XCTest; + +#import "NSString+SyncInflections.h" + +@interface NSString (PrivateInflections) + +- (BOOL)hyp_containsWord:(NSString *)word; +- (NSString *)hyp_lowerCaseFirstLetter; +- (NSString *)hyp_replaceIdentifierWithString:(NSString *)replacementString; + +@end + +@interface NSString_SyncInflectionsTests : XCTestCase + +@end + +@implementation NSString_SyncInflectionsTests + +#pragma mark - Inflections + +- (void)testReplacementIdentifier { + NSString *testString = @"first_name"; + + XCTAssertEqualObjects([testString hyp_replaceIdentifierWithString:@""], @"FirstName"); + + testString = @"id"; + + XCTAssertEqualObjects([testString hyp_replaceIdentifierWithString:@""], @"ID"); + + testString = @"user_id"; + + XCTAssertEqualObjects([testString hyp_replaceIdentifierWithString:@""], @"UserID"); +} + +- (void)testLowerCaseFirstLetter { + NSString *testString = @"FirstName"; + + XCTAssertEqualObjects([testString hyp_lowerCaseFirstLetter], @"firstName"); +} + +- (void)testSnakeCase { + NSString *camelCase = @"age"; + NSString *snakeCase = @"age"; + + XCTAssertEqualObjects(snakeCase, [camelCase hyp_snakeCase]); + + camelCase = @"id"; + snakeCase = @"id"; + + XCTAssertEqualObjects(snakeCase, [camelCase hyp_snakeCase]); + + camelCase = @"pdf"; + snakeCase = @"pdf"; + + XCTAssertEqualObjects(snakeCase, [camelCase hyp_snakeCase]); + + camelCase = @"driverIdentifier"; + snakeCase = @"driver_identifier"; + + XCTAssertEqualObjects(snakeCase, [camelCase hyp_snakeCase]); + + camelCase = @"integer16"; + snakeCase = @"integer16"; + + XCTAssertEqualObjects(snakeCase, [camelCase hyp_snakeCase]); + + camelCase = @"userID"; + snakeCase = @"user_id"; + + XCTAssertEqualObjects(snakeCase, [camelCase hyp_snakeCase]); + + camelCase = @"createdAt"; + snakeCase = @"created_at"; + + XCTAssertEqualObjects(snakeCase, [camelCase hyp_snakeCase]); + + camelCase = @"userIDFirst"; + snakeCase = @"user_id_first"; + + XCTAssertEqualObjects(snakeCase, [camelCase hyp_snakeCase]); + + camelCase = @"OrderedUser"; + snakeCase = @"ordered_user"; + + XCTAssertEqualObjects(snakeCase, [camelCase hyp_snakeCase]); +} + +- (void)testCamelCase { + NSString *snakeCase = @"age"; + NSString *camelCase = @"age"; + + XCTAssertEqualObjects(camelCase, [snakeCase hyp_camelCase]); + + snakeCase = @"id"; + camelCase = @"id"; + + XCTAssertEqualObjects(camelCase, [snakeCase hyp_camelCase]); + + snakeCase = @"pdf"; + camelCase = @"pdf"; + + XCTAssertEqualObjects(camelCase, [snakeCase hyp_camelCase]); + + snakeCase = @"driver_identifier"; + camelCase = @"driverIdentifier"; + + XCTAssertEqualObjects(camelCase, [snakeCase hyp_camelCase]); + + snakeCase = @"integer16"; + camelCase = @"integer16"; + + XCTAssertEqualObjects(snakeCase, [camelCase hyp_camelCase]); + + snakeCase = @"user_id"; + camelCase = @"userID"; + + XCTAssertEqualObjects(camelCase, [snakeCase hyp_camelCase]); + + snakeCase = @"updated_at"; + camelCase = @"updatedAt"; + + XCTAssertEqualObjects(camelCase, [snakeCase hyp_camelCase]); + +// snakeCase = @"f2f_url"; +// camelCase = @"f2fURL"; +// +// XCTAssertEqualObjects(camelCase, [snakeCase hyp_camelCase]); + + snakeCase = @"test_!_key"; + + XCTAssertNil([snakeCase hyp_camelCase]); +} + +- (void)testCamelCaseCapitalizedString { + NSString *capitalizedString = @"GreenWallet"; + NSString *camelCase = @"greenWallet"; + + XCTAssertEqualObjects(camelCase, [capitalizedString hyp_camelCase]); +} + +- (void)testStorageForSameWordButDifferentInflections { + XCTAssertEqualObjects(@"greenWallet", [@"GreenWallet" hyp_camelCase]); + XCTAssertEqualObjects(@"green_wallet", [@"GreenWallet" hyp_snakeCase]); +} + +- (void)testConcurrentAccess { + dispatch_queue_t concurrentQueue = dispatch_queue_create("com.syncdb.test", DISPATCH_QUEUE_CONCURRENT); + + dispatch_apply(6000, concurrentQueue, ^(const size_t i){ + [self testSnakeCase]; + [self testCamelCase]; + }); + +} + +@end diff --git a/Tests/Sync/DeleteTests.swift b/Tests/Sync/DeleteTests.swift new file mode 100644 index 00000000..0de92435 --- /dev/null +++ b/Tests/Sync/DeleteTests.swift @@ -0,0 +1,32 @@ +import XCTest + +import CoreData +import Sync + +class DeleteTests: XCTestCase { + func testDeleteWithStringID() { + let dataStack = Helper.dataStackWithModelName("id") + let user = NSEntityDescription.insertNewObject(forEntityName: "User", into: dataStack.mainContext) + user.setValue("id", forKey: "id") + try! dataStack.mainContext.save() + + XCTAssertEqual(1, Helper.countForEntity("User", inContext: dataStack.mainContext)) + try! Sync.delete("id", inEntityNamed: "User", using: dataStack.mainContext) + XCTAssertEqual(0, Helper.countForEntity("User", inContext: dataStack.mainContext)) + + dataStack.drop() + } + + func testDeleteWithNumberID() { + let dataStack = Helper.dataStackWithModelName("Tests") + let user = NSEntityDescription.insertNewObject(forEntityName: "User", into: dataStack.mainContext) + user.setValue(1, forKey: "remoteID") + try! dataStack.mainContext.save() + + XCTAssertEqual(1, Helper.countForEntity("User", inContext: dataStack.mainContext)) + try! Sync.delete(1, inEntityNamed: "User", using: dataStack.mainContext) + XCTAssertEqual(0, Helper.countForEntity("User", inContext: dataStack.mainContext)) + + dataStack.drop() + } +} diff --git a/Tests/Sync/FetchTests.swift b/Tests/Sync/FetchTests.swift new file mode 100644 index 00000000..95ee33c3 --- /dev/null +++ b/Tests/Sync/FetchTests.swift @@ -0,0 +1,27 @@ +import XCTest + +import CoreData +import Sync + +class FetchTests: XCTestCase { + func testFetch() { + let dataStack = Helper.dataStackWithModelName("id") + let user = NSEntityDescription.insertNewObject(forEntityName: "User", into: dataStack.mainContext) + user.setValue("id", forKey: "id") + user.setValue("dada", forKey: "name") + try! dataStack.mainContext.save() + XCTAssertEqual(1, Helper.countForEntity("User", inContext: dataStack.mainContext)) + + let fetched = try! Sync.fetch("id", inEntityNamed: "User", using: dataStack.mainContext) + XCTAssertEqual(fetched?.value(forKey: "id") as? String, "id") + XCTAssertEqual(fetched?.value(forKey: "name") as? String, "dada") + + try! Sync.delete("id", inEntityNamed: "User", using: dataStack.mainContext) + XCTAssertEqual(0, Helper.countForEntity("User", inContext: dataStack.mainContext)) + + let newFetched = try! Sync.fetch("id", inEntityNamed: "User", using: dataStack.mainContext) + XCTAssertNil(newFetched) + + dataStack.drop() + } +} diff --git a/Tests/Sync/Helpers/Helper.swift b/Tests/Sync/Helpers/Helper.swift new file mode 100644 index 00000000..77525ed0 --- /dev/null +++ b/Tests/Sync/Helpers/Helper.swift @@ -0,0 +1,74 @@ +import XCTest +import CoreData +import Sync + +@objc class Helper: NSObject { + class func objectsFromJSON(_ fileName: String) -> Any { + let bundle = Bundle(for: Helper.self) + let objects = try! JSON.from(fileName, bundle: bundle)! + + return objects + } + + class func dataStackWithModelName(_ modelName: String) -> DataStack { + let bundle = Bundle(for: Helper.self) + let dataStack = DataStack(modelName: modelName, bundle: bundle, storeType: .sqLite) + + return dataStack + } + + @available(iOS 10, watchOS 3, tvOS 10, OSX 10.12, *) + class func persistentStoreWithModelName(_ modelName: String) -> NSPersistentContainer { + let momdModelURL = Bundle(for: NSPersistentContainerTests.self).url(forResource: modelName, withExtension: "momd")! + let model = NSManagedObjectModel(contentsOf: momdModelURL)! + let persistentContainer = NSPersistentContainer(name: modelName, managedObjectModel: model) + try! persistentContainer.persistentStoreCoordinator.addPersistentStore(ofType: NSInMemoryStoreType, configurationName: nil, at: nil, options: nil) + + return persistentContainer + } + + class func countForEntity(_ entityName: String, inContext context: NSManagedObjectContext) -> Int { + return self.countForEntity(entityName, predicate: nil, inContext: context) + } + + class func countForEntity(_ entityName: String, predicate: NSPredicate?, inContext context: NSManagedObjectContext) -> Int { + let fetchRequest = NSFetchRequest(entityName: entityName) + fetchRequest.predicate = predicate + let count = try! context.count(for: fetchRequest) + + return count + } + + class func fetchEntity(_ entityName: String, inContext context: NSManagedObjectContext) -> [NSManagedObject] { + return self.fetchEntity(entityName, predicate: nil, sortDescriptors: nil, inContext: context) + } + + class func fetchEntity(_ entityName: String, predicate: NSPredicate?, inContext context: NSManagedObjectContext) -> [NSManagedObject] { + return self.fetchEntity(entityName, predicate: predicate, sortDescriptors: nil, inContext: context) + } + + class func fetchEntity(_ entityName: String, sortDescriptors: [NSSortDescriptor]?, inContext context: NSManagedObjectContext) -> [NSManagedObject] { + return self.fetchEntity(entityName, predicate: nil, sortDescriptors: sortDescriptors, inContext: context) + } + + class func fetchEntity(_ entityName: String, predicate: NSPredicate?, sortDescriptors: [NSSortDescriptor]?, inContext context: NSManagedObjectContext) -> [NSManagedObject] { + let request = NSFetchRequest(entityName: entityName) + request.predicate = predicate + request.sortDescriptors = sortDescriptors + let objects = try! context.fetch(request) as? [NSManagedObject] ?? [NSManagedObject]() + + return objects + } + + class func dataStackWithModelName(_ modelName: String, storeType: DataStackStoreType = .sqLite) -> DataStack { + let bundle = Bundle(for: Helper.self) + let dataStack = DataStack(modelName: modelName, bundle: bundle, storeType: storeType) + return dataStack + } + + class func insertEntity(_ name: String, dataStack: DataStack) -> NSManagedObject { + let entity = NSEntityDescription.entity(forEntityName: name, in: dataStack.mainContext)! + return NSManagedObject(entity: entity, insertInto: dataStack.mainContext) + } + +} diff --git a/Tests/Sync/InsertOrUpdateTests.swift b/Tests/Sync/InsertOrUpdateTests.swift new file mode 100644 index 00000000..6606619f --- /dev/null +++ b/Tests/Sync/InsertOrUpdateTests.swift @@ -0,0 +1,54 @@ +import XCTest + +import CoreData +import Sync + +class InsertOrUpdateTests: XCTestCase { + func testInsertOrUpdateWithStringID() { + let dataStack = Helper.dataStackWithModelName("id") + let json = ["id": "id", "name": "name"] + let insertedObject = try! Sync.insertOrUpdate(json, inEntityNamed: "User", using: dataStack.mainContext) + XCTAssertEqual(1, Helper.countForEntity("User", inContext: dataStack.mainContext)) + + XCTAssertEqual(insertedObject.value(forKey: "id") as? String, "id") + XCTAssertEqual(insertedObject.value(forKey: "name") as? String, "name") + + if let object = Helper.fetchEntity("User", inContext: dataStack.mainContext).first { + XCTAssertEqual(object.value(forKey: "id") as? String, "id") + XCTAssertEqual(object.value(forKey: "name") as? String, "name") + } else { + XCTFail() + } + dataStack.drop() + } + + func testInsertOrUpdateWithNumberID() { + let dataStack = Helper.dataStackWithModelName("Tests") + let json = ["id": 1] + try! Sync.insertOrUpdate(json, inEntityNamed: "User", using: dataStack.mainContext) + XCTAssertEqual(1, Helper.countForEntity("User", inContext: dataStack.mainContext)) + dataStack.drop() + } + + func testInsertOrUpdateUpdate() { + let dataStack = Helper.dataStackWithModelName("id") + let user = NSEntityDescription.insertNewObject(forEntityName: "User", into: dataStack.mainContext) + user.setValue("id", forKey: "id") + user.setValue("old", forKey: "name") + try! dataStack.mainContext.save() + + let json = ["id": "id", "name": "new"] + let updatedObject = try! Sync.insertOrUpdate(json, inEntityNamed: "User", using: dataStack.mainContext) + XCTAssertEqual(updatedObject.value(forKey: "id") as? String, "id") + XCTAssertEqual(updatedObject.value(forKey: "name") as? String, "new") + + XCTAssertEqual(1, Helper.countForEntity("User", inContext: dataStack.mainContext)) + if let object = Helper.fetchEntity("User", inContext: dataStack.mainContext).first { + XCTAssertEqual(object.value(forKey: "id") as? String, "id") + XCTAssertEqual(object.value(forKey: "name") as? String, "new") + } else { + XCTFail() + } + dataStack.drop() + } +} diff --git a/Tests/Sync/JSONs/151-many-to-many-notes-update.json b/Tests/Sync/JSONs/151-many-to-many-notes-update.json new file mode 100644 index 00000000..eb3e2c90 --- /dev/null +++ b/Tests/Sync/JSONs/151-many-to-many-notes-update.json @@ -0,0 +1,22 @@ +[ + { + "id": 0, + "name": "Note 0", + "tags_ids": ["0"] + }, + { + "id": 1, + "name": "Note 1", + "tags_ids": [] + }, + { + "id": 2, + "name": "Note 2", + "tags_ids": ["0", "1"] + }, + { + "id": 3, + "name": "Note 3", + "tags_ids": null + } +] diff --git a/Tests/Sync/JSONs/151-many-to-many-notes.json b/Tests/Sync/JSONs/151-many-to-many-notes.json new file mode 100644 index 00000000..f6146450 --- /dev/null +++ b/Tests/Sync/JSONs/151-many-to-many-notes.json @@ -0,0 +1,22 @@ +[ + { + "id": 0, + "name": "Note 0", + "tags_ids": ["0", "1"] + }, + { + "id": 1, + "name": "Note 1", + "tags_ids": ["0"] + }, + { + "id": 2, + "name": "Note 2", + "tags_ids": [] + }, + { + "id": 3, + "name": "Note 3", + "tags_ids": ["1"] + } +] diff --git a/Tests/Sync/JSONs/151-many-to-many-tags.json b/Tests/Sync/JSONs/151-many-to-many-tags.json new file mode 100644 index 00000000..dafd7c65 --- /dev/null +++ b/Tests/Sync/JSONs/151-many-to-many-tags.json @@ -0,0 +1,10 @@ +[ + { + "id": "0", + "name": "#yolo" + }, + { + "id": "1", + "name": "#nofilter" + } +] diff --git a/Tests/Sync/JSONs/151-to-many-notes.json b/Tests/Sync/JSONs/151-to-many-notes.json new file mode 100644 index 00000000..18ad8b7b --- /dev/null +++ b/Tests/Sync/JSONs/151-to-many-notes.json @@ -0,0 +1,14 @@ +[ + { + "id": 0, + "name": "Note 0" + }, + { + "id": 1, + "name": "Note 1" + }, + { + "id": 2, + "name": "Note 2" + } +] diff --git a/Tests/Sync/JSONs/151-to-many-users-update.json b/Tests/Sync/JSONs/151-to-many-users-update.json new file mode 100644 index 00000000..8d560f79 --- /dev/null +++ b/Tests/Sync/JSONs/151-to-many-users-update.json @@ -0,0 +1,17 @@ +[ + { + "id": 10, + "name": "User 10", + "notes_ids": [] + }, + { + "id": 11, + "name": "User 11", + "notes_ids": [2] + }, + { + "id": 12, + "name": "User 12", + "notes_ids": [0, 1] + } +] diff --git a/Tests/Sync/JSONs/151-to-many-users.json b/Tests/Sync/JSONs/151-to-many-users.json new file mode 100644 index 00000000..36431754 --- /dev/null +++ b/Tests/Sync/JSONs/151-to-many-users.json @@ -0,0 +1,17 @@ +[ + { + "id": 10, + "name": "User 10", + "notes_ids": [0, 1] + }, + { + "id": 11, + "name": "User 11", + "notes_ids": [2] + }, + { + "id": 12, + "name": "User 12", + "notes_ids": [] + } +] diff --git a/Tests/Sync/JSONs/157-cities.json b/Tests/Sync/JSONs/157-cities.json new file mode 100644 index 00000000..30bf476b --- /dev/null +++ b/Tests/Sync/JSONs/157-cities.json @@ -0,0 +1,14 @@ +[ + { + "id": 0, + "name": "Oslo" + }, + { + "id": 1, + "name": "Paris" + }, + { + "id": 2, + "name": "Berlin" + } +] diff --git a/Tests/Sync/JSONs/157-locations-update.json b/Tests/Sync/JSONs/157-locations-update.json new file mode 100644 index 00000000..4d6a6805 --- /dev/null +++ b/Tests/Sync/JSONs/157-locations-update.json @@ -0,0 +1,17 @@ +[ + { + "id": 0, + "name": "Mesh", + "city_id": null + }, + { + "id": 1, + "name": "Makerspace", + "city_id": 0 + }, + { + "id": 2, + "name": "WorldHack", + "city_id": 1 + } +] diff --git a/Tests/Sync/JSONs/157-locations.json b/Tests/Sync/JSONs/157-locations.json new file mode 100644 index 00000000..a412192c --- /dev/null +++ b/Tests/Sync/JSONs/157-locations.json @@ -0,0 +1,17 @@ +[ + { + "id": 0, + "name": "Mesh", + "city_id": 0 + }, + { + "id": 1, + "name": "Makerspace", + "city_id": 1 + }, + { + "id": 2, + "name": "WorldHack", + "city_id": null + } +] diff --git a/Tests/Sync/JSONs/225-a-empty.json b/Tests/Sync/JSONs/225-a-empty.json new file mode 100755 index 00000000..e59f15ad --- /dev/null +++ b/Tests/Sync/JSONs/225-a-empty.json @@ -0,0 +1,6 @@ +[ + { + "id": 1, + "tags": [] + } +] diff --git a/Tests/Sync/JSONs/225-a-null.json b/Tests/Sync/JSONs/225-a-null.json new file mode 100755 index 00000000..130b63a5 --- /dev/null +++ b/Tests/Sync/JSONs/225-a-null.json @@ -0,0 +1,6 @@ +[ + { + "id": 1, + "tags": null + } +] diff --git a/Tests/Sync/JSONs/225-a-replaced.json b/Tests/Sync/JSONs/225-a-replaced.json new file mode 100755 index 00000000..5a675570 --- /dev/null +++ b/Tests/Sync/JSONs/225-a-replaced.json @@ -0,0 +1,10 @@ +[ + { + "id": 1, + "tags": [ + { + "id": 20 + } + ] + } +] diff --git a/Tests/Sync/JSONs/225-a.json b/Tests/Sync/JSONs/225-a.json new file mode 100755 index 00000000..0b5fae8d --- /dev/null +++ b/Tests/Sync/JSONs/225-a.json @@ -0,0 +1,10 @@ +[ + { + "id": 1, + "tags": [ + { + "id": 10 + } + ] + } +] diff --git a/Tests/Sync/JSONs/233.json b/Tests/Sync/JSONs/233.json new file mode 100644 index 00000000..8c003be1 --- /dev/null +++ b/Tests/Sync/JSONs/233.json @@ -0,0 +1,6 @@ +[ + { + "id": 1, + "slides_ids": [ 2, 1 ] + } +] diff --git a/Tests/Sync/JSONs/237.json b/Tests/Sync/JSONs/237.json new file mode 100644 index 00000000..aeefe0b4 --- /dev/null +++ b/Tests/Sync/JSONs/237.json @@ -0,0 +1,13 @@ +[ + { + "id": 1, + "slides": [ + { + "id": 2 + }, + { + "id": 1 + } + ] + } +] diff --git a/Tests/Sync/JSONs/265.json b/Tests/Sync/JSONs/265.json new file mode 100644 index 00000000..671ac805 --- /dev/null +++ b/Tests/Sync/JSONs/265.json @@ -0,0 +1,13 @@ +[ + { + "id": 1, + "player_group": { + "id": 1, + "players": [ + { + "id": 1 + } + ] + } + } +] diff --git a/Tests/Sync/JSONs/277.json b/Tests/Sync/JSONs/277.json new file mode 100755 index 00000000..6ffde54d --- /dev/null +++ b/Tests/Sync/JSONs/277.json @@ -0,0 +1,18 @@ +[ + { + "id": 31, + "passengers": [ + { + "id": 22 + } + ] + }, + { + "id": 32, + "passengers": [ + { + "id": 22 + } + ] + } +] diff --git a/Tests/Sync/JSONs/280.json b/Tests/Sync/JSONs/280.json new file mode 100644 index 00000000..4ce2540b --- /dev/null +++ b/Tests/Sync/JSONs/280.json @@ -0,0 +1,15 @@ +[ + { + "busID": "0", + "stops": [ + { + "index": "0", + } + ], + "polyline": [ + { + "index": "0", + } + ] + } +] diff --git a/Tests/Sync/JSONs/283.json b/Tests/Sync/JSONs/283.json new file mode 100644 index 00000000..ee15816f --- /dev/null +++ b/Tests/Sync/JSONs/283.json @@ -0,0 +1,8 @@ +[ + { + "id": "0", + "owner": { + "id": "a" + } + } +] diff --git a/Tests/Sync/JSONs/320.json b/Tests/Sync/JSONs/320.json new file mode 100644 index 00000000..71f99820 --- /dev/null +++ b/Tests/Sync/JSONs/320.json @@ -0,0 +1,6 @@ +[ + { + "id": 1, + "tag": null + } +] diff --git a/Tests/Sync/JSONs/3ca82a0.json b/Tests/Sync/JSONs/3ca82a0.json new file mode 100644 index 00000000..9ddca929 --- /dev/null +++ b/Tests/Sync/JSONs/3ca82a0.json @@ -0,0 +1,18 @@ +[ + { + "id": 1, + "tags": [ + { + "id": 6 + }, + ] + }, + { + "id": 2, + "tags": [ + { + "id": 6 + }, + ] + } +] diff --git a/Tests/Sync/JSONs/bug-113-comments-no-id.json b/Tests/Sync/JSONs/bug-113-comments-no-id.json new file mode 100644 index 00000000..371de465 --- /dev/null +++ b/Tests/Sync/JSONs/bug-113-comments-no-id.json @@ -0,0 +1,30 @@ +[ + { + "body":"comment 1", + "awesome_comments":[ + { + "body":"sub-comment 1" + }, + { + "body":"sub-comment 2" + }, + { + "body":"sub-comment 3" + } + ] + }, + { + "body":"comment 2", + "awesome_comments":[ + { + "body":"sub-comment 4" + }, + { + "body":"sub-comment 5" + }, + { + "body":"sub-comment 6" + } + ] + } +] diff --git a/Tests/Sync/JSONs/bug-113-custom_relationship_key_to_one.json b/Tests/Sync/JSONs/bug-113-custom_relationship_key_to_one.json new file mode 100755 index 00000000..ff2beca4 --- /dev/null +++ b/Tests/Sync/JSONs/bug-113-custom_relationship_key_to_one.json @@ -0,0 +1,9 @@ +[ + { + "id":50, + "title":"Fairy tale", + "summarize_text":{ + "body":"Body of tale" + } + } +] diff --git a/Tests/Sync/JSONs/bug-113-stories-comments-no-ids.json b/Tests/Sync/JSONs/bug-113-stories-comments-no-ids.json new file mode 100644 index 00000000..3c2c974b --- /dev/null +++ b/Tests/Sync/JSONs/bug-113-stories-comments-no-ids.json @@ -0,0 +1,47 @@ +[ + { + "id":0, + "title":"story 1", + "awesome_comments":[ + { + "body":"comment 1" + }, + { + "body":"comment 2" + }, + { + "body":"comment 3" + } + ] + }, + { + "id":1, + "title":"story 2", + "awesome_comments":[ + { + "body":"comment 1" + }, + { + "body":"comment 2" + }, + { + "body":"comment 3" + } + ] + }, + { + "id":2, + "title":"story 3", + "awesome_comments":[ + { + "body":"comment 1" + }, + { + "body":"comment 2" + }, + { + "body":"comment 3" + } + ] + } +] diff --git a/Tests/Sync/JSONs/bug-125-light.json b/Tests/Sync/JSONs/bug-125-light.json new file mode 100644 index 00000000..2f7e3bd0 --- /dev/null +++ b/Tests/Sync/JSONs/bug-125-light.json @@ -0,0 +1,38 @@ + +{ + "element":{ + "elements":[ + { + "elements":[ + { + "elements":[], + "items":[ + { + "value":"option1" + } + ], + "label":"Any Single Select" + } + ], + "items":[], + "label":"Any Group" + } + ], + "items":[], + "label":"Any Form" + }, + "model":{ + "uri":"http://tempuri.org/any/path/mymodel/1.0/", + "properties":[ + { + "id":"anyString", + "restrictions":[ + { + "restriction":"enumeration" + } + ] + } + ] + }, + "uri":"http://tempuri.org/any/path/mydescriptor/1.2?language=en-US" +} diff --git a/Tests/Sync/JSONs/bug-125.json b/Tests/Sync/JSONs/bug-125.json new file mode 100644 index 00000000..8711ae2c --- /dev/null +++ b/Tests/Sync/JSONs/bug-125.json @@ -0,0 +1,167 @@ +{ + "uri":"http://tempuri.org/any/path/mydescriptor/1.2?language=en-US", + "type":"http://tempuri.org/any/path/mydescriptor/", + "version":"1.2", + "language":"en-US", + "element":{ + "type":"group", + "label":"Any Form", + "elements":[ + { + "type":"input", + "label":"Any Input", + "reference":"anyInteger", + "inputConfiguration":{ + "justAHint":[ + "a", + "b", + "c" + ] + } + }, + { + "type":"group", + "label":"Any Group", + "elements":[ + { + "type":"selectSingle", + "label":"Any Single Select", + "reference":"anyString", + "selection":"closed", + "items":[ + { + "value":"option1", + "label":"Option One" + }, + { + "value":"option2", + "label":"Option Two" + } + ] + }, + { + "type":"selectMultiple", + "label":"Any Multiple Select", + "reference":"anyString", + "selection":"open", + "items":[ + { + "value":"option3", + "label":"Option Three", + "aliasLabels":[ + { + "label":"Option Three A" + }, + { + "label":"Option Three B" + } + ] + }, + { + "value":"option4", + "label":"Option Four" + } + ] + } + ] + }, + { + "type":"textArea", + "label":"Any Text Area", + "reference":"anyString", + "comment":"This is a Comment for Any Text Area" + }, + { + "type":"image", + "label":"Any Image", + "reference":"anyMultimedia", + "maximumFileSize":12000 + }, + { + "type":"location", + "label":"Any Address Location", + "reference":"anyString", + "locationRepresentation":"address" + }, + { + "type":"custom", + "label":"Any Custom", + "reference":"anyString", + "customType":"http://tempuri.org/path/to/custom/element" + }, + { + "type":"input", + "label":"Any Input With Keyboard", + "reference":"anyString" + }, + { + "type":"form", + "label":"Any Form Link", + "formURI":"http://tempuri.org/path/to/another/form" + } + ] + }, + "model":{ + "uri":"http://tempuri.org/any/path/mymodel/1.0/", + "type":"http://tempuri.org/any/path/mymodel/", + "version":"1.0", + "properties":[ + { + "id":"anyString", + "type":"string", + "restrictions":[ + { + "restriction":"enumeration", + "items":[ + "option1", + "option2", + "option3" + ] + } + ] + }, + { + "id":"anyBoolean", + "type":"boolean", + "restrictions":[ + { + "restriction":"minLength", + "value":2 + }, + { + "restriction":"maxLength", + "value":50 + } + ] + }, + { + "id":"anyDecimal", + "type":"decimal" + }, + { + "id":"anyInteger", + "type":"integer" + }, + { + "id":"anyDateTime", + "type":"dateTime" + }, + { + "id":"anyDate", + "type":"date" + }, + { + "id":"anyDuration", + "type":"duration" + }, + { + "id":"anyMultimedia", + "type":"multimedia" + }, + { + "id":"anyLocation", + "type":"location" + } + ] + } +} diff --git a/Tests/Sync/JSONs/bug-179-places.json b/Tests/Sync/JSONs/bug-179-places.json new file mode 100644 index 00000000..20334741 --- /dev/null +++ b/Tests/Sync/JSONs/bug-179-places.json @@ -0,0 +1,8 @@ +[ + { + "name" : "Here", + }, + { + "name" : "There" + } + ] \ No newline at end of file diff --git a/Tests/Sync/JSONs/bug-179-routes.json b/Tests/Sync/JSONs/bug-179-routes.json new file mode 100644 index 00000000..b0240b9b --- /dev/null +++ b/Tests/Sync/JSONs/bug-179-routes.json @@ -0,0 +1,5 @@ +{ + "ident" : "1", + "start" : "Here", + "end" : "There" +} \ No newline at end of file diff --git a/Tests/Sync/JSONs/bug-202-a.json b/Tests/Sync/JSONs/bug-202-a.json new file mode 100644 index 00000000..0fea280e --- /dev/null +++ b/Tests/Sync/JSONs/bug-202-a.json @@ -0,0 +1,12 @@ +[ + { + "id": "1", + "name": "First", + "favorite_tags": [ + { + "id": "1", + "hashtag": "#first" + } + ] + } +] diff --git a/Tests/Sync/JSONs/bug-202-b.json b/Tests/Sync/JSONs/bug-202-b.json new file mode 100644 index 00000000..f9c3cc6b --- /dev/null +++ b/Tests/Sync/JSONs/bug-202-b.json @@ -0,0 +1,7 @@ +[ + { + "id": "1", + "name": "First", + "favorite_tags": [] + } +] diff --git a/Tests/Sync/JSONs/bug-239.json b/Tests/Sync/JSONs/bug-239.json new file mode 100755 index 00000000..3e92ed3e --- /dev/null +++ b/Tests/Sync/JSONs/bug-239.json @@ -0,0 +1,16 @@ +[ + { + "id": 31, + "max_speed": 370, + "passengers": [ + { + "id": 22, + "name": "Jenson Button" + }, + { + "id": 7, + "name": "Kimi Raikkonen" + } + ] + } +] diff --git a/Tests/Sync/JSONs/bug-254.json b/Tests/Sync/JSONs/bug-254.json new file mode 100644 index 00000000..6242a81a --- /dev/null +++ b/Tests/Sync/JSONs/bug-254.json @@ -0,0 +1,8 @@ +{ + "id": 0, + "residents": [ + { + "id": 0 + } + ] +} diff --git a/Tests/Sync/JSONs/bug-257.json b/Tests/Sync/JSONs/bug-257.json new file mode 100644 index 00000000..e7c5aaa6 --- /dev/null +++ b/Tests/Sync/JSONs/bug-257.json @@ -0,0 +1,21 @@ +{ + "_id" : "4yCb6LwmqwM2gnyWA", + "workoutName" : "Test new training program", + "workoutDesc" : "

Description

", + "workoutExercises" : [ + { + "_id" : "6JDa8DFBe8K6Kkguw", + "exerciseName" : "Exercise 1", + "exerciseDesc" : "Description exercise 1", + "mainMuscle" : "Biceps", + "mechanicsType" : "Pull" + }, + { + "_id" : "Lx5mZNG5iGRagmtS4", + "exerciseName" : "Exercise 2", + "exerciseDesc" : "Description exercise 2", + "mainMuscle" : "Abs", + "mechanicsType" : "crunch" + } + ] +} diff --git a/Tests/Sync/JSONs/bug-number-84.json b/Tests/Sync/JSONs/bug-number-84.json new file mode 100644 index 00000000..a9ebf1e9 --- /dev/null +++ b/Tests/Sync/JSONs/bug-number-84.json @@ -0,0 +1,16 @@ +[ + { + "xid": "mstaff_F58dVBTsXznvMpCPmpQgyV", + "image": "a.jpg", + "fulfillers": [ + { + "xid": "ffr_AkAHQegYkrobp5xc2ySc5D", + "name": "New York" + }, + { + "xid": "ffr_n5eGjfHQRqKr4tAfL7RNi9", + "name": "Chicago" + } + ] + } +] diff --git a/Tests/Sync/JSONs/camelcase.json b/Tests/Sync/JSONs/camelcase.json new file mode 100644 index 00000000..093688ce --- /dev/null +++ b/Tests/Sync/JSONs/camelcase.json @@ -0,0 +1,9 @@ +[ + { + "id": "1", + "numberOfChildren": 1, + "fullName": "Elvis Nuñez", + "first_name": "Elvis", + "last_name": "Nuñez" + } +] diff --git a/Tests/Sync/JSONs/comments-no-id.json b/Tests/Sync/JSONs/comments-no-id.json new file mode 100644 index 00000000..fe3fc540 --- /dev/null +++ b/Tests/Sync/JSONs/comments-no-id.json @@ -0,0 +1,30 @@ +[ + { + "body":"comment 1", + "comments":[ + { + "body":"sub-comment 1" + }, + { + "body":"sub-comment 2" + }, + { + "body":"sub-comment 3" + } + ] + }, + { + "body":"comment 2", + "comments":[ + { + "body":"sub-comment 4" + }, + { + "body":"sub-comment 5" + }, + { + "body":"sub-comment 6" + } + ] + } +] diff --git a/Tests/Sync/JSONs/custom_relationship_key_to_many.json b/Tests/Sync/JSONs/custom_relationship_key_to_many.json new file mode 100755 index 00000000..f1d50565 --- /dev/null +++ b/Tests/Sync/JSONs/custom_relationship_key_to_many.json @@ -0,0 +1,20 @@ +[ + { + "id":10, + "name":"Legen Dary", + "email":"awesome@ness.com", + "created_at":"2014-02-14T00:00:00+00:00", + "updated_at":"2014-02-17T00:00:00+00:00", + "annotations":[ + { + "id":30 + }, + { + "id":50 + }, + { + "id":70 + } + ] + } +] diff --git a/Tests/Sync/JSONs/custom_relationship_key_to_one.json b/Tests/Sync/JSONs/custom_relationship_key_to_one.json new file mode 100755 index 00000000..ff2beca4 --- /dev/null +++ b/Tests/Sync/JSONs/custom_relationship_key_to_one.json @@ -0,0 +1,9 @@ +[ + { + "id":50, + "title":"Fairy tale", + "summarize_text":{ + "body":"Body of tale" + } + } +] diff --git a/Tests/Sync/JSONs/id.json b/Tests/Sync/JSONs/id.json new file mode 100755 index 00000000..5cd7db89 --- /dev/null +++ b/Tests/Sync/JSONs/id.json @@ -0,0 +1,10 @@ +[ + { + "id": "a", + "name": "Legen Dary" + }, + { + "id": "b", + "name": "Wait Forit" + } +] diff --git a/Tests/Sync/JSONs/images.json b/Tests/Sync/JSONs/images.json new file mode 100644 index 00000000..ffd6c697 --- /dev/null +++ b/Tests/Sync/JSONs/images.json @@ -0,0 +1,14 @@ +[ + { + "ImageId": 0, + "url": "http://sample.com/sample0.png" + }, + { + "ImageId": 1, + "url": "http://sample.com/sample1.png" + }, + { + "ImageId": 2, + "url": "http://sample.com/sample2.png" + } +] diff --git a/Tests/Sync/JSONs/markets_items.json b/Tests/Sync/JSONs/markets_items.json new file mode 100644 index 00000000..4f0f3e3f --- /dev/null +++ b/Tests/Sync/JSONs/markets_items.json @@ -0,0 +1,22 @@ +[ + { + "id": "1", + "other_attribute": "Market 1", + "items": [ + { + "id": "1", + "other_attribute": "Item 1" + } + ] + }, + { + "id": "2", + "other_attribute": "Market 2", + "items": [ + { + "id": "1", + "other_attribute": "Item 1" + } + ] + } +] diff --git a/Tests/Sync/JSONs/notes_for_user_a.json b/Tests/Sync/JSONs/notes_for_user_a.json new file mode 100644 index 00000000..d751b578 --- /dev/null +++ b/Tests/Sync/JSONs/notes_for_user_a.json @@ -0,0 +1,8 @@ +[ + { + "id": 0, + }, + { + "id": 1, + } +] diff --git a/Tests/Sync/JSONs/notes_with_user_id.json b/Tests/Sync/JSONs/notes_with_user_id.json new file mode 100644 index 00000000..643174e7 --- /dev/null +++ b/Tests/Sync/JSONs/notes_with_user_id.json @@ -0,0 +1,27 @@ +[ + { + "id": 0, + "text": "Melisa White's diary, episode 0", + "super_user_id": 0 + }, + { + "id": 1, + "text": "Melisa White's diary, episode 1", + "super_user_id": 0 + }, + { + "id": 2, + "text": "Melisa White's diary, episode 2", + "super_user_id": 0 + }, + { + "id": 3, + "text": "Glass Oconnor's diary, episode 0", + "super_user_id": 1 + }, + { + "id": 4, + "text": "Glass Oconnor's diary, episode 1", + "super_user_id": 1 + } +] diff --git a/Tests/Sync/JSONs/notes_with_user_id_custom.json b/Tests/Sync/JSONs/notes_with_user_id_custom.json new file mode 100644 index 00000000..e52c13f5 --- /dev/null +++ b/Tests/Sync/JSONs/notes_with_user_id_custom.json @@ -0,0 +1,27 @@ +[ + { + "id": 0, + "text": "Melisa White's diary, episode 0", + "super_user": 0 + }, + { + "id": 1, + "text": "Melisa White's diary, episode 1", + "super_user": 0 + }, + { + "id": 2, + "text": "Melisa White's diary, episode 2", + "super_user": 0 + }, + { + "id": 3, + "text": "Glass Oconnor's diary, episode 0", + "super_user": 1 + }, + { + "id": 4, + "text": "Glass Oconnor's diary, episode 1", + "super_user": 1 + } +] diff --git a/Tests/Sync/JSONs/numbers.json b/Tests/Sync/JSONs/numbers.json new file mode 100644 index 00000000..fa7e6a38 --- /dev/null +++ b/Tests/Sync/JSONs/numbers.json @@ -0,0 +1,28 @@ +[ + { + "id": 0, + "values": 10, + "numbers": [ + { + "id": 1, + "values": 11, + }, + { + "id": 2, + "values": 12, + }] + }, + { + "id": 10, + "values": 20, + "numbers": [ + { + "id": 11, + "values": 21, + }, + { + "id": 12, + "values": 22, + }] + }, +] diff --git a/Tests/Sync/JSONs/numbers_in_collection.json b/Tests/Sync/JSONs/numbers_in_collection.json new file mode 100644 index 00000000..a651d481 --- /dev/null +++ b/Tests/Sync/JSONs/numbers_in_collection.json @@ -0,0 +1,10 @@ +[ + { + "id": 0, + "values": 10, + "parent" : + { + "name": "Collection 1" + } + } +] diff --git a/Tests/Sync/JSONs/operation-types-users-a.json b/Tests/Sync/JSONs/operation-types-users-a.json new file mode 100644 index 00000000..95783f6e --- /dev/null +++ b/Tests/Sync/JSONs/operation-types-users-a.json @@ -0,0 +1,12 @@ +[ + { + "id": 0, + "name": "Melisa White", + "email": "melisawhite@ovium.com" + }, + { + "id": 1, + "name": "Glass Oconnor", + "email": "glassoconnor@ovium.com" + } +] diff --git a/Tests/Sync/JSONs/operation-types-users-b.json b/Tests/Sync/JSONs/operation-types-users-b.json new file mode 100644 index 00000000..841f04a0 --- /dev/null +++ b/Tests/Sync/JSONs/operation-types-users-b.json @@ -0,0 +1,12 @@ +[ + { + "id": 0, + "name": "Melisa White", + "email": "updated@ovium.com" + }, + { + "id": 6, + "name": "Shawn Merrill", + "email": "shawnmerrill@ovium.com" + } +] diff --git a/Tests/Sync/JSONs/organizations-tree.json b/Tests/Sync/JSONs/organizations-tree.json new file mode 100644 index 00000000..ec29bb28 --- /dev/null +++ b/Tests/Sync/JSONs/organizations-tree.json @@ -0,0 +1,39 @@ +[ + { + "id" : 1, + "name" : "Org 1", + "children" : [ + { + "id" : 11, + "name" : "Org 11", + "children" : [ + { + "id" : 111, + "name" : "Org 111", + "children" : [ + { + "id" : 1106, + "name" : "Org 1106" + }, + { + "id" : 1131, + "name" : "Org 1131" + } + ] + }, + { + "id" : 112, + "name" : "Org 1213", + "children" : [ + { + "id" : 1114, + "name" : "Org 12132" + } + ] + } + ] + } + ] + } +] + \ No newline at end of file diff --git a/Tests/Sync/JSONs/patients.json b/Tests/Sync/JSONs/patients.json new file mode 100644 index 00000000..6604c7d3 --- /dev/null +++ b/Tests/Sync/JSONs/patients.json @@ -0,0 +1,42 @@ +[ + { + "id": 0, + "text": "Melisa White", + "baselines": [ + { + "id": 0, + "text": "Baseline 1" + } + ], + "alcohols": [ + { + "id": 0, + "text": "Alcohol 1", + "measure": { + "id": 0, + "text": "Alcohol Measure 1" + } + } + ], + "fitnesses": [ + { + "id": 0, + "text": "Fitness 1", + "measure": { + "id": 0, + "text": "Fitness Measure 1" + } + } + ], + "weights": [ + { + "id": 0, + "text": "Weight 1", + "measure": { + "id": 0, + "text": "Weight Measure 1" + } + } + ] + } +] diff --git a/Tests/Sync/JSONs/stories-comments-no-ids.json b/Tests/Sync/JSONs/stories-comments-no-ids.json new file mode 100644 index 00000000..cdc7eea8 --- /dev/null +++ b/Tests/Sync/JSONs/stories-comments-no-ids.json @@ -0,0 +1,47 @@ +[ + { + "id":0, + "title":"story 1", + "comments":[ + { + "body":"comment 1" + }, + { + "body":"comment 2" + }, + { + "body":"comment 3" + } + ] + }, + { + "id":1, + "title":"story 2", + "comments":[ + { + "body":"comment 1" + }, + { + "body":"comment 2" + }, + { + "body":"comment 3" + } + ] + }, + { + "id":2, + "title":"story 3", + "comments":[ + { + "body":"comment 1" + }, + { + "body":"comment 2" + }, + { + "body":"comment 3" + } + ] + } +] diff --git a/Tests/Sync/JSONs/story-summarize.json b/Tests/Sync/JSONs/story-summarize.json new file mode 100644 index 00000000..a97d2779 --- /dev/null +++ b/Tests/Sync/JSONs/story-summarize.json @@ -0,0 +1,11 @@ +{ + "id":1, + "summarize_text":{ + "id":1 + }, + "comments":[ + { + "body":"Hi" + } + ] +} diff --git a/Tests/Sync/JSONs/tagged_notes.json b/Tests/Sync/JSONs/tagged_notes.json new file mode 100644 index 00000000..61d5468f --- /dev/null +++ b/Tests/Sync/JSONs/tagged_notes.json @@ -0,0 +1,30 @@ +[ + { + "id": 0, + "text": "Shawn Merril's diary, episode 0", + "super_tags": [ + { + "id": 1, + "name": "diary" + }, + { + "id": 2, + "name": "neverforget" + } + ] + }, + { + "id": 1, + "text": "Shawn Merril's diary, episode 1", + "super_tags": [ + { + "id": 1, + "name": "diary" + } + ] + }, + { + "id": 2, + "text": "Shawn Merril's diary, episode 4" + } +] diff --git a/Tests/Sync/JSONs/to-one-camelcase.json b/Tests/Sync/JSONs/to-one-camelcase.json new file mode 100644 index 00000000..eb24fb8f --- /dev/null +++ b/Tests/Sync/JSONs/to-one-camelcase.json @@ -0,0 +1,6 @@ +{ + "id": 0, + "legalTenant": { + "id": 0 + } +} diff --git a/Tests/Sync/JSONs/to-one-snakecase.json b/Tests/Sync/JSONs/to-one-snakecase.json new file mode 100644 index 00000000..2501f470 --- /dev/null +++ b/Tests/Sync/JSONs/to-one-snakecase.json @@ -0,0 +1,6 @@ +{ + "id": 0, + "legal_tenant": { + "id": 0 + } +} diff --git a/Tests/Sync/JSONs/unique.json b/Tests/Sync/JSONs/unique.json new file mode 100644 index 00000000..5ed81b8d --- /dev/null +++ b/Tests/Sync/JSONs/unique.json @@ -0,0 +1,13 @@ +[ + { + "id" : 1, + "b" : [ + { + "id" : 2 + }, + { + "id" : 3 + } + ] + } +] \ No newline at end of file diff --git a/Tests/Sync/JSONs/users_a.json b/Tests/Sync/JSONs/users_a.json new file mode 100644 index 00000000..9452f1c0 --- /dev/null +++ b/Tests/Sync/JSONs/users_a.json @@ -0,0 +1,58 @@ +[ + { + "id": 0, + "name": "Melisa White", + "email": "melisawhite@ovium.com", + "created_at": "2014-02-14T00:00:00+00:00", + "updated_at": "2014-02-17T00:00:00+00:00" + }, + { + "id": 1, + "name": "Glass Oconnor", + "email": "glassoconnor@ovium.com", + "created_at": "2014-02-15T00:00:00+00:00", + "updated_at": "2014-02-18T00:00:00+00:00" + }, + { + "id": 2, + "name": "Maritza Boyer", + "email": "maritzaboyer@ovium.com", + "created_at": "2014-02-18T00:00:00+00:00", + "updated_at": "2014-02-19T00:00:00+00:00" + }, + { + "id": 3, + "name": "Margo Nixon", + "email": "margonixon@ovium.com", + "created_at": "2014-02-14T00:00:00+00:00", + "updated_at": "2014-02-14T00:00:00+00:00" + }, + { + "id": 4, + "name": "Valencia Foley", + "email": "valenciafoley@ovium.com", + "created_at": "2014-02-14T00:00:00+00:00", + "updated_at": "2014-02-14T00:00:00+00:00" + }, + { + "id": 5, + "name": "Hayden Livingston", + "email": "haydenlivingston@ovium.com", + "created_at": "2014-02-14T00:00:00+00:00", + "updated_at": "2014-02-14T00:00:00+00:00" + }, + { + "id": 6, + "name": "Shawn Merrill", + "email": "shawnmerrill@ovium.com", + "created_at": "2014-02-14T00:00:00+00:00", + "updated_at": "2014-02-14T00:00:00+00:00" + }, + { + "id": 7, + "name": "Bradford Duke", + "email": "bradfordduke@ovium.com", + "created_at": "2014-02-14T00:00:00+00:00", + "updated_at": "2014-02-17T00:00:00+00:00" + } +] diff --git a/Tests/Sync/JSONs/users_b.json b/Tests/Sync/JSONs/users_b.json new file mode 100644 index 00000000..cd8f1fdb --- /dev/null +++ b/Tests/Sync/JSONs/users_b.json @@ -0,0 +1,32 @@ +[ + { + "id": 6, + "name": "Shawn Merrill", + "email": "firstupdate@ovium.com" + }, + { + "id": 7, + "name": "Bradford Duke", + "email": "secondupdated@ovium.com" + }, + { + "id": 8, + "name": "Dodson Vaughan", + "email": "dodsonvaughan@ovium.com" + }, + { + "id": 9, + "name": "Alexis Richmond", + "email": "alexisrichmond@ovium.com" + }, + { + "id": 10, + "name": "Flores Daniel", + "email": "floresdaniel@ovium.com" + }, + { + "id": 11, + "name": "Odessa Turner", + "email": "odessaturner@ovium.com" + } +] diff --git a/Tests/Sync/JSONs/users_c.json b/Tests/Sync/JSONs/users_c.json new file mode 100644 index 00000000..db9f16b1 --- /dev/null +++ b/Tests/Sync/JSONs/users_c.json @@ -0,0 +1,63 @@ +[ + { + "id": 6, + "name": "Shawn Merrill", + "email": "firstupdate@ovium.com", + "location": { + "city": "New York", + "street": "Broadway", + "zip_code": 10012 + }, + "profile_pictures": [ + { + "ImageId": 0, + "url": "http://sample.com/sample0.png" + }, + { + "ImageId": 1, + "url": "http://sample.com/sample1.png" + }, + { + "ImageId": 2, + "url": "http://sample.com/sample2.png" + } + ], + }, + { + "id": 7, + "name": "Bradford Duke", + "email": "secondupdated@ovium.com", + "location": { + "city": "London" + }, + "profile_pictures": [ + { + "ImageId": 0, + "url": "http://sample.com/sample0.png" + }, + { + "ImageId": 1, + "url": "http://sample.com/sample1.png" + } + ], + }, + { + "id": 8, + "name": "Dodson Vaughan", + "email": "dodsonvaughan@ovium.com", + "profile_pictures": [ + { + "ImageId": 0, + "url": "http://sample.com/sample0.png" + } + ], + }, + { + "id": 9, + "name": "Alexis Richmond", + "email": "alexisrichmond@ovium.com", + "location": { + "city": "Lille" + }, + } +] diff --git a/Tests/Sync/JSONs/users_company.json b/Tests/Sync/JSONs/users_company.json new file mode 100644 index 00000000..3ebfa844 --- /dev/null +++ b/Tests/Sync/JSONs/users_company.json @@ -0,0 +1,57 @@ +[ + { + "id": 0, + "name": "Melisa White", + "email": "melisawhite@ovium.com", + "created_at": "2014-02-14T00:00:00+00:00", + "updated_at": "2014-02-17T00:00:00+00:00", + "company":{ + "id": 0, + "name": "Apple" + } + }, + { + "id": 1, + "name": "Glass Oconnor", + "email": "glassoconnor@ovium.com", + "created_at": "2014-02-15T00:00:00+00:00", + "updated_at": "2014-02-18T00:00:00+00:00", + "company":{ + "id": 0, + "name": "Apple" + } + }, + { + "id": 2, + "name": "Maritza Boyer", + "email": "maritzaboyer@ovium.com", + "created_at": "2014-02-18T00:00:00+00:00", + "updated_at": "2014-02-19T00:00:00+00:00", + "company":{ + "id": 0, + "name": "Apple" + } + }, + { + "id": 3, + "name": "Margo Nixon", + "email": "margonixon@ovium.com", + "created_at": "2014-02-14T00:00:00+00:00", + "updated_at": "2014-02-14T00:00:00+00:00", + "company":{ + "id": 1, + "name": "Facebook" + } + }, + { + "id": 4, + "name": "Valencia Foley", + "email": "valenciafoley@ovium.com", + "created_at": "2014-02-14T00:00:00+00:00", + "updated_at": "2014-02-14T00:00:00+00:00", + "company":{ + "id": 1, + "name": "Facebook" + } + } +] diff --git a/Tests/Sync/JSONs/users_notes.json b/Tests/Sync/JSONs/users_notes.json new file mode 100644 index 00000000..389e729a --- /dev/null +++ b/Tests/Sync/JSONs/users_notes.json @@ -0,0 +1,72 @@ +[ + { + "id": 6, + "name": "Shawn Merrill", + "email": "firstupdate@ovium.com", + "annotations": [ + { + "id": 0, + "text": "Shawn Merril's diary, episode 1" + }, + { + "id": 6, + "text": "Shawn Merril's diary, episode 2" + }, + { + "id": 7, + "text": "Shawn Merril's diary, episode 3" + }, + { + "id": 8, + "text": "Shawn Merril's diary, episode 4" + }, + { + "id": 9, + "text": "Shawn Merril's diary, episode 5" + } + ] + }, + { + "id": 7, + "name": "Bradford Duke", + "email": "secondupdated@ovium.com", + "annotations": [ + { + "id": 1, + "text": "Bradford Duke's diary, episode 1" + }, + { + "id": 2, + "text": "Bradford Duke's diary, episode 2" + }, + { + "id": 3, + "text": "Bradford Duke's diary, episode 3" + }, + { + "id": 4, + "text": "Bradford Duke's diary, episode 4" + }, + { + "id": 5, + "text": "Bradford Duke's diary, episode 5" + } + ] + }, + { + "id": 8, + "name": "Dodson Vaughan", + "email": "dodsonvaughan@ovium.com", + "annotations": [ + { + "id": 10, + "text": "Bradford Duke's diary, episode 1" + } + ] + }, + { + "id": 9, + "name": "Alexis Richmond", + "email": "alexisrichmond@ovium.com" + } +] diff --git a/Tests/Sync/Models/113.xcdatamodeld/.xccurrentversion b/Tests/Sync/Models/113.xcdatamodeld/.xccurrentversion new file mode 100644 index 00000000..17d74ca8 --- /dev/null +++ b/Tests/Sync/Models/113.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + 113.xcdatamodel + + diff --git a/Tests/Sync/Models/113.xcdatamodeld/113.xcdatamodel/contents b/Tests/Sync/Models/113.xcdatamodeld/113.xcdatamodel/contents new file mode 100644 index 00000000..fceec10e --- /dev/null +++ b/Tests/Sync/Models/113.xcdatamodeld/113.xcdatamodel/contents @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/125.xcdatamodeld/.xccurrentversion b/Tests/Sync/Models/125.xcdatamodeld/.xccurrentversion new file mode 100644 index 00000000..258167bc --- /dev/null +++ b/Tests/Sync/Models/125.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + 125.xcdatamodel + + diff --git a/Tests/Sync/Models/125.xcdatamodeld/125.xcdatamodel/contents b/Tests/Sync/Models/125.xcdatamodeld/125.xcdatamodel/contents new file mode 100644 index 00000000..e4e32374 --- /dev/null +++ b/Tests/Sync/Models/125.xcdatamodeld/125.xcdatamodel/contents @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/151-many-to-many.xcdatamodeld/.xccurrentversion b/Tests/Sync/Models/151-many-to-many.xcdatamodeld/.xccurrentversion new file mode 100644 index 00000000..2477a35b --- /dev/null +++ b/Tests/Sync/Models/151-many-to-many.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + 151-many-to-many.xcdatamodel + + diff --git a/Tests/Sync/Models/151-many-to-many.xcdatamodeld/151-many-to-many.xcdatamodel/contents b/Tests/Sync/Models/151-many-to-many.xcdatamodeld/151-many-to-many.xcdatamodel/contents new file mode 100644 index 00000000..fee9c750 --- /dev/null +++ b/Tests/Sync/Models/151-many-to-many.xcdatamodeld/151-many-to-many.xcdatamodel/contents @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/151-ordered-many-to-many.xcdatamodeld/.xccurrentversion b/Tests/Sync/Models/151-ordered-many-to-many.xcdatamodeld/.xccurrentversion new file mode 100644 index 00000000..2477a35b --- /dev/null +++ b/Tests/Sync/Models/151-ordered-many-to-many.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + 151-many-to-many.xcdatamodel + + diff --git a/Tests/Sync/Models/151-ordered-many-to-many.xcdatamodeld/151-many-to-many.xcdatamodel/contents b/Tests/Sync/Models/151-ordered-many-to-many.xcdatamodeld/151-many-to-many.xcdatamodel/contents new file mode 100644 index 00000000..5380e015 --- /dev/null +++ b/Tests/Sync/Models/151-ordered-many-to-many.xcdatamodeld/151-many-to-many.xcdatamodel/contents @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/151-ordered-to-many.xcdatamodeld/.xccurrentversion b/Tests/Sync/Models/151-ordered-to-many.xcdatamodeld/.xccurrentversion new file mode 100644 index 00000000..2300ed4f --- /dev/null +++ b/Tests/Sync/Models/151-ordered-to-many.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + 151-to-many.xcdatamodel + + diff --git a/Tests/Sync/Models/151-ordered-to-many.xcdatamodeld/151-to-many.xcdatamodel/contents b/Tests/Sync/Models/151-ordered-to-many.xcdatamodeld/151-to-many.xcdatamodel/contents new file mode 100644 index 00000000..885d7da4 --- /dev/null +++ b/Tests/Sync/Models/151-ordered-to-many.xcdatamodeld/151-to-many.xcdatamodel/contents @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/151-to-many.xcdatamodeld/.xccurrentversion b/Tests/Sync/Models/151-to-many.xcdatamodeld/.xccurrentversion new file mode 100644 index 00000000..2300ed4f --- /dev/null +++ b/Tests/Sync/Models/151-to-many.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + 151-to-many.xcdatamodel + + diff --git a/Tests/Sync/Models/151-to-many.xcdatamodeld/151-to-many.xcdatamodel/contents b/Tests/Sync/Models/151-to-many.xcdatamodeld/151-to-many.xcdatamodel/contents new file mode 100644 index 00000000..9f79c576 --- /dev/null +++ b/Tests/Sync/Models/151-to-many.xcdatamodeld/151-to-many.xcdatamodel/contents @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/157.xcdatamodeld/.xccurrentversion b/Tests/Sync/Models/157.xcdatamodeld/.xccurrentversion new file mode 100644 index 00000000..69a39526 --- /dev/null +++ b/Tests/Sync/Models/157.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + 157.xcdatamodel + + diff --git a/Tests/Sync/Models/157.xcdatamodeld/157.xcdatamodel/contents b/Tests/Sync/Models/157.xcdatamodeld/157.xcdatamodel/contents new file mode 100644 index 00000000..bcb8e09d --- /dev/null +++ b/Tests/Sync/Models/157.xcdatamodeld/157.xcdatamodel/contents @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/179.xcdatamodeld/179.xcdatamodel/contents b/Tests/Sync/Models/179.xcdatamodeld/179.xcdatamodel/contents new file mode 100644 index 00000000..efa2f7dd --- /dev/null +++ b/Tests/Sync/Models/179.xcdatamodeld/179.xcdatamodel/contents @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/202.xcdatamodeld/.xccurrentversion b/Tests/Sync/Models/202.xcdatamodeld/.xccurrentversion new file mode 100644 index 00000000..207e4146 --- /dev/null +++ b/Tests/Sync/Models/202.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + 202.xcdatamodel + + diff --git a/Tests/Sync/Models/202.xcdatamodeld/202.xcdatamodel/contents b/Tests/Sync/Models/202.xcdatamodeld/202.xcdatamodel/contents new file mode 100644 index 00000000..b86cd757 --- /dev/null +++ b/Tests/Sync/Models/202.xcdatamodeld/202.xcdatamodel/contents @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/225.xcdatamodeld/.xccurrentversion b/Tests/Sync/Models/225.xcdatamodeld/.xccurrentversion new file mode 100755 index 00000000..2477a35b --- /dev/null +++ b/Tests/Sync/Models/225.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + 151-many-to-many.xcdatamodel + + diff --git a/Tests/Sync/Models/225.xcdatamodeld/151-many-to-many.xcdatamodel/contents b/Tests/Sync/Models/225.xcdatamodeld/151-many-to-many.xcdatamodel/contents new file mode 100644 index 00000000..9f497085 --- /dev/null +++ b/Tests/Sync/Models/225.xcdatamodeld/151-many-to-many.xcdatamodel/contents @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/233.xcdatamodeld/233.xcdatamodel/contents b/Tests/Sync/Models/233.xcdatamodeld/233.xcdatamodel/contents new file mode 100644 index 00000000..f036ac0c --- /dev/null +++ b/Tests/Sync/Models/233.xcdatamodeld/233.xcdatamodel/contents @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/239.xcdatamodeld/239.xcdatamodel/contents b/Tests/Sync/Models/239.xcdatamodeld/239.xcdatamodel/contents new file mode 100644 index 00000000..b7ab3937 --- /dev/null +++ b/Tests/Sync/Models/239.xcdatamodeld/239.xcdatamodel/contents @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/254.xcdatamodeld/254.xcdatamodel/contents b/Tests/Sync/Models/254.xcdatamodeld/254.xcdatamodel/contents new file mode 100644 index 00000000..95ce75ea --- /dev/null +++ b/Tests/Sync/Models/254.xcdatamodeld/254.xcdatamodel/contents @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/257.xcdatamodeld/257.xcdatamodel/contents b/Tests/Sync/Models/257.xcdatamodeld/257.xcdatamodel/contents new file mode 100644 index 00000000..dff1b1dc --- /dev/null +++ b/Tests/Sync/Models/257.xcdatamodeld/257.xcdatamodel/contents @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/265.xcdatamodeld/.xccurrentversion b/Tests/Sync/Models/265.xcdatamodeld/.xccurrentversion new file mode 100755 index 00000000..31a1c6cd --- /dev/null +++ b/Tests/Sync/Models/265.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + 265.xcdatamodel + + diff --git a/Tests/Sync/Models/265.xcdatamodeld/265.xcdatamodel/contents b/Tests/Sync/Models/265.xcdatamodeld/265.xcdatamodel/contents new file mode 100644 index 00000000..1c8c1042 --- /dev/null +++ b/Tests/Sync/Models/265.xcdatamodeld/265.xcdatamodel/contents @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/277.xcdatamodeld/239.xcdatamodel/contents b/Tests/Sync/Models/277.xcdatamodeld/239.xcdatamodel/contents new file mode 100644 index 00000000..947d82b7 --- /dev/null +++ b/Tests/Sync/Models/277.xcdatamodeld/239.xcdatamodel/contents @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/280.xcdatamodeld/.xccurrentversion b/Tests/Sync/Models/280.xcdatamodeld/.xccurrentversion new file mode 100755 index 00000000..2477a35b --- /dev/null +++ b/Tests/Sync/Models/280.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + 151-many-to-many.xcdatamodel + + diff --git a/Tests/Sync/Models/280.xcdatamodeld/151-many-to-many.xcdatamodel/contents b/Tests/Sync/Models/280.xcdatamodeld/151-many-to-many.xcdatamodel/contents new file mode 100644 index 00000000..93df3e47 --- /dev/null +++ b/Tests/Sync/Models/280.xcdatamodeld/151-many-to-many.xcdatamodel/contents @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/283.xcdatamodeld/.xccurrentversion b/Tests/Sync/Models/283.xcdatamodeld/.xccurrentversion new file mode 100644 index 00000000..00318b09 --- /dev/null +++ b/Tests/Sync/Models/283.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + Demo.xcdatamodel + + diff --git a/Tests/Sync/Models/283.xcdatamodeld/Demo.xcdatamodel/contents b/Tests/Sync/Models/283.xcdatamodeld/Demo.xcdatamodel/contents new file mode 100644 index 00000000..4c122e62 --- /dev/null +++ b/Tests/Sync/Models/283.xcdatamodeld/Demo.xcdatamodel/contents @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/320.xcdatamodeld/320.xcdatamodel/contents b/Tests/Sync/Models/320.xcdatamodeld/320.xcdatamodel/contents new file mode 100644 index 00000000..6a4257a0 --- /dev/null +++ b/Tests/Sync/Models/320.xcdatamodeld/320.xcdatamodel/contents @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/3ca82a0.xcdatamodeld/3ca82a0.xcdatamodel/contents b/Tests/Sync/Models/3ca82a0.xcdatamodeld/3ca82a0.xcdatamodel/contents new file mode 100644 index 00000000..7e4c8d74 --- /dev/null +++ b/Tests/Sync/Models/3ca82a0.xcdatamodeld/3ca82a0.xcdatamodel/contents @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/84.xcdatamodeld/.xccurrentversion b/Tests/Sync/Models/84.xcdatamodeld/.xccurrentversion new file mode 100644 index 00000000..9238c02b --- /dev/null +++ b/Tests/Sync/Models/84.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + 84.xcdatamodel + + diff --git a/Tests/Sync/Models/84.xcdatamodeld/84.xcdatamodel/contents b/Tests/Sync/Models/84.xcdatamodeld/84.xcdatamodel/contents new file mode 100644 index 00000000..f451551d --- /dev/null +++ b/Tests/Sync/Models/84.xcdatamodeld/84.xcdatamodel/contents @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/Camelcase.xcdatamodeld/.xccurrentversion b/Tests/Sync/Models/Camelcase.xcdatamodeld/.xccurrentversion new file mode 100644 index 00000000..00318b09 --- /dev/null +++ b/Tests/Sync/Models/Camelcase.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + Demo.xcdatamodel + + diff --git a/Tests/Sync/Models/Camelcase.xcdatamodeld/Demo.xcdatamodel/contents b/Tests/Sync/Models/Camelcase.xcdatamodeld/Demo.xcdatamodel/contents new file mode 100644 index 00000000..4291d66e --- /dev/null +++ b/Tests/Sync/Models/Camelcase.xcdatamodeld/Demo.xcdatamodel/contents @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/Contacts.xcdatamodeld/.xccurrentversion b/Tests/Sync/Models/Contacts.xcdatamodeld/.xccurrentversion new file mode 100644 index 00000000..00318b09 --- /dev/null +++ b/Tests/Sync/Models/Contacts.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + Demo.xcdatamodel + + diff --git a/Tests/Sync/Models/Contacts.xcdatamodeld/Demo.xcdatamodel/contents b/Tests/Sync/Models/Contacts.xcdatamodeld/Demo.xcdatamodel/contents new file mode 100644 index 00000000..eef25ba8 --- /dev/null +++ b/Tests/Sync/Models/Contacts.xcdatamodeld/Demo.xcdatamodel/contents @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/CustomRelationshipKey.xcdatamodeld/.xccurrentversion b/Tests/Sync/Models/CustomRelationshipKey.xcdatamodeld/.xccurrentversion new file mode 100644 index 00000000..00318b09 --- /dev/null +++ b/Tests/Sync/Models/CustomRelationshipKey.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + Demo.xcdatamodel + + diff --git a/Tests/Sync/Models/CustomRelationshipKey.xcdatamodeld/Demo.xcdatamodel/contents b/Tests/Sync/Models/CustomRelationshipKey.xcdatamodeld/Demo.xcdatamodel/contents new file mode 100644 index 00000000..19417a79 --- /dev/null +++ b/Tests/Sync/Models/CustomRelationshipKey.xcdatamodeld/Demo.xcdatamodel/contents @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/InsertObjectsInParent.xcdatamodeld/.xccurrentversion b/Tests/Sync/Models/InsertObjectsInParent.xcdatamodeld/.xccurrentversion new file mode 100644 index 00000000..00318b09 --- /dev/null +++ b/Tests/Sync/Models/InsertObjectsInParent.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + Demo.xcdatamodel + + diff --git a/Tests/Sync/Models/InsertObjectsInParent.xcdatamodeld/Demo.xcdatamodel/contents b/Tests/Sync/Models/InsertObjectsInParent.xcdatamodeld/Demo.xcdatamodel/contents new file mode 100644 index 00000000..0c7e5e4b --- /dev/null +++ b/Tests/Sync/Models/InsertObjectsInParent.xcdatamodeld/Demo.xcdatamodel/contents @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/Markets.xcdatamodeld/.xccurrentversion b/Tests/Sync/Models/Markets.xcdatamodeld/.xccurrentversion new file mode 100644 index 00000000..00318b09 --- /dev/null +++ b/Tests/Sync/Models/Markets.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + Demo.xcdatamodel + + diff --git a/Tests/Sync/Models/Markets.xcdatamodeld/Demo.xcdatamodel/contents b/Tests/Sync/Models/Markets.xcdatamodeld/Demo.xcdatamodel/contents new file mode 100644 index 00000000..038a1ba2 --- /dev/null +++ b/Tests/Sync/Models/Markets.xcdatamodeld/Demo.xcdatamodel/contents @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/Notes.xcdatamodeld/.xccurrentversion b/Tests/Sync/Models/Notes.xcdatamodeld/.xccurrentversion new file mode 100644 index 00000000..00318b09 --- /dev/null +++ b/Tests/Sync/Models/Notes.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + Demo.xcdatamodel + + diff --git a/Tests/Sync/Models/Notes.xcdatamodeld/Demo.xcdatamodel/contents b/Tests/Sync/Models/Notes.xcdatamodeld/Demo.xcdatamodel/contents new file mode 100644 index 00000000..207493dc --- /dev/null +++ b/Tests/Sync/Models/Notes.xcdatamodeld/Demo.xcdatamodel/contents @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/NotesB.xcdatamodeld/.xccurrentversion b/Tests/Sync/Models/NotesB.xcdatamodeld/.xccurrentversion new file mode 100644 index 00000000..00318b09 --- /dev/null +++ b/Tests/Sync/Models/NotesB.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + Demo.xcdatamodel + + diff --git a/Tests/Sync/Models/NotesB.xcdatamodeld/Demo.xcdatamodel/contents b/Tests/Sync/Models/NotesB.xcdatamodeld/Demo.xcdatamodel/contents new file mode 100644 index 00000000..65903b4a --- /dev/null +++ b/Tests/Sync/Models/NotesB.xcdatamodeld/Demo.xcdatamodel/contents @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/OrderedSocial.xcdatamodeld/.xccurrentversion b/Tests/Sync/Models/OrderedSocial.xcdatamodeld/.xccurrentversion new file mode 100644 index 00000000..00318b09 --- /dev/null +++ b/Tests/Sync/Models/OrderedSocial.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + Demo.xcdatamodel + + diff --git a/Tests/Sync/Models/OrderedSocial.xcdatamodeld/Demo.xcdatamodel/contents b/Tests/Sync/Models/OrderedSocial.xcdatamodeld/Demo.xcdatamodel/contents new file mode 100644 index 00000000..d9272a26 --- /dev/null +++ b/Tests/Sync/Models/OrderedSocial.xcdatamodeld/Demo.xcdatamodel/contents @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/Organizations.xcdatamodeld/Organizations.xcdatamodel/contents b/Tests/Sync/Models/Organizations.xcdatamodeld/Organizations.xcdatamodel/contents new file mode 100644 index 00000000..e39762d1 --- /dev/null +++ b/Tests/Sync/Models/Organizations.xcdatamodeld/Organizations.xcdatamodel/contents @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/Patients.xcdatamodeld/.xccurrentversion b/Tests/Sync/Models/Patients.xcdatamodeld/.xccurrentversion new file mode 100644 index 00000000..00318b09 --- /dev/null +++ b/Tests/Sync/Models/Patients.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + Demo.xcdatamodel + + diff --git a/Tests/Sync/Models/Patients.xcdatamodeld/Demo.xcdatamodel/contents b/Tests/Sync/Models/Patients.xcdatamodeld/Demo.xcdatamodel/contents new file mode 100644 index 00000000..cfd99816 --- /dev/null +++ b/Tests/Sync/Models/Patients.xcdatamodeld/Demo.xcdatamodel/contents @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/Recursive.xcdatamodeld/.xccurrentversion b/Tests/Sync/Models/Recursive.xcdatamodeld/.xccurrentversion new file mode 100644 index 00000000..00318b09 --- /dev/null +++ b/Tests/Sync/Models/Recursive.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + Demo.xcdatamodel + + diff --git a/Tests/Sync/Models/Recursive.xcdatamodeld/Demo.xcdatamodel/contents b/Tests/Sync/Models/Recursive.xcdatamodeld/Demo.xcdatamodel/contents new file mode 100644 index 00000000..7233fef4 --- /dev/null +++ b/Tests/Sync/Models/Recursive.xcdatamodeld/Demo.xcdatamodel/contents @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/Social.xcdatamodeld/.xccurrentversion b/Tests/Sync/Models/Social.xcdatamodeld/.xccurrentversion new file mode 100644 index 00000000..00318b09 --- /dev/null +++ b/Tests/Sync/Models/Social.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + Demo.xcdatamodel + + diff --git a/Tests/Sync/Models/Social.xcdatamodeld/Demo.xcdatamodel/contents b/Tests/Sync/Models/Social.xcdatamodeld/Demo.xcdatamodel/contents new file mode 100644 index 00000000..0c0085a3 --- /dev/null +++ b/Tests/Sync/Models/Social.xcdatamodeld/Demo.xcdatamodel/contents @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/Tests.xcdatamodeld/Tests.xcdatamodel/contents b/Tests/Sync/Models/Tests.xcdatamodeld/Tests.xcdatamodel/contents new file mode 100644 index 00000000..fbbc85b0 --- /dev/null +++ b/Tests/Sync/Models/Tests.xcdatamodeld/Tests.xcdatamodel/contents @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/ToOne.xcdatamodeld/.xccurrentversion b/Tests/Sync/Models/ToOne.xcdatamodeld/.xccurrentversion new file mode 100644 index 00000000..00318b09 --- /dev/null +++ b/Tests/Sync/Models/ToOne.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + Demo.xcdatamodel + + diff --git a/Tests/Sync/Models/ToOne.xcdatamodeld/Demo.xcdatamodel/contents b/Tests/Sync/Models/ToOne.xcdatamodeld/Demo.xcdatamodel/contents new file mode 100644 index 00000000..25998b70 --- /dev/null +++ b/Tests/Sync/Models/ToOne.xcdatamodeld/Demo.xcdatamodel/contents @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/Unique.xcdatamodeld/Unique.xcdatamodel/contents b/Tests/Sync/Models/Unique.xcdatamodeld/Unique.xcdatamodel/contents new file mode 100644 index 00000000..2f1051fe --- /dev/null +++ b/Tests/Sync/Models/Unique.xcdatamodeld/Unique.xcdatamodel/contents @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/Models/id.xcdatamodeld/.xccurrentversion b/Tests/Sync/Models/id.xcdatamodeld/.xccurrentversion new file mode 100644 index 00000000..00318b09 --- /dev/null +++ b/Tests/Sync/Models/id.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + Demo.xcdatamodel + + diff --git a/Tests/Sync/Models/id.xcdatamodeld/Demo.xcdatamodel/contents b/Tests/Sync/Models/id.xcdatamodeld/Demo.xcdatamodel/contents new file mode 100644 index 00000000..d6e5fe8c --- /dev/null +++ b/Tests/Sync/Models/id.xcdatamodeld/Demo.xcdatamodel/contents @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Sync/NSArray+SyncTests.swift b/Tests/Sync/NSArray+SyncTests.swift new file mode 100644 index 00000000..ab4eaa00 --- /dev/null +++ b/Tests/Sync/NSArray+SyncTests.swift @@ -0,0 +1,14 @@ +import XCTest + +class NSArray_SyncTests: XCTestCase { + // Bug 125 => https://github.com/SyncDB/Sync/issues/125 + + /*func testPreprocessForEntityNamed() { + let formDictionary = Helper.objectsFromJSON("bug-125-light.json") as! [String : NSObject] + let uri = formDictionary["uri"] as! String + let dataStack = Helper.dataStackWithModelName("Bug125") + + let preprocessed = ([formDictionary] as NSArray).preprocessForEntityNamed("Form", predicate: NSPredicate(format: "uri = %@", uri), parent: nil, dataStack: dataStack).first! as! [String : NSObject] + XCTAssertEqual(preprocessed, formDictionary) + }*/ +} \ No newline at end of file diff --git a/Tests/Sync/NSEntityDescription+SyncTests.swift b/Tests/Sync/NSEntityDescription+SyncTests.swift new file mode 100644 index 00000000..2d3847e5 --- /dev/null +++ b/Tests/Sync/NSEntityDescription+SyncTests.swift @@ -0,0 +1,9 @@ +import XCTest + +class NSEntityDescription_SyncTests: XCTestCase { + func testRelationships() { + } + + func testParentEntity() { + } +} diff --git a/Tests/Sync/NSManagedObject+SyncTests.swift b/Tests/Sync/NSManagedObject+SyncTests.swift new file mode 100644 index 00000000..cf1c80f3 --- /dev/null +++ b/Tests/Sync/NSManagedObject+SyncTests.swift @@ -0,0 +1,25 @@ +import XCTest +import CoreData + +class NSManagedObject_SyncTests: XCTestCase { + func testCopyInContext() { + // Create and fetch an item in a mainContext, then copy that item + // to a background context. Then do the check there. + } + + func testFillWithDictionary() { + // This method is mostly to categorize the type of filling that we should do. + // Maybe is better to just unit test a method that returns the type of relationship syncing. + } + + // I am afraid that we will end up duplicating many of the current existing tests in order to test the following + // methods. Maybe a better idea would be to split up those into methods that return things instead of just mutations. + func testToManyRelationship() { + } + + func testRelationshipUsingIDInsteadOfDictionary() { + } + + func testToOneRelationship() { + } +} diff --git a/Tests/Sync/NSManagedObjectContext+SyncTests.swift b/Tests/Sync/NSManagedObjectContext+SyncTests.swift new file mode 100644 index 00000000..d11933a5 --- /dev/null +++ b/Tests/Sync/NSManagedObjectContext+SyncTests.swift @@ -0,0 +1,48 @@ +import XCTest + +import Sync + +class NSManagedObjectContext_SyncTests: XCTestCase { + func testSafeObjectInContext() { + } + + func configureUserWithRemoteID(remoteID: NSNumber?, localID: String?, name: String, block: @escaping (_ user: NSManagedObject, _ context: NSManagedObjectContext) -> Void) { + let stack = DataStack(modelName: "Tests", bundle: Bundle(for: NSManagedObjectContext_SyncTests.self), storeType: .inMemory) + stack.performInNewBackgroundContext { context in + let user = NSEntityDescription.insertNewObject(forEntityName: "User", into: context) + user.setValue(remoteID, forKey: "remoteID") + user.setValue(localID, forKey: "localID") + user.setValue(name, forKey: "name") + try! context.save() + block(user, context) + } + } + + func testDictionary() { + self.configureUserWithRemoteID(remoteID: 1, localID: nil, name: "Joshua") { user, context in + let dictionary = context.managedObjectIDs(in: "User", usingAsKey: "remoteID", predicate: nil) + XCTAssertNotNil(dictionary) + XCTAssertTrue(dictionary.count == 1) + XCTAssertEqual(dictionary[NSNumber(value: 1)], user.objectID) + + let objectID = dictionary[NSNumber(value: 1)]! + let retreivedUser = context.object(with: objectID) + XCTAssertEqual(retreivedUser.value(forKey: "remoteID") as? Int, 1) + XCTAssertEqual(retreivedUser.value(forKey: "name") as? String, "Joshua") + } + } + + func testDictionaryStringLocalKey() { + self.configureUserWithRemoteID(remoteID: nil, localID: "100", name: "Joshua") { user, context in + let dictionary = context.managedObjectIDs(in: "User", usingAsKey: "localID", predicate: nil) + XCTAssertNotNil(dictionary) + XCTAssertTrue(dictionary.count == 1) + XCTAssertEqual(dictionary["100"], user.objectID) + + let objectID = dictionary["100"]! + let retreivedUser = context.object(with: objectID) + XCTAssertEqual(retreivedUser.value(forKey: "localID") as? String, "100") + XCTAssertEqual(retreivedUser.value(forKey: "name") as? String, "Joshua") + } + } +} diff --git a/Tests/Sync/NSPersistentContainerTests.swift b/Tests/Sync/NSPersistentContainerTests.swift new file mode 100644 index 00000000..3de3a25f --- /dev/null +++ b/Tests/Sync/NSPersistentContainerTests.swift @@ -0,0 +1,129 @@ +import XCTest + +import CoreData +import Sync + +class NSPersistentContainerTests: XCTestCase { + func testPersistentContainer() { + if #available(iOS 10, *) { + let expectation = self.expectation(description: "testSkipTestMode") + + let momdModelURL = Bundle(for: NSPersistentContainerTests.self).url(forResource: "Camelcase", withExtension: "momd")! + let model = NSManagedObjectModel(contentsOf: momdModelURL)! + let persistentContainer = NSPersistentContainer(name: "Camelcase", managedObjectModel: model) + try! persistentContainer.persistentStoreCoordinator.addPersistentStore(ofType: NSInMemoryStoreType, configurationName: nil, at: nil, options: nil) + let objects = Helper.objectsFromJSON("camelcase.json") as! [[String: Any]] + + Sync.changes(objects, inEntityNamed: "NormalUser", predicate: nil, persistentContainer: persistentContainer) { error in + let result = Helper.fetchEntity("NormalUser", inContext: persistentContainer.viewContext) + XCTAssertEqual(result.count, 1) + + if let first = result.first { + XCTAssertEqual(first.value(forKey: "etternavn") as? String, "Nuñez") + XCTAssertEqual(first.value(forKey: "firstName") as? String, "Elvis") + XCTAssertEqual(first.value(forKey: "fullName") as? String, "Elvis Nuñez") + XCTAssertEqual(first.value(forKey: "numberOfChildren") as? Int, 1) + XCTAssertEqual(first.value(forKey: "remoteID") as? String, "1") + } else { + XCTFail() + } + + expectation.fulfill() + } + + self.waitForExpectations(timeout: 150.0, handler: nil) + } + } + + func testPersistentContainerExtension() { + if #available(iOS 10, *) { + let expectation = self.expectation(description: "testSkipTestMode") + + let persistentContainer = Helper.persistentStoreWithModelName("Camelcase") + let objects = Helper.objectsFromJSON("camelcase.json") as! [[String: Any]] + + persistentContainer.sync(objects, inEntityNamed: "NormalUser") { error in + let result = Helper.fetchEntity("NormalUser", inContext: persistentContainer.viewContext) + XCTAssertEqual(result.count, 1) + + if let first = result.first { + XCTAssertEqual(first.value(forKey: "etternavn") as? String, "Nuñez") + XCTAssertEqual(first.value(forKey: "firstName") as? String, "Elvis") + XCTAssertEqual(first.value(forKey: "fullName") as? String, "Elvis Nuñez") + XCTAssertEqual(first.value(forKey: "numberOfChildren") as? Int, 1) + XCTAssertEqual(first.value(forKey: "remoteID") as? String, "1") + } else { + XCTFail() + } + + expectation.fulfill() + } + + self.waitForExpectations(timeout: 150.0, handler: nil) + } + } + + func testInsertOrUpdate() { + if #available(iOS 10, *) { + let expectation = self.expectation(description: "testSkipTestMode") + let persistentContainer = Helper.persistentStoreWithModelName("Tests") + let json = ["id": 1] + persistentContainer.insertOrUpdate(json, inEntityNamed: "User") { result in + switch result { + case .success: + XCTAssertEqual(1, Helper.countForEntity("User", inContext: persistentContainer.viewContext)) + case .failure: + XCTFail() + } + + expectation.fulfill() + } + self.waitForExpectations(timeout: 150.0, handler: nil) + } + } + + func testUpdate() { + if #available(iOS 10, *) { + let expectation = self.expectation(description: "testSkipTestMode") + let persistentContainer = Helper.persistentStoreWithModelName("id") + let user = NSEntityDescription.insertNewObject(forEntityName: "User", into: persistentContainer.viewContext) + user.setValue("id", forKey: "id") + try! persistentContainer.viewContext.save() + + XCTAssertEqual(1, Helper.countForEntity("User", inContext: persistentContainer.viewContext)) + persistentContainer.update("id", with: ["name": "bossy"], inEntityNamed: "User") { result in + switch result { + case .success(let id): + XCTAssertEqual(id as? String, "id") + XCTAssertEqual(1, Helper.countForEntity("User", inContext: persistentContainer.viewContext)) + + persistentContainer.viewContext.refresh(user, mergeChanges: false) + + XCTAssertEqual(user.value(forKey: "name") as? String, "bossy") + case .failure: + XCTFail() + } + + expectation.fulfill() + } + + self.waitForExpectations(timeout: 150.0, handler: nil) + } + } + + func testDelete() { + let expectation = self.expectation(description: "testSkipTestMode") + let persistentContainer = Helper.persistentStoreWithModelName("id") + let user = NSEntityDescription.insertNewObject(forEntityName: "User", into: persistentContainer.viewContext) + user.setValue("id", forKey: "id") + try! persistentContainer.viewContext.save() + + XCTAssertEqual(1, Helper.countForEntity("User", inContext: persistentContainer.viewContext)) + persistentContainer.delete("id", inEntityNamed: "User") { error in + XCTAssertEqual(0, Helper.countForEntity("User", inContext: persistentContainer.viewContext)) + expectation.fulfill() + } + + self.waitForExpectations(timeout: 150.0, handler: nil) + } +} diff --git a/Tests/Sync/SyncDelegateTests.swift b/Tests/Sync/SyncDelegateTests.swift new file mode 100644 index 00000000..77a9e070 --- /dev/null +++ b/Tests/Sync/SyncDelegateTests.swift @@ -0,0 +1,34 @@ +import XCTest + +import CoreData +import Sync + +class SyncDelegateTests: XCTestCase { + func testWillInsertJSON() { + let dataStack = Helper.dataStackWithModelName("Tests") + + let json = [["id": 9, "completed": false]] + let syncOperation = Sync(changes: json, inEntityNamed: "User", dataStack: dataStack) + syncOperation.delegate = self + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 0) + syncOperation.start() + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 1) + + if let task = Helper.fetchEntity("User", inContext: dataStack.mainContext).first { + XCTAssertEqual(task.value(forKey: "remoteID") as? Int, 9) + XCTAssertEqual(task.value(forKey: "localID") as? String, "local") + } else { + XCTFail() + } + dataStack.drop() + } +} + +extension SyncDelegateTests: SyncDelegate { + func sync(_ sync: Sync, willInsert json: [String: Any], in entityNamed: String, parent: NSManagedObject?) -> [String: Any] { + var newJSON = json + newJSON["localID"] = "local" + + return newJSON + } +} diff --git a/Tests/Sync/SyncTests.swift b/Tests/Sync/SyncTests.swift new file mode 100644 index 00000000..cd1bb1de --- /dev/null +++ b/Tests/Sync/SyncTests.swift @@ -0,0 +1,1413 @@ +import XCTest + +import CoreData +import Sync + +class SyncTests: XCTestCase { + func testSynchronous() { + let dataStack = Helper.dataStackWithModelName("Camelcase") + let objects = Helper.objectsFromJSON("camelcase.json") as! [[String: Any]] + var synchronous = false + Sync.changes(objects, inEntityNamed: "NormalUser", dataStack: dataStack) { _ in + synchronous = true + } + XCTAssertTrue(synchronous) + dataStack.drop() + } + + // MARK: - Camelcase + func testAutomaticCamelcaseMapping() { + let dataStack = Helper.dataStackWithModelName("Camelcase") + let objects = Helper.objectsFromJSON("camelcase.json") as! [[String: Any]] + Sync.changes(objects, inEntityNamed: "NormalUser", dataStack: dataStack, completion: nil) + + let result = Helper.fetchEntity("NormalUser", inContext: dataStack.mainContext) + XCTAssertEqual(result.count, 1) + + let first = result.first! + XCTAssertEqual(first.value(forKey: "etternavn") as? String, "Nuñez") + XCTAssertEqual(first.value(forKey: "firstName") as? String, "Elvis") + XCTAssertEqual(first.value(forKey: "fullName") as? String, "Elvis Nuñez") + XCTAssertEqual(first.value(forKey: "numberOfChildren") as? Int, 1) + XCTAssertEqual(first.value(forKey: "remoteID") as? String, "1") + + dataStack.drop() + } + + // MARK: - Contacts + + func testLoadAndUpdateUsers() { + let dataStack = Helper.dataStackWithModelName("Contacts") + + let objectsA = Helper.objectsFromJSON("users_a.json") as! [[String: Any]] + Sync.changes(objectsA, inEntityNamed: "User", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 8) + + let objectsB = Helper.objectsFromJSON("users_b.json") as! [[String: Any]] + Sync.changes(objectsB, inEntityNamed: "User", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 6) + + let result = Helper.fetchEntity("User", predicate: NSPredicate(format: "remoteID = %@", NSNumber(value: 7)), sortDescriptors: [NSSortDescriptor(key: "remoteID", ascending: true)], inContext: dataStack.mainContext).first! + XCTAssertEqual(result.value(forKey: "email") as? String, "secondupdated@ovium.com") + + let dateFormat = DateFormatter() + dateFormat.dateFormat = "yyyy-MM-dd" + dateFormat.timeZone = TimeZone(identifier: "GMT") + + let createdDate = dateFormat.date(from: "2014-02-14") + XCTAssertEqual(result.value(forKey: "createdAt") as? Date, createdDate) + + let updatedDate = dateFormat.date(from: "2014-02-17") + XCTAssertEqual(result.value(forKey: "updatedAt") as? Date, updatedDate) + + dataStack.drop() + } + + func testUsersAndCompanies() { + let dataStack = Helper.dataStackWithModelName("Contacts") + + let objects = Helper.objectsFromJSON("users_company.json") as! [[String: Any]] + Sync.changes(objects, inEntityNamed: "User", dataStack: dataStack, completion: nil) + + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 5) + let user = Helper.fetchEntity("User", predicate: NSPredicate(format: "remoteID = %@", NSNumber(value: 0)), sortDescriptors: [NSSortDescriptor(key: "remoteID", ascending: true)], inContext: dataStack.mainContext).first! + XCTAssertEqual((user.value(forKey: "company")! as? NSManagedObject)!.value(forKey: "name") as? String, "Apple") + + XCTAssertEqual(Helper.countForEntity("Company", inContext: dataStack.mainContext), 2) + let company = Helper.fetchEntity("Company", predicate: NSPredicate(format: "remoteID = %@", NSNumber(value: 1)), sortDescriptors: [NSSortDescriptor(key: "remoteID", ascending: true)], inContext: dataStack.mainContext).first! + XCTAssertEqual(company.value(forKey: "name") as? String, "Facebook") + + dataStack.drop() + } + + func testCustomMappingAndCustomPrimaryKey() { + let dataStack = Helper.dataStackWithModelName("Contacts") + let objects = Helper.objectsFromJSON("images.json") as! [[String: Any]] + Sync.changes(objects, inEntityNamed: "Image", dataStack: dataStack, completion: nil) + + let array = Helper.fetchEntity("Image", sortDescriptors: [NSSortDescriptor(key: "url", ascending: true)], inContext: dataStack.mainContext) + XCTAssertEqual(array.count, 3) + let image = array.first + XCTAssertEqual(image!.value(forKey: "url") as? String, "http://sample.com/sample0.png") + + dataStack.drop() + } + + func testRelationshipsB() { + let dataStack = Helper.dataStackWithModelName("Contacts") + + let objects = Helper.objectsFromJSON("users_c.json") as! [[String: Any]] + Sync.changes(objects, inEntityNamed: "User", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 4) + + let users = Helper.fetchEntity("User", predicate: NSPredicate(format: "remoteID = %@", NSNumber(value: 6)), inContext: dataStack.mainContext) + let user = users.first! + XCTAssertEqual(user.value(forKey: "name") as? String, "Shawn Merrill") + + let location = user.value(forKey: "location") as! NSManagedObject + XCTAssertEqual(location.value(forKey: "city") as? String, "New York") + XCTAssertEqual(location.value(forKey: "street") as? String, "Broadway") + XCTAssertEqual(location.value(forKey: "zipCode") as? NSNumber, NSNumber(value: 10012)) + + let profilePicturesCount = Helper.countForEntity("Image", predicate: NSPredicate(format: "user = %@", user), inContext: dataStack.mainContext) + XCTAssertEqual(profilePicturesCount, 3) + + dataStack.drop() + } + + // If all operations where enabled in the first sync, 2 users would be inserted, in the second sync 1 user would be updated + // and one user deleted. In this test we try only inserting user, no update, no insert, so the second sync should leave us with + // 2 users with no changes and 1 inserted user. + func testSyncingWithOnlyInsertOperationType() { + let dataStack = Helper.dataStackWithModelName("Contacts") + + let objectsA = Helper.objectsFromJSON("operation-types-users-a.json") as! [[String: Any]] + Sync.changes(objectsA, inEntityNamed: "User", dataStack: dataStack, operations: [.all], completion: nil) + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 2) + + let objectsB = Helper.objectsFromJSON("operation-types-users-b.json") as! [[String: Any]] + Sync.changes(objectsB, inEntityNamed: "User", dataStack: dataStack, operations: [.insert], completion: nil) + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 3) + + let result = Helper.fetchEntity("User", predicate: NSPredicate(format: "remoteID = %@", NSNumber(value: 0)), sortDescriptors: [NSSortDescriptor(key: "remoteID", ascending: true)], inContext: dataStack.mainContext).first! + XCTAssertEqual(result.value(forKey: "email") as? String, "melisawhite@ovium.com") + + dataStack.drop() + } + + // If all operations where enabled in the first sync, 2 users would be inserted, in the second sync 1 user would be updated + // and one user deleted. In this test we try only inserting user, no update, no insert, so the second sync should leave us with + // 2 users with no changes and 1 inserted user. After this is done, we'll try inserting again, this shouldn't make any changes. + func testSyncingWithMultipleInsertOperationTypes() { + let dataStack = Helper.dataStackWithModelName("Contacts") + + let objectsA = Helper.objectsFromJSON("operation-types-users-a.json") as! [[String: Any]] + Sync.changes(objectsA, inEntityNamed: "User", dataStack: dataStack, operations: [.all], completion: nil) + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 2) + + let objectsB = Helper.objectsFromJSON("operation-types-users-b.json") as! [[String: Any]] + Sync.changes(objectsB, inEntityNamed: "User", dataStack: dataStack, operations: [.insert], completion: nil) + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 3) + + let result = Helper.fetchEntity("User", predicate: NSPredicate(format: "remoteID = %@", NSNumber(value: 0)), sortDescriptors: [NSSortDescriptor(key: "remoteID", ascending: true)], inContext: dataStack.mainContext).first! + XCTAssertEqual(result.value(forKey: "email") as? String, "melisawhite@ovium.com") + + Sync.changes(objectsB, inEntityNamed: "User", dataStack: dataStack, operations: [.insert], completion: nil) + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 3) + + dataStack.drop() + } + + // If all operations where enabled in the first sync, 2 users would be inserted, in the second sync 1 user would be updated + // and one user deleted. In this test we try only updating users, no insert, no delete, so the second sync should leave us with + // one updated user and one inserted user, the third user will be discarded. + func testSyncingWithOnlyUpdateOperationType() { + let dataStack = Helper.dataStackWithModelName("Contacts") + + let objectsA = Helper.objectsFromJSON("operation-types-users-a.json") as! [[String: Any]] + Sync.changes(objectsA, inEntityNamed: "User", dataStack: dataStack, operations: [.all], completion: nil) + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 2) + + let objectsB = Helper.objectsFromJSON("operation-types-users-b.json") as! [[String: Any]] + Sync.changes(objectsB, inEntityNamed: "User", dataStack: dataStack, operations: [.update], completion: nil) + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 2) + + let result = Helper.fetchEntity("User", predicate: NSPredicate(format: "remoteID = %@", NSNumber(value: 0)), sortDescriptors: [NSSortDescriptor(key: "remoteID", ascending: true)], inContext: dataStack.mainContext).first! + XCTAssertEqual(result.value(forKey: "email") as? String, "updated@ovium.com") + + dataStack.drop() + } + + // If all operations where enabled in the first sync, 2 users would be inserted, in the second sync 1 user would be updated + // and one user deleted. In this test we try only deleting users, no insert, no update, so the second sync should leave us with + // one inserted user, one deleted user and one discarded user. + func testSyncingWithOnlyDeleteOperationType() { + let dataStack = Helper.dataStackWithModelName("Contacts") + + let objectsA = Helper.objectsFromJSON("operation-types-users-a.json") as! [[String: Any]] + Sync.changes(objectsA, inEntityNamed: "User", dataStack: dataStack, operations: [.all], completion: nil) + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 2) + + let objectsB = Helper.objectsFromJSON("operation-types-users-b.json") as! [[String: Any]] + Sync.changes(objectsB, inEntityNamed: "User", dataStack: dataStack, operations: [.delete], completion: nil) + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 1) + + let result = Helper.fetchEntity("User", predicate: NSPredicate(format: "remoteID = %@", NSNumber(value: 0)), sortDescriptors: [NSSortDescriptor(key: "remoteID", ascending: true)], inContext: dataStack.mainContext).first! + XCTAssertEqual(result.value(forKey: "email") as? String, "melisawhite@ovium.com") + + dataStack.drop() + } + + // If all operations where enabled in the first sync, 2 users would be inserted, in the second sync 1 user would be updated + // and one user deleted. In this test we try inserting and updating users, no delete, so the second sync should leave us with + // one updated user, one inserted user, and one user with no changes. + func testSyncingWithInsertAndUpdateOperationType() { + let dataStack = Helper.dataStackWithModelName("Contacts") + + let objectsA = Helper.objectsFromJSON("operation-types-users-a.json") as! [[String: Any]] + Sync.changes(objectsA, inEntityNamed: "User", dataStack: dataStack, operations: [.all], completion: nil) + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 2) + + let objectsB = Helper.objectsFromJSON("operation-types-users-b.json") as! [[String: Any]] + Sync.changes(objectsB, inEntityNamed: "User", dataStack: dataStack, operations: [.insert, .update], completion: nil) + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 3) + + let user0 = Helper.fetchEntity("User", predicate: NSPredicate(format: "remoteID = %@", NSNumber(value: 0)), sortDescriptors: [NSSortDescriptor(key: "remoteID", ascending: true)], inContext: dataStack.mainContext).first! + XCTAssertEqual(user0.value(forKey: "email") as? String, "updated@ovium.com") + + let user1 = Helper.fetchEntity("User", predicate: NSPredicate(format: "remoteID = %@", NSNumber(value: 1)), sortDescriptors: [NSSortDescriptor(key: "remoteID", ascending: true)], inContext: dataStack.mainContext).first! + XCTAssertNotNil(user1) + + dataStack.drop() + } + + // MARK: - Notes + + func testRelationshipsA() { + let objects = Helper.objectsFromJSON("users_notes.json") as! [[String: Any]] + let dataStack = Helper.dataStackWithModelName("Notes") + + Sync.changes(objects, inEntityNamed: "SuperUser", dataStack: dataStack, completion: nil) + + XCTAssertEqual(Helper.countForEntity("SuperUser", inContext: dataStack.mainContext), 4) + let users = Helper.fetchEntity("SuperUser", predicate: NSPredicate(format: "remoteID = %@", NSNumber(value: 6)), inContext: dataStack.mainContext) + let user = users.first! + XCTAssertEqual(user.value(forKey: "name") as? String, "Shawn Merrill") + + let notesCount = Helper.countForEntity("SuperNote", predicate: NSPredicate(format: "superUser = %@", user), inContext: dataStack.mainContext) + XCTAssertEqual(notesCount, 5) + + dataStack.drop() + } + + func testObjectsForParent() { + let objects = Helper.objectsFromJSON("notes_for_user_a.json") as! [[String: Any]] + let dataStack = Helper.dataStackWithModelName("InsertObjectsInParent") + dataStack.performInNewBackgroundContext { backgroundContext in + // First, we create a parent user, this user is the one that will own all the notes + let user = NSEntityDescription.insertNewObject(forEntityName: "SuperUser", into: backgroundContext) + user.setValue(NSNumber(value: 6), forKey: "remoteID") + + try! backgroundContext.save() + } + + // Then we fetch the user on the main context, because we don't want to break things between contexts + var users = Helper.fetchEntity("SuperUser", predicate: NSPredicate(format: "remoteID = %@", NSNumber(value: 6)), inContext: dataStack.mainContext) + XCTAssertEqual(users.count, 1) + + // Finally we say "Sync all the notes, for this user" + Sync.changes(objects, inEntityNamed: "SuperNote", parent: users.first!, dataStack: dataStack, completion: nil) + + // Here we just make sure that the user has the notes that we just inserted + users = Helper.fetchEntity("SuperUser", predicate: NSPredicate(format: "remoteID = %@", NSNumber(value: 6)), inContext: dataStack.mainContext) + let user = users.first! + XCTAssertEqual(user.value(forKey: "remoteID") as? NSNumber, NSNumber(value: 6)) + + let notesCount = Helper.countForEntity("SuperNote", predicate: NSPredicate(format: "superUser = %@", user), inContext: dataStack.mainContext) + XCTAssertEqual(notesCount, 2) + + dataStack.drop() + } + + func testTaggedNotesForUser() { + let objects = Helper.objectsFromJSON("tagged_notes.json") as! [[String: Any]] + let dataStack = Helper.dataStackWithModelName("Notes") + + Sync.changes(objects, inEntityNamed: "SuperNote", dataStack: dataStack, completion: nil) + + XCTAssertEqual(Helper.countForEntity("SuperNote", inContext: dataStack.mainContext), 3) + let notes = Helper.fetchEntity("SuperNote", predicate: NSPredicate(format: "remoteID = %@", NSNumber(value: 0)), inContext: dataStack.mainContext) + let note = notes.first! + XCTAssertEqual((note.value(forKey: "superTags") as? NSSet)!.allObjects.count, 2) + + XCTAssertEqual(Helper.countForEntity("SuperTag", inContext: dataStack.mainContext), 2) + let tags = Helper.fetchEntity("SuperTag", predicate: NSPredicate(format: "remoteID = %@", NSNumber(value: 1)), inContext: dataStack.mainContext) + XCTAssertEqual(tags.count, 1) + + let tag = tags.first! + XCTAssertEqual((tag.value(forKey: "superNotes") as? NSSet)!.allObjects.count, 2) + dataStack.drop() + } + + func testCustomKeysInRelationshipsToMany() { + let objects = Helper.objectsFromJSON("custom_relationship_key_to_many.json") as! [[String: Any]] + let dataStack = Helper.dataStackWithModelName("CustomRelationshipKey") + + Sync.changes(objects, inEntityNamed: "User", dataStack: dataStack, completion: nil) + + let array = Helper.fetchEntity("User", inContext: dataStack.mainContext) + let user = array.first! + XCTAssertEqual((user.value(forKey: "notes") as? NSSet)!.allObjects.count, 3) + + dataStack.drop() + } + + // MARK: - Recursive + + func testNumbersWithEmptyRelationship() { + let objects = Helper.objectsFromJSON("numbers.json") as! [[String: Any]] + let dataStack = Helper.dataStackWithModelName("Recursive") + + Sync.changes(objects, inEntityNamed: "Number", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("Number", inContext: dataStack.mainContext), 6) + + dataStack.drop() + } + + func testRelationshipName() { + let objects = Helper.objectsFromJSON("numbers_in_collection.json") as! [[String: Any]] + let dataStack = Helper.dataStackWithModelName("Recursive") + + Sync.changes(objects, inEntityNamed: "Number", dataStack: dataStack, completion: nil) + + XCTAssertEqual(Helper.countForEntity("Collection", inContext: dataStack.mainContext), 1) + + let numbers = Helper.fetchEntity("Number", inContext: dataStack.mainContext) + let number = numbers.first! + XCTAssertNotNil(number.value(forKey: "parent")) + XCTAssertEqual((number.value(forKey: "parent") as! NSManagedObject).value(forKey: "name") as? String, "Collection 1") + + dataStack.drop() + } + + // MARK: - Social + + func testCustomPrimaryKey() { + let objects = Helper.objectsFromJSON("comments-no-id.json") as! [[String: Any]] + let dataStack = Helper.dataStackWithModelName("Social") + + Sync.changes(objects, inEntityNamed: "SocialComment", dataStack: dataStack, completion: nil) + + XCTAssertEqual(Helper.countForEntity("SocialComment", inContext: dataStack.mainContext), 8) + let comments = Helper.fetchEntity("SocialComment", predicate: NSPredicate(format: "body = %@", "comment 1"), inContext: dataStack.mainContext) + XCTAssertEqual(comments.count, 1) + XCTAssertEqual((comments.first!.value(forKey: "comments") as! NSSet).count, 3) + + let comment = comments.first! + XCTAssertEqual(comment.value(forKey: "body") as? String, "comment 1") + + dataStack.drop() + } + + func testCustomPrimaryKeyInsideToManyRelationship() { + let objects = Helper.objectsFromJSON("stories-comments-no-ids.json") as! [[String: Any]] + let dataStack = Helper.dataStackWithModelName("Social") + + Sync.changes(objects, inEntityNamed: "Story", dataStack: dataStack, completion: nil) + + XCTAssertEqual(Helper.countForEntity("Story", inContext: dataStack.mainContext), 3) + let stories = Helper.fetchEntity("Story", predicate: NSPredicate(format: "remoteID = %@", NSNumber(value: 0)), inContext: dataStack.mainContext) + let story = stories.first! + + XCTAssertEqual((story.value(forKey: "comments") as! NSSet).count, 3) + + XCTAssertEqual(Helper.countForEntity("SocialComment", inContext: dataStack.mainContext), 9) + var comments = Helper.fetchEntity("SocialComment", predicate: NSPredicate(format: "body = %@", "comment 1"), inContext: dataStack.mainContext) + XCTAssertEqual(comments.count, 3) + + comments = Helper.fetchEntity("SocialComment", predicate: NSPredicate(format: "body = %@ AND story = %@", "comment 1", story), inContext: dataStack.mainContext) + XCTAssertEqual(comments.count, 1) + if let comment = comments.first { + XCTAssertEqual(comment.value(forKey: "body") as? String, "comment 1") + XCTAssertEqual((comment.value(forKey: "story") as? NSManagedObject)!.value(forKey: "remoteID") as? NSNumber, NSNumber(value: 0)) + XCTAssertEqual((comment.value(forKey: "story") as? NSManagedObject)!.value(forKey: "title") as? String, "story 1") + } else { + XCTFail() + } + + dataStack.drop() + } + + func testCustomKeysInRelationshipsToOne() { + let objects = Helper.objectsFromJSON("custom_relationship_key_to_one.json") as! [[String: Any]] + let dataStack = Helper.dataStackWithModelName("Social") + + Sync.changes(objects, inEntityNamed: "Story", dataStack: dataStack, completion: nil) + + let array = Helper.fetchEntity("Story", inContext: dataStack.mainContext) + let story = array.first! + XCTAssertNotNil(story.value(forKey: "summarize")) + + dataStack.drop() + } + + // MARK: - Markets + + func testMarketsAndItems() { + let objects = Helper.objectsFromJSON("markets_items.json") as! [[String: Any]] + let dataStack = Helper.dataStackWithModelName("Markets") + + Sync.changes(objects, inEntityNamed: "Market", dataStack: dataStack, completion: nil) + + XCTAssertEqual(Helper.countForEntity("Market", inContext: dataStack.mainContext), 2) + let markets = Helper.fetchEntity("Market", predicate: NSPredicate(format: "uniqueId = %@", "1"), inContext: dataStack.mainContext) + let market = markets.first! + XCTAssertEqual(market.value(forKey: "otherAttribute") as? String, "Market 1") + XCTAssertEqual((market.value(forKey: "items") as? NSSet)!.allObjects.count, 1) + + XCTAssertEqual(Helper.countForEntity("Item", inContext: dataStack.mainContext), 1) + let items = Helper.fetchEntity("Item", predicate: NSPredicate(format: "uniqueId = %@", "1"), inContext: dataStack.mainContext) + let item = items.first! + XCTAssertEqual(item.value(forKey: "otherAttribute") as? String, "Item 1") + XCTAssertEqual((item.value(forKey: "markets") as? NSSet)!.allObjects.count, 2) + + dataStack.drop() + } + + // MARK: - Organization + + func testOrganization() { + let json = Helper.objectsFromJSON("organizations-tree.json") as! [[String: Any]] + let dataStack = Helper.dataStackWithModelName("Organizations") + + Sync.changes(json, inEntityNamed: "OrganizationUnit", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("OrganizationUnit", inContext: dataStack.mainContext), 7) + + Sync.changes(json, inEntityNamed: "OrganizationUnit", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("OrganizationUnit", inContext: dataStack.mainContext), 7) + + dataStack.drop() + } + + // MARK: - Unique + + /** + * C and A share the same collection of B, so in the first block + * 2 entries of B get stored in A, in the second block this + * 2 entries of B get updated and one entry of C gets added. + */ + func testUniqueObject() { + let objects = Helper.objectsFromJSON("unique.json") as! [[String: Any]] + let dataStack = Helper.dataStackWithModelName("Unique") + + Sync.changes(objects, inEntityNamed: "A", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("A", inContext: dataStack.mainContext), 1) + XCTAssertEqual(Helper.countForEntity("B", inContext: dataStack.mainContext), 2) + XCTAssertEqual(Helper.countForEntity("C", inContext: dataStack.mainContext), 0) + + Sync.changes(objects, inEntityNamed: "C", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("A", inContext: dataStack.mainContext), 1) + XCTAssertEqual(Helper.countForEntity("B", inContext: dataStack.mainContext), 2) + XCTAssertEqual(Helper.countForEntity("C", inContext: dataStack.mainContext), 1) + + dataStack.drop() + } + + // MARK: - Patients => https://github.com/SyncDB/Sync/issues/121 + + func testPatients() { + let objects = Helper.objectsFromJSON("patients.json") as! [[String: Any]] + let dataStack = Helper.dataStackWithModelName("Patients") + + Sync.changes(objects, inEntityNamed: "Patient", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("Patient", inContext: dataStack.mainContext), 1) + XCTAssertEqual(Helper.countForEntity("Baseline", inContext: dataStack.mainContext), 1) + XCTAssertEqual(Helper.countForEntity("Alcohol", inContext: dataStack.mainContext), 1) + XCTAssertEqual(Helper.countForEntity("Fitness", inContext: dataStack.mainContext), 1) + XCTAssertEqual(Helper.countForEntity("Weight", inContext: dataStack.mainContext), 1) + XCTAssertEqual(Helper.countForEntity("Measure", inContext: dataStack.mainContext), 1) + + dataStack.drop() + } + + // MARK: - Bug 84 => https://github.com/SyncDB/Sync/issues/84 + + func testStaffAndfulfillers() { + let objects = Helper.objectsFromJSON("bug-number-84.json") as! [[String: Any]] + let dataStack = Helper.dataStackWithModelName("84") + + Sync.changes(objects, inEntityNamed: "MSStaff", dataStack: dataStack, completion: nil) + + XCTAssertEqual(Helper.countForEntity("MSStaff", inContext: dataStack.mainContext), 1) + + let staff = Helper.fetchEntity("MSStaff", predicate: NSPredicate(format: "xid = %@", "mstaff_F58dVBTsXznvMpCPmpQgyV"), inContext: dataStack.mainContext) + let oneStaff = staff.first! + XCTAssertEqual(oneStaff.value(forKey: "image") as? String, "a.jpg") + XCTAssertEqual((oneStaff.value(forKey: "fulfillers") as? NSSet)!.allObjects.count, 2) + + let numberOffulfillers = Helper.countForEntity("MSFulfiller", inContext: dataStack.mainContext) + XCTAssertEqual(numberOffulfillers, 2) + + let fulfillers = Helper.fetchEntity("MSFulfiller", predicate: NSPredicate(format: "xid = %@", "ffr_AkAHQegYkrobp5xc2ySc5D"), inContext: dataStack.mainContext) + let fullfiller = fulfillers.first! + XCTAssertEqual(fullfiller.value(forKey: "name") as? String, "New York") + XCTAssertEqual((fullfiller.value(forKey: "staff") as? NSSet)!.allObjects.count, 1) + + dataStack.drop() + } + + // MARK: - Bug 113 => https://github.com/SyncDB/Sync/issues/113 + + func testCustomPrimaryKeyBug113() { + let objects = Helper.objectsFromJSON("bug-113-comments-no-id.json") as! [[String: Any]] + let dataStack = Helper.dataStackWithModelName("113") + + Sync.changes(objects, inEntityNamed: "AwesomeComment", dataStack: dataStack, completion: nil) + + XCTAssertEqual(Helper.countForEntity("AwesomeComment", inContext: dataStack.mainContext), 8) + let comments = Helper.fetchEntity("AwesomeComment", predicate: NSPredicate(format: "body = %@", "comment 1"), inContext: dataStack.mainContext) + XCTAssertEqual(comments.count, 1) + XCTAssertEqual((comments.first!.value(forKey: "awesomeComments") as! NSSet).count, 3) + + let comment = comments.first! + XCTAssertEqual(comment.value(forKey: "body") as? String, "comment 1") + + dataStack.drop() + } + + func testCustomPrimaryKeyInsideToManyRelationshipBug113() { + let objects = Helper.objectsFromJSON("bug-113-stories-comments-no-ids.json") as! [[String: Any]] + let dataStack = Helper.dataStackWithModelName("113") + + Sync.changes(objects, inEntityNamed: "AwesomeStory", dataStack: dataStack, completion: nil) + + XCTAssertEqual(Helper.countForEntity("AwesomeStory", inContext: dataStack.mainContext), 3) + let stories = Helper.fetchEntity("AwesomeStory", predicate: NSPredicate(format: "remoteID = %@", NSNumber(value: 0)), inContext: dataStack.mainContext) + let story = stories.first! + XCTAssertEqual((story.value(forKey: "awesomeComments") as! NSSet).count, 3) + + XCTAssertEqual(Helper.countForEntity("AwesomeComment", inContext: dataStack.mainContext), 9) + var comments = Helper.fetchEntity("AwesomeComment", predicate: NSPredicate(format: "body = %@", "comment 1"), inContext: dataStack.mainContext) + XCTAssertEqual(comments.count, 3) + + comments = Helper.fetchEntity("AwesomeComment", predicate: NSPredicate(format: "body = %@ AND awesomeStory = %@", "comment 1", story), inContext: dataStack.mainContext) + XCTAssertEqual(comments.count, 1) + if let comment = comments.first { + XCTAssertEqual(comment.value(forKey: "body") as? String, "comment 1") + let awesomeStory = comment.value(forKey: "awesomeStory") as! NSManagedObject + XCTAssertEqual(awesomeStory.value(forKey: "remoteID") as? NSNumber, NSNumber(value: 0)) + XCTAssertEqual(awesomeStory.value(forKey: "title") as? String, "story 1") + } else { + XCTFail() + } + + dataStack.drop() + } + + func testCustomKeysInRelationshipsToOneBug113() { + let objects = Helper.objectsFromJSON("bug-113-custom_relationship_key_to_one.json") as! [[String: Any]] + let dataStack = Helper.dataStackWithModelName("113") + + Sync.changes(objects, inEntityNamed: "AwesomeStory", dataStack: dataStack, completion: nil) + + let array = Helper.fetchEntity("AwesomeStory", inContext: dataStack.mainContext) + let story = array.first! + XCTAssertNotNil(story.value(forKey: "awesomeSummarize")) + + dataStack.drop() + } + + // MARK: - Bug 125 => https://github.com/SyncDB/Sync/issues/125 + + func testNilRelationshipsAfterUpdating_Sync_1_0_10() { + let formDictionary = Helper.objectsFromJSON("bug-125.json") as! [String: Any] + let uri = formDictionary["uri"] as! String + let dataStack = Helper.dataStackWithModelName("125") + + Sync.changes([formDictionary], inEntityNamed: "Form", predicate: NSPredicate(format: "uri == %@", uri), dataStack: dataStack, completion: nil) + + XCTAssertEqual(Helper.countForEntity("Form", inContext: dataStack.mainContext), 1) + + XCTAssertEqual(Helper.countForEntity("Element", inContext: dataStack.mainContext), 11) + + XCTAssertEqual(Helper.countForEntity("SelectionItem", inContext: dataStack.mainContext), 4) + + XCTAssertEqual(Helper.countForEntity("Model", inContext: dataStack.mainContext), 1) + + XCTAssertEqual(Helper.countForEntity("ModelProperty", inContext: dataStack.mainContext), 9) + + XCTAssertEqual(Helper.countForEntity("Restriction", inContext: dataStack.mainContext), 3) + + let array = Helper.fetchEntity("Form", inContext: dataStack.mainContext) + let form = array.first! + let element = form.value(forKey: "element") as! NSManagedObject + let model = form.value(forKey: "model") as! NSManagedObject + XCTAssertNotNil(element) + XCTAssertNotNil(model) + + dataStack.drop() + } + + func testStoryToSummarize() { + let formDictionary = Helper.objectsFromJSON("story-summarize.json") as! [String: Any] + let dataStack = Helper.dataStackWithModelName("Social") + + Sync.changes([formDictionary], inEntityNamed: "Story", predicate: NSPredicate(format: "remoteID == %@", NSNumber(value: 1)), dataStack: dataStack, completion: nil) + + XCTAssertEqual(Helper.countForEntity("Story", inContext: dataStack.mainContext), 1) + let stories = Helper.fetchEntity("Story", predicate: NSPredicate(format: "remoteID = %@", NSNumber(value: 1)), inContext: dataStack.mainContext) + let story = stories.first! + let summarize = story.value(forKey: "summarize") as! NSManagedObject + XCTAssertEqual(summarize.value(forKey: "remoteID") as? NSNumber, NSNumber(value: 1)) + XCTAssertEqual((story.value(forKey: "comments") as! NSSet).count, 1) + + XCTAssertEqual(Helper.countForEntity("SocialComment", inContext: dataStack.mainContext), 1) + let comments = Helper.fetchEntity("SocialComment", predicate: NSPredicate(format: "body = %@", "Hi"), inContext: dataStack.mainContext) + XCTAssertEqual(comments.count, 1) + + dataStack.drop() + } + + /** + * When having JSONs like this: + * { + * "id":12345, + * "name":"My Project", + * "category_id":12345 + * } + * It will should map category_id with the necesary category object using the ID 12345 + */ + func testIDRelationshipMapping() { + let usersDictionary = Helper.objectsFromJSON("users_a.json") as! [[String: Any]] + let dataStack = Helper.dataStackWithModelName("Notes") + + Sync.changes(usersDictionary, inEntityNamed: "SuperUser", dataStack: dataStack, completion: nil) + + let usersCount = Helper.countForEntity("SuperUser", inContext: dataStack.mainContext) + XCTAssertEqual(usersCount, 8) + + let notesDictionary = Helper.objectsFromJSON("notes_with_user_id.json") as! [[String: Any]] + + Sync.changes(notesDictionary, inEntityNamed: "SuperNote", dataStack: dataStack, completion: nil) + + let notesCount = Helper.countForEntity("SuperNote", inContext: dataStack.mainContext) + XCTAssertEqual(notesCount, 5) + + let notes = Helper.fetchEntity("SuperNote", predicate: NSPredicate(format: "remoteID = %@", NSNumber(value: 0)), inContext: dataStack.mainContext) + let note = notes.first! + let user = note.value(forKey: "superUser")! + XCTAssertEqual((user as AnyObject).value(forKey: "name") as? String, "Melisa White") + + dataStack.drop() + } + + /** + * When having JSONs like this: + * { + * "id":12345, + * "name":"My Project", + * "category":12345 + * } + * It will should map category_id with the necesary category object using the ID 12345, but adding a custom remoteKey would make it map to category with ID 12345 + */ + func testIDRelationshipCustomMapping() { + let usersDictionary = Helper.objectsFromJSON("users_a.json") as! [[String: Any]] + let dataStack = Helper.dataStackWithModelName("NotesB") + + Sync.changes(usersDictionary, inEntityNamed: "SuperUserB", dataStack: dataStack, completion: nil) + + let usersCount = Helper.countForEntity("SuperUserB", inContext: dataStack.mainContext) + XCTAssertEqual(usersCount, 8) + + let notesDictionary = Helper.objectsFromJSON("notes_with_user_id_custom.json") as! [[String: Any]] + + Sync.changes(notesDictionary, inEntityNamed: "SuperNoteB", dataStack: dataStack, completion: nil) + + let notesCount = Helper.countForEntity("SuperNoteB", inContext: dataStack.mainContext) + XCTAssertEqual(notesCount, 5) + + let notes = Helper.fetchEntity("SuperNoteB", predicate: NSPredicate(format: "remoteID = %@", NSNumber(value: 0)), inContext: dataStack.mainContext) + let note = notes.first! + let user = note.value(forKey: "superUser")! + XCTAssertEqual((user as AnyObject).value(forKey: "name") as? String, "Melisa White") + + dataStack.drop() + } + + // MARK: - Ordered Social + + func testCustomPrimaryKeyInOrderedRelationship() { + let objects = Helper.objectsFromJSON("comments-no-id.json") as! [[String: Any]] + let dataStack = Helper.dataStackWithModelName("OrderedSocial") + + Sync.changes(objects, inEntityNamed: "Comment", dataStack: dataStack, completion: nil) + + XCTAssertEqual(Helper.countForEntity("Comment", inContext: dataStack.mainContext), 8) + let comments = Helper.fetchEntity("Comment", predicate: NSPredicate(format: "body = %@", "comment 1"), inContext: dataStack.mainContext) + XCTAssertEqual(comments.count, 1) + XCTAssertEqual((comments.first!.value(forKey: "comments") as! NSOrderedSet).count, 3) + + let comment = comments.first! + XCTAssertEqual(comment.value(forKey: "body") as? String, "comment 1") + + dataStack.drop() + } + + // MARK: - Bug 179 => https://github.com/SyncDB/Sync/issues/179 + + func testConnectMultipleRelationships() { + let places = Helper.objectsFromJSON("bug-179-places.json") as! [[String: Any]] + let routes = Helper.objectsFromJSON("bug-179-routes.json") as! [String: Any] + let dataStack = Helper.dataStackWithModelName("179") + + Sync.changes(places, inEntityNamed: "Place", dataStack: dataStack, completion: nil) + Sync.changes([routes], inEntityNamed: "Route", dataStack: dataStack, completion: nil) + + XCTAssertEqual(Helper.countForEntity("Route", inContext: dataStack.mainContext), 1) + XCTAssertEqual(Helper.countForEntity("Place", inContext: dataStack.mainContext), 2) + let importedRoutes = Helper.fetchEntity("Route", predicate: nil, inContext: dataStack.mainContext) + XCTAssertEqual(importedRoutes.count, 1) + let importedRouter = importedRoutes.first! + XCTAssertEqual(importedRouter.value(forKey: "ident") as? String, "1") + + let startPlace = importedRoutes.first!.value(forKey: "startPlace") as! NSManagedObject + let endPlace = importedRoutes.first!.value(forKey: "endPlace") as! NSManagedObject + XCTAssertEqual(startPlace.value(forKey: "name") as? String, "Here") + XCTAssertEqual(endPlace.value(forKey: "name") as? String, "There") + + dataStack.drop() + } + + // MARK: - Bug 202 => https://github.com/SyncDB/Sync/issues/202 + + func testManyToManyKeyNotAllowedHere() { + let dataStack = Helper.dataStackWithModelName("202") + + let initialInsert = Helper.objectsFromJSON("bug-202-a.json") as! [[String: Any]] + Sync.changes(initialInsert, inEntityNamed: "User", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("Tag", inContext: dataStack.mainContext), 1) + + let removeAll = Helper.objectsFromJSON("bug-202-b.json") as! [[String: Any]] + Sync.changes(removeAll, inEntityNamed: "User", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("Tag", inContext: dataStack.mainContext), 0) + + dataStack.drop() + } + + // MARK: - Automatic use of id as remoteID + + func testIDAsRemoteID() { + let dataStack = Helper.dataStackWithModelName("id") + + let users = Helper.objectsFromJSON("id.json") as! [[String: Any]] + Sync.changes(users, inEntityNamed: "User", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 2) + + dataStack.drop() + } + + // MARK: - Bug 157 => https://github.com/SyncDB/Sync/issues/157 + + func testBug157() { + let dataStack = Helper.dataStackWithModelName("157") + + // 3 locations get synced, their references get ignored since no cities are found + let locations = Helper.objectsFromJSON("157-locations.json") as! [[String: Any]] + Sync.changes(locations, inEntityNamed: "Location", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("Location", inContext: dataStack.mainContext), 3) + XCTAssertEqual(Helper.countForEntity("City", inContext: dataStack.mainContext), 0) + + // 3 cities get synced + let cities = Helper.objectsFromJSON("157-cities.json") as! [[String: Any]] + Sync.changes(cities, inEntityNamed: "City", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("Location", inContext: dataStack.mainContext), 3) + XCTAssertEqual(Helper.countForEntity("City", inContext: dataStack.mainContext), 3) + + // 3 locations get synced, but now since their references are available the relationships get made + Sync.changes(locations, inEntityNamed: "Location", dataStack: dataStack, completion: nil) + var location1 = Helper.fetchEntity("Location", predicate: NSPredicate(format: "locationID = 0"), inContext: dataStack.mainContext).first + var location1City = location1?.value(forKey: "city") as? NSManagedObject + XCTAssertEqual(location1City?.value(forKey: "name") as? String, "Oslo") + var location2 = Helper.fetchEntity("Location", predicate: NSPredicate(format: "locationID = 1"), inContext: dataStack.mainContext).first + var location2City = location2?.value(forKey: "city") as? NSManagedObject + XCTAssertEqual(location2City?.value(forKey: "name") as? String, "Paris") + var location3 = Helper.fetchEntity("Location", predicate: NSPredicate(format: "locationID = 2"), inContext: dataStack.mainContext).first + var location3City = location3?.value(forKey: "city") as? NSManagedObject + XCTAssertNil(location3City?.value(forKey: "name") as? String) + + // Finally we update the relationships to test changing relationships + let updatedLocations = Helper.objectsFromJSON("157-locations-update.json") as! [[String: Any]] + Sync.changes(updatedLocations, inEntityNamed: "Location", dataStack: dataStack, completion: nil) + location1 = Helper.fetchEntity("Location", predicate: NSPredicate(format: "locationID = 0"), inContext: dataStack.mainContext).first + location1City = location1?.value(forKey: "city") as? NSManagedObject + XCTAssertNil(location1City?.value(forKey: "name") as? String) + location2 = Helper.fetchEntity("Location", predicate: NSPredicate(format: "locationID = 1"), inContext: dataStack.mainContext).first + location2City = location2?.value(forKey: "city") as? NSManagedObject + XCTAssertEqual(location2City?.value(forKey: "name") as? String, "Oslo") + location3 = Helper.fetchEntity("Location", predicate: NSPredicate(format: "locationID = 2"), inContext: dataStack.mainContext).first + location3City = location3?.value(forKey: "city") as? NSManagedObject + XCTAssertEqual(location3City?.value(forKey: "name") as? String, "Paris") + + dataStack.drop() + } + + // MARK: - Add support for cancellable sync processes https://github.com/SyncDB/Sync/pull/216 + + func testOperation() { + let dataStack = Helper.dataStackWithModelName("id") + + let users = Helper.objectsFromJSON("id.json") as! [[String: Any]] + let operation = Sync(changes: users, inEntityNamed: "User", predicate: nil, dataStack: dataStack) + operation.start() + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 2) + + dataStack.drop() + } + + // MARK: - Support multiple ids to set a relationship (to-many) => https://github.com/SyncDB/Sync/issues/151 + // Notes have to be unique, two users can't have the same note. + + func testMultipleIDRelationshipToMany() { + let dataStack = Helper.dataStackWithModelName("151-to-many") + + // Inserts 3 users, it ignores the relationships since no notes are found + let users = Helper.objectsFromJSON("151-to-many-users.json") as! [[String: Any]] + Sync.changes(users, inEntityNamed: "User", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 3) + XCTAssertEqual(Helper.countForEntity("Note", inContext: dataStack.mainContext), 0) + + // Inserts 3 notes + let notes = Helper.objectsFromJSON("151-to-many-notes.json") as! [[String: Any]] + Sync.changes(notes, inEntityNamed: "Note", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 3) + XCTAssertEqual(Helper.countForEntity("Note", inContext: dataStack.mainContext), 3) + let savedUsers = Helper.fetchEntity("User", inContext: dataStack.mainContext) + var total = 0 + for user in savedUsers { + let notes = user.value(forKey: "notes") as? Set ?? Set() + total += notes.count + } + XCTAssertEqual(total, 0) + + // Updates the first 3 users, but now it makes the relationships with the notes + Sync.changes(users, inEntityNamed: "User", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 3) + XCTAssertEqual(Helper.countForEntity("Note", inContext: dataStack.mainContext), 3) + var user10 = Helper.fetchEntity("User", predicate: NSPredicate(format: "userID = 10"), inContext: dataStack.mainContext).first + var user10Notes = user10?.value(forKey: "notes") as? Set + XCTAssertEqual(user10Notes?.count, 2) + var user11 = Helper.fetchEntity("User", predicate: NSPredicate(format: "userID = 11"), inContext: dataStack.mainContext).first + var user11Notes = user11?.value(forKey: "notes") as? Set + XCTAssertEqual(user11Notes?.count, 1) + var user12 = Helper.fetchEntity("User", predicate: NSPredicate(format: "userID = 12"), inContext: dataStack.mainContext).first + var user12Notes = user12?.value(forKey: "notes") as? Set + XCTAssertEqual(user12Notes?.count, 0) + var note0 = Helper.fetchEntity("Note", predicate: NSPredicate(format: "noteID = 0"), inContext: dataStack.mainContext).first + var note0User = note0?.value(forKey: "user") as? NSManagedObject + XCTAssertEqual(note0User?.value(forKey: "userID") as? Int, 10) + var note1 = Helper.fetchEntity("Note", predicate: NSPredicate(format: "noteID = 1"), inContext: dataStack.mainContext).first + var note1User = note1?.value(forKey: "user") as? NSManagedObject + XCTAssertEqual(note1User?.value(forKey: "userID") as? Int, 10) + var note2 = Helper.fetchEntity("Note", predicate: NSPredicate(format: "noteID = 2"), inContext: dataStack.mainContext).first + var note2User = note2?.value(forKey: "user") as? NSManagedObject + XCTAssertEqual(note2User?.value(forKey: "userID") as? Int, 11) + + // Updates the first 3 users again, but now it changes all the relationships + let updatedUsers = Helper.objectsFromJSON("151-to-many-users-update.json") as! [[String: Any]] + Sync.changes(updatedUsers, inEntityNamed: "User", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 3) + XCTAssertEqual(Helper.countForEntity("Note", inContext: dataStack.mainContext), 3) + user10 = Helper.fetchEntity("User", predicate: NSPredicate(format: "userID = 10"), inContext: dataStack.mainContext).first + user10Notes = user10?.value(forKey: "notes") as? Set + XCTAssertEqual(user10Notes?.count, 0) + user11 = Helper.fetchEntity("User", predicate: NSPredicate(format: "userID = 11"), inContext: dataStack.mainContext).first + user11Notes = user11?.value(forKey: "notes") as? Set + XCTAssertEqual(user11Notes?.count, 1) + user12 = Helper.fetchEntity("User", predicate: NSPredicate(format: "userID = 12"), inContext: dataStack.mainContext).first + user12Notes = user12?.value(forKey: "notes") as? Set + XCTAssertEqual(user12Notes?.count, 2) + note0 = Helper.fetchEntity("Note", predicate: NSPredicate(format: "noteID = 0"), inContext: dataStack.mainContext).first + note0User = note0?.value(forKey: "user") as? NSManagedObject + XCTAssertEqual(note0User?.value(forKey: "userID") as? Int, 12) + note1 = Helper.fetchEntity("Note", predicate: NSPredicate(format: "noteID = 1"), inContext: dataStack.mainContext).first + note1User = note1?.value(forKey: "user") as? NSManagedObject + XCTAssertEqual(note1User?.value(forKey: "userID") as? Int, 12) + note2 = Helper.fetchEntity("Note", predicate: NSPredicate(format: "noteID = 2"), inContext: dataStack.mainContext).first + note2User = note2?.value(forKey: "user") as? NSManagedObject + XCTAssertEqual(note2User?.value(forKey: "userID") as? Int, 11) + + dataStack.drop() + } + + // MARK: - Support multiple ids to set a relationship (to-many) => https://github.com/SyncDB/Sync/issues/151 + // Notes have to be unique, two users can't have the same note. + + func testOrderedMultipleIDRelationshipToMany() { + let dataStack = Helper.dataStackWithModelName("151-ordered-to-many") + + // Inserts 3 users, it ignores the relationships since no notes are found + let users = Helper.objectsFromJSON("151-to-many-users.json") as! [[String: Any]] + Sync.changes(users, inEntityNamed: "User", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 3) + XCTAssertEqual(Helper.countForEntity("Note", inContext: dataStack.mainContext), 0) + + // Inserts 3 notes + let notes = Helper.objectsFromJSON("151-to-many-notes.json") as! [[String: Any]] + Sync.changes(notes, inEntityNamed: "Note", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 3) + XCTAssertEqual(Helper.countForEntity("Note", inContext: dataStack.mainContext), 3) + let savedUsers = Helper.fetchEntity("User", inContext: dataStack.mainContext) + var total = 0 + for user in savedUsers { + let notes = user.value(forKey: "notes") as? Set ?? Set() + total += notes.count + } + XCTAssertEqual(total, 0) + + // Updates the first 3 users, but now it makes the relationships with the notes + Sync.changes(users, inEntityNamed: "User", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 3) + XCTAssertEqual(Helper.countForEntity("Note", inContext: dataStack.mainContext), 3) + var user10 = Helper.fetchEntity("User", predicate: NSPredicate(format: "id = 10"), inContext: dataStack.mainContext).first + var user10Notes = user10?.value(forKey: "notes") as? NSOrderedSet + XCTAssertEqual(user10Notes?.set.count, 2) + var user11 = Helper.fetchEntity("User", predicate: NSPredicate(format: "id = 11"), inContext: dataStack.mainContext).first + var user11Notes = user11?.value(forKey: "notes") as? NSOrderedSet + XCTAssertEqual(user11Notes?.set.count, 1) + var user12 = Helper.fetchEntity("User", predicate: NSPredicate(format: "id = 12"), inContext: dataStack.mainContext).first + var user12Notes = user12?.value(forKey: "notes") as? NSOrderedSet + XCTAssertEqual(user12Notes?.set.count, 0) + + // Updates the first 3 users again, but now it changes all the relationships + let updatedUsers = Helper.objectsFromJSON("151-to-many-users-update.json") as! [[String: Any]] + Sync.changes(updatedUsers, inEntityNamed: "User", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 3) + XCTAssertEqual(Helper.countForEntity("Note", inContext: dataStack.mainContext), 3) + user10 = Helper.fetchEntity("User", predicate: NSPredicate(format: "id = 10"), inContext: dataStack.mainContext).first + user10Notes = user10?.value(forKey: "notes") as? NSOrderedSet + XCTAssertEqual(user10Notes?.set.count, 0) + user11 = Helper.fetchEntity("User", predicate: NSPredicate(format: "id = 11"), inContext: dataStack.mainContext).first + user11Notes = user11?.value(forKey: "notes") as? NSOrderedSet + XCTAssertEqual(user11Notes?.set.count, 1) + user12 = Helper.fetchEntity("User", predicate: NSPredicate(format: "id = 12"), inContext: dataStack.mainContext).first + user12Notes = user12?.value(forKey: "notes") as? NSOrderedSet + XCTAssertEqual(user12Notes?.set.count, 2) + + dataStack.drop() + } + + // MARK: - Support multiple ids to set a relationship (many-to-many) => https://github.com/SyncDB/Sync/issues/151 + + func testMultipleIDRelationshipManyToMany() { + let dataStack = Helper.dataStackWithModelName("151-many-to-many") + + // Inserts 4 notes + let notes = Helper.objectsFromJSON("151-many-to-many-notes.json") as! [[String: Any]] + Sync.changes(notes, inEntityNamed: "Note", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("Note", inContext: dataStack.mainContext), 4) + XCTAssertEqual(Helper.countForEntity("Tag", inContext: dataStack.mainContext), 0) + + // Inserts 3 tags + let tags = Helper.objectsFromJSON("151-many-to-many-tags.json") as! [[String: Any]] + Sync.changes(tags, inEntityNamed: "Tag", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("Note", inContext: dataStack.mainContext), 4) + XCTAssertEqual(Helper.countForEntity("Tag", inContext: dataStack.mainContext), 2) + let savedNotes = Helper.fetchEntity("Note", inContext: dataStack.mainContext) + var total = 0 + for note in savedNotes { + let tags = note.value(forKey: "tags") as? Set ?? Set() + total += tags.count + } + XCTAssertEqual(total, 0) + + // Updates the first 4 notes, but now it makes the relationships with the tags + Sync.changes(notes, inEntityNamed: "Note", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("Note", inContext: dataStack.mainContext), 4) + XCTAssertEqual(Helper.countForEntity("Tag", inContext: dataStack.mainContext), 2) + var note0 = Helper.fetchEntity("Note", predicate: NSPredicate(format: "noteID = 0"), inContext: dataStack.mainContext).first + var note0Tags = note0?.value(forKey: "tags") as? Set + XCTAssertEqual(note0Tags?.count, 2) + var note1 = Helper.fetchEntity("Note", predicate: NSPredicate(format: "noteID = 1"), inContext: dataStack.mainContext).first + var note1Tags = note1?.value(forKey: "tags") as? Set + XCTAssertEqual(note1Tags?.count, 1) + var note2 = Helper.fetchEntity("Note", predicate: NSPredicate(format: "noteID = 2"), inContext: dataStack.mainContext).first + var note2Tags = note2?.value(forKey: "tags") as? Set + XCTAssertEqual(note2Tags?.count, 0) + var note3 = Helper.fetchEntity("Note", predicate: NSPredicate(format: "noteID = 3"), inContext: dataStack.mainContext).first + var note3Tags = note3?.value(forKey: "tags") as? Set + XCTAssertEqual(note3Tags?.count, 1) + + // Updates the first 4 notes again, but now it changes all the relationships + let updatedNotes = Helper.objectsFromJSON("151-many-to-many-notes-update.json") as! [[String: Any]] + XCTAssertEqual(Helper.countForEntity("Note", inContext: dataStack.mainContext), 4) + XCTAssertEqual(Helper.countForEntity("Tag", inContext: dataStack.mainContext), 2) + Sync.changes(updatedNotes, inEntityNamed: "Note", dataStack: dataStack, completion: nil) + note0 = Helper.fetchEntity("Note", predicate: NSPredicate(format: "noteID = 0"), inContext: dataStack.mainContext).first + note0Tags = note0?.value(forKey: "tags") as? Set + XCTAssertEqual(note0Tags?.count, 1) + note1 = Helper.fetchEntity("Note", predicate: NSPredicate(format: "noteID = 1"), inContext: dataStack.mainContext).first + note1Tags = note1?.value(forKey: "tags") as? Set + XCTAssertEqual(note1Tags?.count, 0) + note2 = Helper.fetchEntity("Note", predicate: NSPredicate(format: "noteID = 2"), inContext: dataStack.mainContext).first + note2Tags = note2?.value(forKey: "tags") as? Set + XCTAssertEqual(note2Tags?.count, 2) + note3 = Helper.fetchEntity("Note", predicate: NSPredicate(format: "noteID = 3"), inContext: dataStack.mainContext).first + note3Tags = note3?.value(forKey: "tags") as? Set + XCTAssertEqual(note3Tags?.count, 0) + + dataStack.drop() + } + + // MARK: - Support multiple ids to set a relationship (many-to-many) => https://github.com/SyncDB/Sync/issues/151 + + func testOrderedMultipleIDRelationshipManyToMany() { + let dataStack = Helper.dataStackWithModelName("151-ordered-many-to-many") + + // Inserts 4 notes + let notes = Helper.objectsFromJSON("151-many-to-many-notes.json") as! [[String: Any]] + Sync.changes(notes, inEntityNamed: "Note", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("Note", inContext: dataStack.mainContext), 4) + XCTAssertEqual(Helper.countForEntity("Tag", inContext: dataStack.mainContext), 0) + + // Inserts 3 tags + let tags = Helper.objectsFromJSON("151-many-to-many-tags.json") as! [[String: Any]] + Sync.changes(tags, inEntityNamed: "Tag", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("Note", inContext: dataStack.mainContext), 4) + XCTAssertEqual(Helper.countForEntity("Tag", inContext: dataStack.mainContext), 2) + let savedNotes = Helper.fetchEntity("Note", inContext: dataStack.mainContext) + var total = 0 + for note in savedNotes { + let tags = note.value(forKey: "tags") as? Set ?? Set() + total += tags.count + } + XCTAssertEqual(total, 0) + + // Updates the first 4 notes, but now it makes the relationships with the tags + Sync.changes(notes, inEntityNamed: "Note", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("Note", inContext: dataStack.mainContext), 4) + XCTAssertEqual(Helper.countForEntity("Tag", inContext: dataStack.mainContext), 2) + var note0 = Helper.fetchEntity("Note", predicate: NSPredicate(format: "id = 0"), inContext: dataStack.mainContext).first + var note0Tags = note0?.value(forKey: "tags") as? NSOrderedSet + XCTAssertEqual(note0Tags?.count, 2) + var note1 = Helper.fetchEntity("Note", predicate: NSPredicate(format: "id = 1"), inContext: dataStack.mainContext).first + var note1Tags = note1?.value(forKey: "tags") as? NSOrderedSet + XCTAssertEqual(note1Tags?.count, 1) + var note2 = Helper.fetchEntity("Note", predicate: NSPredicate(format: "id = 2"), inContext: dataStack.mainContext).first + var note2Tags = note2?.value(forKey: "tags") as? NSOrderedSet + XCTAssertEqual(note2Tags?.count, 0) + var note3 = Helper.fetchEntity("Note", predicate: NSPredicate(format: "id = 3"), inContext: dataStack.mainContext).first + var note3Tags = note3?.value(forKey: "tags") as? NSOrderedSet + XCTAssertEqual(note3Tags?.count, 1) + + // Updates the first 4 notes again, but now it changes all the relationships + let updatedNotes = Helper.objectsFromJSON("151-many-to-many-notes-update.json") as! [[String: Any]] + XCTAssertEqual(Helper.countForEntity("Note", inContext: dataStack.mainContext), 4) + XCTAssertEqual(Helper.countForEntity("Tag", inContext: dataStack.mainContext), 2) + Sync.changes(updatedNotes, inEntityNamed: "Note", dataStack: dataStack, completion: nil) + note0 = Helper.fetchEntity("Note", predicate: NSPredicate(format: "id = 0"), inContext: dataStack.mainContext).first + note0Tags = note0?.value(forKey: "tags") as? NSOrderedSet + XCTAssertEqual(note0Tags?.set.count, 1) + note1 = Helper.fetchEntity("Note", predicate: NSPredicate(format: "id = 1"), inContext: dataStack.mainContext).first + note1Tags = note1?.value(forKey: "tags") as? NSOrderedSet + XCTAssertEqual(note1Tags?.set.count, 0) + note2 = Helper.fetchEntity("Note", predicate: NSPredicate(format: "id = 2"), inContext: dataStack.mainContext).first + note2Tags = note2?.value(forKey: "tags") as? NSOrderedSet + XCTAssertEqual(note2Tags?.set.count, 2) + note3 = Helper.fetchEntity("Note", predicate: NSPredicate(format: "id = 3"), inContext: dataStack.mainContext).first + note3Tags = note3?.value(forKey: "tags") as? NSOrderedSet + XCTAssertEqual(note3Tags?.count, 0) + + dataStack.drop() + } + + // MARK: - Bug 257 => https://github.com/SyncDB/Sync/issues/257 + + func testBug257() { + let dataStack = Helper.dataStackWithModelName("257") + + let JSON = Helper.objectsFromJSON("bug-257.json") as! [String: Any] + Sync.changes([JSON], inEntityNamed: "Workout", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("Workout", inContext: dataStack.mainContext), 1) + XCTAssertEqual(Helper.countForEntity("Exercise", inContext: dataStack.mainContext), 2) + + dataStack.drop() + } + + // MARK: - Bug 254 => https://github.com/SyncDB/Sync/issues/254 + + func testBug254() { + let dataStack = Helper.dataStackWithModelName("254") + + let JSON = Helper.objectsFromJSON("bug-254.json") as! [String: Any] + Sync.changes([JSON], inEntityNamed: "House", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("House", inContext: dataStack.mainContext), 1) + XCTAssertEqual(Helper.countForEntity("Human", inContext: dataStack.mainContext), 1) + + // Verify correct "House -> Resident" connections + let house = Helper.fetchEntity("House", predicate: NSPredicate(format: "id = 0"), inContext: dataStack.mainContext).first + let residents = house?.value(forKey: "residents") as? Set + let resident = residents?.first + XCTAssertEqual(resident?.value(forKey: "id") as? Int, 0) + + let residentHouse = resident?.value(forKey: "residenthouse") as? NSManagedObject + XCTAssertEqual(residentHouse?.value(forKey: "id") as? Int, 0) + + // Verify empty "Ownhouses -> Owners" connections + let human = Helper.fetchEntity("Human", predicate: NSPredicate(format: "id = 0"), inContext: dataStack.mainContext).first + let ownhouses = human?.value(forKey: "ownhouses") as? Set + XCTAssertEqual(ownhouses?.count, 0) + + dataStack.drop() + } + + // MARK: Bug 260 => https://github.com/SyncDB/Sync/issues/260 + + func testBug260CamelCase() { + let dataStack = Helper.dataStackWithModelName("ToOne") + + let snakeCaseJSON = Helper.objectsFromJSON("to-one-snakecase.json") as! [String: Any] + Sync.changes([snakeCaseJSON], inEntityNamed: "RentedHome", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("RentedHome", inContext: dataStack.mainContext), 1) + XCTAssertEqual(Helper.countForEntity("LegalPerson", inContext: dataStack.mainContext), 1) + + dataStack.drop() + } + + func testBug260SnakeCase() { + let dataStack = Helper.dataStackWithModelName("ToOne") + + let camelCaseJSON = Helper.objectsFromJSON("to-one-camelcase.json") as! [String: Any] + Sync.changes([camelCaseJSON], inEntityNamed: "RentedHome", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("RentedHome", inContext: dataStack.mainContext), 1) + XCTAssertEqual(Helper.countForEntity("LegalPerson", inContext: dataStack.mainContext), 1) + + dataStack.drop() + } + + // MARK: Bug 239 => https://github.com/SyncDB/Sync/pull/239 + + func testBug239() { + let carsObject = Helper.objectsFromJSON("bug-239.json") as! [[String: Any]] + let dataStack = Helper.dataStackWithModelName("239") + Sync.changes(carsObject, inEntityNamed: "Racecar", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("Racecar", inContext: dataStack.mainContext), 1) + XCTAssertEqual(Helper.countForEntity("Passenger", inContext: dataStack.mainContext), 2) + + let racecars = Helper.fetchEntity("Racecar", predicate: nil, inContext: dataStack.mainContext) + let racecar = racecars.first! + XCTAssertEqual((racecar.value(forKey: "passengers") as? NSSet)!.allObjects.count, 2) + + dataStack.drop() + } + + // MARK: - https://github.com/SyncDB/Sync/issues/225 + + func test225ReplacedTag() { + let dataStack = Helper.dataStackWithModelName("225") + + let usersA = Helper.objectsFromJSON("225-a.json") as! [[String: Any]] + Sync.changes(usersA, inEntityNamed: "User", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 1) + XCTAssertEqual(Helper.countForEntity("Tag", inContext: dataStack.mainContext), 1) + + // This should remove the old tag reference to the user and insert this new one. + let usersB = Helper.objectsFromJSON("225-a-replaced.json") as! [[String: Any]] + Sync.changes(usersB, inEntityNamed: "User", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 1) + XCTAssertEqual(Helper.countForEntity("Tag", inContext: dataStack.mainContext), 2) + + let user = Helper.fetchEntity("User", inContext: dataStack.mainContext).first! + let predicate = NSPredicate(format: "ANY users IN %@", [user]) + let tags = Helper.fetchEntity("Tag", predicate: predicate, inContext: dataStack.mainContext) + XCTAssertEqual(tags.count, 1) + + dataStack.drop() + } + + func test225RemovedTagsWithEmptyArray() { + let dataStack = Helper.dataStackWithModelName("225") + + let usersA = Helper.objectsFromJSON("225-a.json") as! [[String: Any]] + Sync.changes(usersA, inEntityNamed: "User", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 1) + XCTAssertEqual(Helper.countForEntity("Tag", inContext: dataStack.mainContext), 1) + + // This should remove all the references. + let usersB = Helper.objectsFromJSON("225-a-empty.json") as! [[String: Any]] + Sync.changes(usersB, inEntityNamed: "User", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 1) + + // WARNING: Maybe this shouldn't be 0, but should be 1 instead, since it shouldn't delete the + // object, but instead, it should just remove the reference. + XCTAssertEqual(Helper.countForEntity("Tag", inContext: dataStack.mainContext), 0) + + let user = Helper.fetchEntity("User", inContext: dataStack.mainContext).first! + let predicate = NSPredicate(format: "ANY users IN %@", [user]) + let tags = Helper.fetchEntity("Tag", predicate: predicate, inContext: dataStack.mainContext) + XCTAssertEqual(tags.count, 0) + + dataStack.drop() + } + + func test225RemovedTagsWithNull() { + let dataStack = Helper.dataStackWithModelName("225") + + let usersA = Helper.objectsFromJSON("225-a.json") as! [[String: Any]] + Sync.changes(usersA, inEntityNamed: "User", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 1) + XCTAssertEqual(Helper.countForEntity("Tag", inContext: dataStack.mainContext), 1) + + // WARNING: Maybe this shouldn't be 0, but should be 1 instead, since it shouldn't delete the + // object, but instead, it should just remove the reference. + let usersB = Helper.objectsFromJSON("225-a-null.json") as! [[String: Any]] + Sync.changes(usersB, inEntityNamed: "User", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 1) + XCTAssertEqual(Helper.countForEntity("Tag", inContext: dataStack.mainContext), 0) + + let user = Helper.fetchEntity("User", inContext: dataStack.mainContext).first! + let predicate = NSPredicate(format: "ANY users IN %@", [user]) + let tags = Helper.fetchEntity("Tag", predicate: predicate, inContext: dataStack.mainContext) + XCTAssertEqual(tags.count, 0) + + dataStack.drop() + } + + func test280() { + let dataStack = Helper.dataStackWithModelName("280") + + let routes = Helper.objectsFromJSON("280.json") as! [[String: Any]] + Sync.changes(routes, inEntityNamed: "Route", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("Route", inContext: dataStack.mainContext), 1) + XCTAssertEqual(Helper.countForEntity("RoutePolylineItem", inContext: dataStack.mainContext), 1) + XCTAssertEqual(Helper.countForEntity("RouteStop", inContext: dataStack.mainContext), 1) + + Sync.changes(routes, inEntityNamed: "Route", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("Route", inContext: dataStack.mainContext), 1) + XCTAssertEqual(Helper.countForEntity("RoutePolylineItem", inContext: dataStack.mainContext), 1) + XCTAssertEqual(Helper.countForEntity("RouteStop", inContext: dataStack.mainContext), 1) + + dataStack.drop() + } + + func test283() { + let dataStack = Helper.dataStackWithModelName("283") + + let taskLists = Helper.objectsFromJSON("283.json") as! [[String: Any]] + Sync.changes(taskLists, inEntityNamed: "TaskList", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("TaskList", inContext: dataStack.mainContext), 1) + XCTAssertEqual(Helper.countForEntity("Owner", inContext: dataStack.mainContext), 1) + + let taskList = Helper.fetchEntity("TaskList", inContext: dataStack.mainContext).first! + let owner = taskList.value(forKey: "owner") as? NSManagedObject + XCTAssertNotNil(owner) + + let participants = taskList.value(forKey: "participants") as? NSSet ?? NSSet() + XCTAssertEqual(participants.count, 0) + + dataStack.drop() + } + + func test320RemoveOneToToneWithNull() { + let dataStack = Helper.dataStackWithModelName("320") + + let tagA = NSEntityDescription.insertNewObject(forEntityName: "Tag", into: dataStack.mainContext) + tagA.setValue(10, forKey: "remoteID") + + let userA = NSEntityDescription.insertNewObject(forEntityName: "User", into: dataStack.mainContext) + userA.setValue(1, forKey: "remoteID") + userA.setValue(tagA, forKey: "tag") + + try! dataStack.mainContext.save() + + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 1) + XCTAssertEqual(Helper.countForEntity("Tag", inContext: dataStack.mainContext), 1) + + let usersB = Helper.objectsFromJSON("320.json") as! [[String: Any]] + Sync.changes(usersB, inEntityNamed: "User", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("User", inContext: dataStack.mainContext), 1) + XCTAssertEqual(Helper.countForEntity("Tag", inContext: dataStack.mainContext), 1) + + let user = Helper.fetchEntity("User", inContext: dataStack.mainContext).first! + let predicate = NSPredicate(format: "ANY user = %@", user) + let tag = Helper.fetchEntity("Tag", predicate: predicate, inContext: dataStack.mainContext) + XCTAssertEqual(tag.count, 0) + + dataStack.drop() + } + + func test233() { + let dataStack = Helper.dataStackWithModelName("233") + + let slide1 = NSEntityDescription.insertNewObject(forEntityName: "Slide", into: dataStack.mainContext) + slide1.setValue(1, forKey: "id") + + let slide2 = NSEntityDescription.insertNewObject(forEntityName: "Slide", into: dataStack.mainContext) + slide2.setValue(2, forKey: "id") + + let presentation = NSEntityDescription.insertNewObject(forEntityName: "Presentation", into: dataStack.mainContext) + presentation.setValue(1, forKey: "id") + + let slides = NSMutableOrderedSet() + slides.add(slide1) + slides.add(slide2) + + presentation.setValue(slides, forKey: "slides") + + try! dataStack.mainContext.save() + + XCTAssertEqual(Helper.countForEntity("Presentation", inContext: dataStack.mainContext), 1) + XCTAssertEqual(Helper.countForEntity("Slide", inContext: dataStack.mainContext), 2) + let lastSlide = Helper.fetchEntity("Presentation", inContext: dataStack.mainContext).first!.mutableOrderedSetValue(forKey: "slides").lastObject as! NSManagedObject + let lastSlideID = lastSlide.value(forKey: "id") as! Int + XCTAssertEqual(lastSlideID, 2) + + // Change order of slides, before it was [1, 2], now it will be [2, 1] + let presentationOrderB = Helper.objectsFromJSON("233.json") as! [[String: Any]] + Sync.changes(presentationOrderB, inEntityNamed: "Presentation", dataStack: dataStack, completion: nil) + + XCTAssertEqual(Helper.countForEntity("Presentation", inContext: dataStack.mainContext), 1) + + let firstSlide = Helper.fetchEntity("Presentation", inContext: dataStack.mainContext).first!.mutableOrderedSetValue(forKey: "slides").firstObject as! NSManagedObject + let firstSlideID = firstSlide.value(forKey: "id") as! Int + + // check if order is properly updated + XCTAssertEqual(firstSlideID, lastSlideID) + + dataStack.drop() + } + + // https://github.com/SyncDB/Sync/issues/327 + + func test327OrderedToMany() { + let dataStack = Helper.dataStackWithModelName("233") + + let slide1 = NSEntityDescription.insertNewObject(forEntityName: "Slide", into: dataStack.mainContext) + slide1.setValue(1, forKey: "id") + + let slide2 = NSEntityDescription.insertNewObject(forEntityName: "Slide", into: dataStack.mainContext) + slide2.setValue(2, forKey: "id") + + let presentation = NSEntityDescription.insertNewObject(forEntityName: "Presentation", into: dataStack.mainContext) + presentation.setValue(1, forKey: "id") + + let slides = NSMutableOrderedSet() + slides.add(slide1) + slides.add(slide2) + + presentation.setValue(slides, forKey: "slides") + + try! dataStack.mainContext.save() + + XCTAssertEqual(Helper.countForEntity("Presentation", inContext: dataStack.mainContext), 1) + XCTAssertEqual(Helper.countForEntity("Slide", inContext: dataStack.mainContext), 2) + let lastSlide = Helper.fetchEntity("Presentation", inContext: dataStack.mainContext).first!.mutableOrderedSetValue(forKey: "slides").lastObject as! NSManagedObject + let lastSlideID = lastSlide.value(forKey: "id") as! Int + XCTAssertEqual(lastSlideID, 2) + + // Change order of slides, before it was [1, 2], now it will be [2, 1] + let presentationOrderB = Helper.objectsFromJSON("237.json") as! [[String: Any]] + Sync.changes(presentationOrderB, inEntityNamed: "Presentation", dataStack: dataStack, completion: nil) + + XCTAssertEqual(Helper.countForEntity("Presentation", inContext: dataStack.mainContext), 1) + + let firstSlide = Helper.fetchEntity("Presentation", inContext: dataStack.mainContext).first!.mutableOrderedSetValue(forKey: "slides").firstObject as! NSManagedObject + let firstSlideID = firstSlide.value(forKey: "id") as! Int + + // check if order is properly updated + XCTAssertEqual(firstSlideID, lastSlideID) + + dataStack.drop() + } + + // https://github.com/SyncDB/Sync/issues/265 + + func test265() { + let dataStack = Helper.dataStackWithModelName("265") + let players = Helper.objectsFromJSON("265.json") as! [[String: Any]] + Sync.changes(players, inEntityNamed: "Player", dataStack: dataStack, completion: nil) + + // Player 1 + // Has one player group: 1 + + // Player group 1 + // This player group has two players: [1] + + // This should be 1, but sadly it's two :( + XCTAssertEqual(Helper.countForEntity("Player", inContext: dataStack.mainContext), 2) + XCTAssertEqual(Helper.countForEntity("PlayerGroup", inContext: dataStack.mainContext), 1) + dataStack.drop() + } + + func test3ca82a0() { + let dataStack = Helper.dataStackWithModelName("3ca82a0") + + let taskLists = Helper.objectsFromJSON("3ca82a0.json") as! [[String: Any]] + Sync.changes(taskLists, inEntityNamed: "Article", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("Article", inContext: dataStack.mainContext), 2) + XCTAssertEqual(Helper.countForEntity("ArticleTag", inContext: dataStack.mainContext), 1) + + dataStack.drop() + } + + // MARK: Bug 277 => https://github.com/SyncDB/Sync/pull/277 + + func test277() { + let carsObject = Helper.objectsFromJSON("277.json") as! [[String: Any]] + let dataStack = Helper.dataStackWithModelName("277") + Sync.changes(carsObject, inEntityNamed: "Racecar", dataStack: dataStack, completion: nil) + XCTAssertEqual(Helper.countForEntity("Racecar", inContext: dataStack.mainContext), 2) + XCTAssertEqual(Helper.countForEntity("Passenger", inContext: dataStack.mainContext), 1) + + var racecars = Helper.fetchEntity("Racecar", predicate: nil, inContext: dataStack.mainContext) + var racecar = racecars.first! + XCTAssertEqual(racecar.value(forKey: "remoteID") as? Int, 31) + XCTAssertEqual((racecar.value(forKey: "passengers") as? NSSet)!.allObjects.count, 1) + + racecars = Helper.fetchEntity("Racecar", predicate: nil, inContext: dataStack.mainContext) + racecar = racecars.last! + XCTAssertEqual(racecar.value(forKey: "remoteID") as? Int, 32) + XCTAssertEqual((racecar.value(forKey: "passengers") as? NSSet)!.allObjects.count, 1) + + dataStack.drop() + } +} diff --git a/Tests/Sync/UpdateTests.swift b/Tests/Sync/UpdateTests.swift new file mode 100644 index 00000000..1e3fac0c --- /dev/null +++ b/Tests/Sync/UpdateTests.swift @@ -0,0 +1,52 @@ +import XCTest + +import CoreData +import Sync + +class UpdateTests: XCTestCase { + func testUpdateWithObjectNotFound() { + let dataStack = Helper.dataStackWithModelName("id") + let user = NSEntityDescription.insertNewObject(forEntityName: "User", into: dataStack.mainContext) + user.setValue("id", forKey: "id") + try! dataStack.mainContext.save() + + XCTAssertEqual(1, Helper.countForEntity("User", inContext: dataStack.mainContext)) + let id = try! Sync.update("someotherid", with: [String: Any](), inEntityNamed: "User", using: dataStack.mainContext) + XCTAssertNil(id) + XCTAssertEqual(1, Helper.countForEntity("User", inContext: dataStack.mainContext)) + + dataStack.drop() + } + + func testUpdateWhileMaintainingTheSameID() { + let dataStack = Helper.dataStackWithModelName("id") + let user = NSEntityDescription.insertNewObject(forEntityName: "User", into: dataStack.mainContext) + user.setValue("id", forKey: "id") + try! dataStack.mainContext.save() + + XCTAssertEqual(1, Helper.countForEntity("User", inContext: dataStack.mainContext)) + let updatedObject = try! Sync.update("id", with: ["name": "bossy"], inEntityNamed: "User", using: dataStack.mainContext) + XCTAssertEqual(updatedObject?.value(forKey: "id") as? String, "id") + XCTAssertEqual(1, Helper.countForEntity("User", inContext: dataStack.mainContext)) + + dataStack.mainContext.refresh(user, mergeChanges: false) + + XCTAssertEqual(user.value(forKey: "name") as? String, "bossy") + + dataStack.drop() + } + + func testUpdateWithJSONThatHasNewID() { + let dataStack = Helper.dataStackWithModelName("id") + let user = NSEntityDescription.insertNewObject(forEntityName: "User", into: dataStack.mainContext) + user.setValue("id", forKey: "id") + try! dataStack.mainContext.save() + + XCTAssertEqual(1, Helper.countForEntity("User", inContext: dataStack.mainContext)) + let updatedObject = try! Sync.update("id", with: ["id": "someid"], inEntityNamed: "User", using: dataStack.mainContext) + XCTAssertEqual(updatedObject?.value(forKey: "id") as? String, "someid") + XCTAssertEqual(1, Helper.countForEntity("User", inContext: dataStack.mainContext)) + + dataStack.drop() + } +} diff --git a/Tests/SyncPropertyMapper/DictionaryTests.swift b/Tests/SyncPropertyMapper/DictionaryTests.swift new file mode 100755 index 00000000..dd167475 --- /dev/null +++ b/Tests/SyncPropertyMapper/DictionaryTests.swift @@ -0,0 +1,300 @@ +import CoreData +import XCTest +import Sync + +class DictionaryTests: XCTestCase { + let sampleSnakeCaseJSON = [ + "description": "reserved", + "inflection_binary_data": ["one", "two"], + "inflection_date": "1970-01-01", + "custom_remote_key": "randomRemoteKey", + "inflection_id": 1, + "inflection_string": "string", + "inflection_integer": 1, + "ignored_parameter": "ignored", + "ignore_transformable": "string", + ] as [String : Any] + + func testExportDictionaryWithSnakeCase() { + // Fill in transformable attributes is not supported in Swift 3. Crashes when saving the context. + let dataStack = Helper.dataStackWithModelName("137") + let user = NSEntityDescription.insertNewObject(forEntityName: "InflectionUser", into: dataStack.mainContext) + user.hyp_fill(with: self.sampleSnakeCaseJSON) + try! dataStack.mainContext.save() + + let compared = [ + "description": "reserved", + "inflection_binary_data": NSKeyedArchiver.archivedData(withRootObject: ["one", "two"]) as NSData, + "inflection_date": "1970-01-01", + "randomRemoteKey": "randomRemoteKey", + "inflection_id": 1, + "inflection_string": "string", + "inflection_integer": 1 + ] as [String : Any] + + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd" + formatter.timeZone = TimeZone(identifier: "GMT") + + let result = user.hyp_dictionary(with: formatter, using: .snakeCase) + + for (key, value) in compared { + if let comparedValue = result[key] { + XCTAssertEqual(value as? NSObject, comparedValue as? NSObject) + } + } + + dataStack.drop() + } + + func testExportDictionaryWithCamelCase() { + // Fill in transformable attributes is not supported in Swift 3. Crashes when saving the context. + let dataStack = Helper.dataStackWithModelName("137") + let user = NSEntityDescription.insertNewObject(forEntityName: "InflectionUser", into: dataStack.mainContext) + user.hyp_fill(with: self.sampleSnakeCaseJSON) + try! dataStack.mainContext.save() + + let compared = [ + "description": "reserved", + "inflectionBinaryData": NSKeyedArchiver.archivedData(withRootObject: ["one", "two"]) as NSData, + "inflectionDate": "1970-01-01", + "randomRemoteKey": "randomRemoteKey", + "inflectionID": 1, + "inflectionString": "string", + "inflectionInteger": 1 + ] as [String : Any] + + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd" + formatter.timeZone = TimeZone(identifier: "GMT") + + let result = user.hyp_dictionary(with: formatter, using: .camelCase) + print(result) + XCTAssertEqual(compared as NSDictionary, result as NSDictionary) + + dataStack.drop() + } + + let sampleSnakeCaseJSONWithRelationship = ["inflection_id": 1] as [String : Any] + + func testExportDictionaryWithSnakeCaseRelationshipArray() { + // Fill in transformable attributes is not supported in Swift 3. Crashes when saving the context. + let dataStack = Helper.dataStackWithModelName("137") + let user = NSEntityDescription.insertNewObject(forEntityName: "InflectionUser", into: dataStack.mainContext) + user.hyp_fill(with: self.sampleSnakeCaseJSONWithRelationship) + + let company = NSEntityDescription.insertNewObject(forEntityName: "InflectionCompany", into: dataStack.mainContext) + company.setValue(NSNumber(value: 1), forKey: "inflectionID") + user.setValue(company, forKey: "camelCaseCompany") + + try! dataStack.mainContext.save() + + let compared = [ + "inflection_binary_data": NSNull(), + "inflection_date": NSNull(), + "inflection_id": 1, + "inflection_integer": NSNull(), + "inflection_string": NSNull(), + "randomRemoteKey": NSNull(), + "description": NSNull(), + "camel_case_company": [ + "inflection_id": 1 + ] + ] as [String : Any] + + let result = user.hyp_dictionary(using: .snakeCase, andRelationshipType: .array) + print(result) + XCTAssertEqual(compared as NSDictionary, result as NSDictionary) + + dataStack.drop() + } + + func testExportDictionaryWithCamelCaseRelationshipArray() { + // Fill in transformable attributes is not supported in Swift 3. Crashes when saving the context. + let dataStack = Helper.dataStackWithModelName("137") + let user = NSEntityDescription.insertNewObject(forEntityName: "InflectionUser", into: dataStack.mainContext) + user.hyp_fill(with: self.sampleSnakeCaseJSONWithRelationship) + + let company = NSEntityDescription.insertNewObject(forEntityName: "InflectionCompany", into: dataStack.mainContext) + company.setValue(NSNumber(value: 1), forKey: "inflectionID") + user.setValue(company, forKey: "camelCaseCompany") + + try! dataStack.mainContext.save() + + let compared = [ + "inflectionBinaryData": NSNull(), + "inflectionDate": NSNull(), + "inflectionID": 1, + "inflectionInteger": NSNull(), + "inflectionString": NSNull(), + "randomRemoteKey": NSNull(), + "description": NSNull(), + "camelCaseCompany": [ + "inflectionID": 1 + ] + ] as [String : Any] + + let result = user.hyp_dictionary(using: .camelCase, andRelationshipType: .array) + print(result) + XCTAssertEqual(compared as NSDictionary, result as NSDictionary) + + dataStack.drop() + } + + func testExportDictionaryWithSnakeCaseRelationshipNested() { + // Fill in transformable attributes is not supported in Swift 3. Crashes when saving the context. + let dataStack = Helper.dataStackWithModelName("137") + let user = NSEntityDescription.insertNewObject(forEntityName: "InflectionUser", into: dataStack.mainContext) + user.hyp_fill(with: self.sampleSnakeCaseJSONWithRelationship) + + let company = NSEntityDescription.insertNewObject(forEntityName: "InflectionCompany", into: dataStack.mainContext) + company.setValue(NSNumber(value: 1), forKey: "inflectionID") + user.setValue(company, forKey: "camelCaseCompany") + + try! dataStack.mainContext.save() + + let compared = [ + "inflection_binary_data": NSNull(), + "inflection_date": NSNull(), + "inflection_id": 1, + "inflection_integer": NSNull(), + "inflection_string": NSNull(), + "randomRemoteKey": NSNull(), + "description": NSNull(), + "camel_case_company_attributes": [ + "inflection_id": 1 + ] + ] as [String : Any] + + let result = user.hyp_dictionary(using: .snakeCase, andRelationshipType: .nested) + print(result) + XCTAssertEqual(compared as NSDictionary, result as NSDictionary) + + dataStack.drop() + } + + func testExportDictionaryWithCamelCaseRelationshipNested() { + // Fill in transformable attributes is not supported in Swift 3. Crashes when saving the context. + let dataStack = Helper.dataStackWithModelName("137") + let user = NSEntityDescription.insertNewObject(forEntityName: "InflectionUser", into: dataStack.mainContext) + user.hyp_fill(with: self.sampleSnakeCaseJSONWithRelationship) + + let company = NSEntityDescription.insertNewObject(forEntityName: "InflectionCompany", into: dataStack.mainContext) + company.setValue(NSNumber(value: 1), forKey: "inflectionID") + user.setValue(company, forKey: "camelCaseCompany") + + try! dataStack.mainContext.save() + + let compared = [ + "inflectionBinaryData": NSNull(), + "inflectionDate": NSNull(), + "inflectionID": 1, + "inflectionInteger": NSNull(), + "inflectionString": NSNull(), + "randomRemoteKey": NSNull(), + "description": NSNull(), + "camelCaseCompanyAttributes": [ + "inflectionID": 1 + ] + ] as [String : Any] + + let result = user.hyp_dictionary(using: .camelCase, andRelationshipType: .nested) + print(result) + XCTAssertEqual(compared as NSDictionary, result as NSDictionary) + + dataStack.drop() + } + + func testReservedAttributeNotExportingWell() { + let dataStack = Helper.dataStackWithModelName("142") + let user = NSEntityDescription.insertNewObject(forEntityName: "TwoLetterEntity", into: dataStack.mainContext) + user.hyp_fill(with: ["description": "test"]) + try! dataStack.mainContext.save() + + let compared = ["description": "test"] as [String : Any] + + let result = user.hyp_dictionary(using: .camelCase) + print(result) + XCTAssertEqual(compared as NSDictionary, result as NSDictionary) + + dataStack.drop() + } + + func setUpWorkout(dataStack: DataStack) -> NSManagedObject { + let workout = NSEntityDescription.insertNewObject(forEntityName: "Workout", into: dataStack.mainContext) + workout.setValue(UUID().uuidString, forKey: "id") + workout.setValue(UUID().uuidString, forKey: "workoutDesc") + workout.setValue(UUID().uuidString, forKey: "workoutName") + + let calendar = NSEntityDescription.insertNewObject(forEntityName: "Calendar", into: dataStack.mainContext) + calendar.setValue(UUID().uuidString, forKey: "eventSourceType") + calendar.setValue(UUID().uuidString, forKey: "id") + calendar.setValue(NSNumber(value: true), forKey: "isCompleted") + calendar.setValue(Date(timeIntervalSince1970: 0), forKey: "start") + + let plannedToIDs = NSMutableSet() + plannedToIDs.add(calendar) + workout.setValue(plannedToIDs, forKey: "plannedToIDs") + + let exercise = NSEntityDescription.insertNewObject(forEntityName: "Exercise", into: dataStack.mainContext) + exercise.setValue(UUID().uuidString, forKey: "exerciseDesc") + exercise.setValue(UUID().uuidString, forKey: "exerciseName") + exercise.setValue(UUID().uuidString, forKey: "id") + exercise.setValue(UUID().uuidString, forKey: "mainMuscle") + + let exerciseSet = NSEntityDescription.insertNewObject(forEntityName: "ExerciseSet", into: dataStack.mainContext) + exerciseSet.setValue(UUID().uuidString, forKey: "id") + exerciseSet.setValue(NSNumber(value: true), forKey: "isCompleted") + exerciseSet.setValue(NSNumber(value: 0), forKey: "setNumber") + exerciseSet.setValue(NSNumber(value: 0), forKey: "setReps") + exerciseSet.setValue(NSNumber(value: 0), forKey: "setWeight") + + let exerciseSets = NSMutableSet() + exerciseSets.add(exerciseSet) + exercise.setValue(exerciseSets, forKey: "exerciseSets") + + let workoutExercises = NSMutableSet() + workoutExercises.add(exercise) + workout.setValue(workoutExercises, forKey: "workoutExercises") + + try! dataStack.mainContext.save() + + return workout + } + + func testBug140CamelCase() { + let dataStack = Helper.dataStackWithModelName("140") + + let workout = self.setUpWorkout(dataStack: dataStack) + + let result = workout.hyp_dictionary(using: .camelCase, andRelationshipType: .array) + + let rootKeys = Array(result.keys) + XCTAssertEqual(rootKeys.count, 5) + XCTAssertEqual(rootKeys[0], "plannedToIDs") + XCTAssertEqual(rootKeys[1], "workoutName") + XCTAssertEqual(rootKeys[2], "_id") + XCTAssertEqual(rootKeys[3], "workoutExercises") + XCTAssertEqual(rootKeys[4], "workoutDesc") + + dataStack.drop() + } + + func testBug140SnakeCase() { + let dataStack = Helper.dataStackWithModelName("140") + + let workout = self.setUpWorkout(dataStack: dataStack) + + let result = workout.hyp_dictionary(using: .snakeCase, andRelationshipType: .array) + + let rootKeys = Array(result.keys) + XCTAssertEqual(rootKeys.count, 5) + XCTAssertEqual(rootKeys[0], "planned_to_ids") + XCTAssertEqual(rootKeys[1], "_id") + XCTAssertEqual(rootKeys[2], "workout_desc") + XCTAssertEqual(rootKeys[3], "workout_name") + XCTAssertEqual(rootKeys[4], "workout_exercises") + + dataStack.drop() + } +} diff --git a/Tests/SyncPropertyMapper/FillWithDictionaryTests.swift b/Tests/SyncPropertyMapper/FillWithDictionaryTests.swift new file mode 100755 index 00000000..0105d2b0 --- /dev/null +++ b/Tests/SyncPropertyMapper/FillWithDictionaryTests.swift @@ -0,0 +1,88 @@ +import CoreData +import XCTest + + +class FillWithDictionaryTests: XCTestCase { + + func testBug112() { + let dataStack = Helper.dataStackWithModelName("112") + + let owner = Helper.insertEntity("Owner", dataStack: dataStack) + owner.setValue(1, forKey: "id") + + let taskList = Helper.insertEntity("TaskList", dataStack: dataStack) + taskList.setValue(1, forKey: "id") + taskList.setValue(owner, forKey: "owner") + + let task = Helper.insertEntity("Task", dataStack: dataStack) + task.setValue(1, forKey: "id") + task.setValue(taskList, forKey: "taskList") + task.setValue(owner, forKey: "owner") + + try! dataStack.mainContext.save() + + let ownerBody = [ + "id": 1, + ] as [String: Any] + let taskBoby = [ + "id": 1, + "owner": ownerBody, + ] as [String: Any] + let expected = [ + "id": 1, + "owner": ownerBody, + "tasks": [taskBoby], + ] as [String: Any] + + XCTAssertEqual(expected as NSDictionary, taskList.hyp_dictionary(using: .array) as NSDictionary) + + dataStack.drop() + } + + func testBug121() { + let dataStack = Helper.dataStackWithModelName("121") + + let album = Helper.insertEntity("Album", dataStack: dataStack) + let json = [ + "id": "a", + "coverPhoto": ["id": "b"], + ] as [String: Any] + album.hyp_fill(with: json) + + XCTAssertNotNil(album.value(forKey: "coverPhoto")) + + dataStack.drop() + } + + func testBug123() { + let dataStack = Helper.dataStackWithModelName("123") + let user = Helper.insertEntity("User", dataStack: dataStack) + user.setValue(1, forKey: "id") + user.setValue("Ignore me", forKey: "name") + + try! dataStack.mainContext.save() + let expected = [ + "id": 1, + ] as [String: Any] + + XCTAssertEqual(expected as NSDictionary, user.hyp_dictionary(using: .none) as NSDictionary) + + dataStack.drop() + } + + func testBug129() { + ValueTransformer.setValueTransformer(BadAPIValueTransformer(), forName: NSValueTransformerName(rawValue: "BadAPIValueTransformer")) + + let dataStack = Helper.dataStackWithModelName("129") + + let user = Helper.insertEntity("User", dataStack: dataStack) + let json = [ + "name": ["bad-backend-dev"], + ] as [String: Any] + user.hyp_fill(with: json) + + XCTAssertEqual(user.value(forKey: "name") as? String, "bad-backend-dev") + + dataStack.drop() + } +} diff --git a/Tests/SyncPropertyMapper/HelperTests.m b/Tests/SyncPropertyMapper/HelperTests.m new file mode 100755 index 00000000..89c5b630 --- /dev/null +++ b/Tests/SyncPropertyMapper/HelperTests.m @@ -0,0 +1,141 @@ +@import CoreData; +@import XCTest; +@import Sync; + +#import "NSManagedObject+SyncPropertyMapperHelpers.h" + +@interface PrivateTests : XCTestCase + +@end + +@implementation PrivateTests + +- (id)entityNamed:(NSString *)entityName { + return [NSEntityDescription insertNewObjectForEntityForName:entityName + inManagedObjectContext:self.managedObjectContext]; +} + +- (NSManagedObjectContext *)managedObjectContext { + DataStack *dataStack = [[DataStack alloc] initWithModelName:@"Model" + bundle:[NSBundle bundleForClass:[self class]] + storeType:DataStackStoreTypeInMemory]; + return dataStack.mainContext; +} + +- (void)testAttributeDescriptionForKeyA { + NSManagedObject *company = [self entityNamed:@"Company"]; + NSAttributeDescription *attributeDescription; + + attributeDescription = [company attributeDescriptionForRemoteKey:@"name"]; + XCTAssertEqualObjects(attributeDescription.name, @"name"); + + attributeDescription = [company attributeDescriptionForRemoteKey:@"id"]; + XCTAssertEqualObjects(attributeDescription.name, @"remoteID"); +} + +- (void)testAttributeDescriptionForKeyB { + NSManagedObject *market = [self entityNamed:@"Market"]; + NSAttributeDescription *attributeDescription; + + attributeDescription = [market attributeDescriptionForRemoteKey:@"id"]; + XCTAssertEqualObjects(attributeDescription.name, @"uniqueId"); + + attributeDescription = [market attributeDescriptionForRemoteKey:@"other_attribute"]; + XCTAssertEqualObjects(attributeDescription.name, @"otherAttribute"); +} + +- (void)testAttributeDescriptionForKeyC { + NSManagedObject *user = [self entityNamed:@"User"]; + NSAttributeDescription *attributeDescription; + + attributeDescription = [user attributeDescriptionForRemoteKey:@"age_of_person"]; + XCTAssertEqualObjects(attributeDescription.name, @"age"); + + attributeDescription = [user attributeDescriptionForRemoteKey:@"driver_identifier_str"]; + XCTAssertEqualObjects(attributeDescription.name, @"driverIdentifier"); + + attributeDescription = [user attributeDescriptionForRemoteKey:@"not_found_key"]; + XCTAssertNil(attributeDescription); +} + +- (void)testAttributeDescriptionForKeyD { + NSManagedObject *keyPath = [self entityNamed:@"KeyPath"]; + NSAttributeDescription *attributeDescription; + + attributeDescription = [keyPath attributeDescriptionForRemoteKey:@"snake_parent.value_one"]; + XCTAssertEqualObjects(attributeDescription.name, @"snakeCaseDepthOne"); + + attributeDescription = [keyPath attributeDescriptionForRemoteKey:@"snake_parent.depth_one.depth_two"]; + XCTAssertEqualObjects(attributeDescription.name, @"snakeCaseDepthTwo"); + + attributeDescription = [keyPath attributeDescriptionForRemoteKey:@"camelParent.valueOne"]; + XCTAssertEqualObjects(attributeDescription.name, @"camelCaseDepthOne"); + + attributeDescription = [keyPath attributeDescriptionForRemoteKey:@"camelParent.depthOne.depthTwo"]; + XCTAssertEqualObjects(attributeDescription.name, @"camelCaseDepthTwo"); +} + +- (void)testRemoteKeyForAttributeDescriptionA { + NSManagedObject *company = [self entityNamed:@"Company"]; + NSAttributeDescription *attributeDescription; + + attributeDescription = company.entity.propertiesByName[@"name"]; + XCTAssertEqualObjects([company remoteKeyForAttributeDescription:attributeDescription], @"name"); + + attributeDescription = company.entity.propertiesByName[@"remoteID"]; + XCTAssertEqualObjects([company remoteKeyForAttributeDescription:attributeDescription], @"id"); +} + +- (void)testRemoteKeyForAttributeDescriptionB { + NSManagedObject *market = [self entityNamed:@"Market"]; + NSAttributeDescription *attributeDescription; + + attributeDescription = market.entity.propertiesByName[@"uniqueId"]; + XCTAssertEqualObjects([market remoteKeyForAttributeDescription:attributeDescription], @"id"); + + attributeDescription = market.entity.propertiesByName[@"otherAttribute"]; + XCTAssertEqualObjects([market remoteKeyForAttributeDescription:attributeDescription], @"other_attribute"); +} + +- (void)testRemoteKeyForAttributeDescriptionC { + NSManagedObject *user = [self entityNamed:@"User"]; + NSAttributeDescription *attributeDescription; + + attributeDescription = user.entity.propertiesByName[@"age"]; ; + XCTAssertEqualObjects([user remoteKeyForAttributeDescription:attributeDescription], @"age_of_person"); + + attributeDescription = user.entity.propertiesByName[@"driverIdentifier"]; + XCTAssertEqualObjects([user remoteKeyForAttributeDescription:attributeDescription], @"driver_identifier_str"); + + XCTAssertNil([user remoteKeyForAttributeDescription:nil]); +} + +- (void)testRemoteKeyForAttributeDescriptionD { + NSManagedObject *keyPath = [self entityNamed:@"KeyPath"]; + NSAttributeDescription *attributeDescription; + + attributeDescription = keyPath.entity.propertiesByName[@"snakeCaseDepthOne"]; + XCTAssertEqualObjects([keyPath remoteKeyForAttributeDescription:attributeDescription], @"snake_parent.value_one"); + + attributeDescription = keyPath.entity.propertiesByName[@"snakeCaseDepthTwo"]; + XCTAssertEqualObjects([keyPath remoteKeyForAttributeDescription:attributeDescription], @"snake_parent.depth_one.depth_two"); + + attributeDescription = keyPath.entity.propertiesByName[@"camelCaseDepthOne"]; + XCTAssertEqualObjects([keyPath remoteKeyForAttributeDescription:attributeDescription], @"camelParent.valueOne"); + + attributeDescription = keyPath.entity.propertiesByName[@"camelCaseDepthTwo"]; + XCTAssertEqualObjects([keyPath remoteKeyForAttributeDescription:attributeDescription], @"camelParent.depthOne.depthTwo"); +} + +- (void)testDestroyKey { + NSManagedObject *note = [self entityNamed:@"Note"]; + NSAttributeDescription *attributeDescription; + + attributeDescription = note.entity.propertiesByName[@"destroy"]; ; + XCTAssertEqualObjects([note remoteKeyForAttributeDescription:attributeDescription], @"_destroy"); + + attributeDescription = note.entity.propertiesByName[@"destroy"]; + XCTAssertEqualObjects([note remoteKeyForAttributeDescription:attributeDescription usingRelationshipType:SyncPropertyMapperRelationshipTypeArray], @"destroy"); +} + +@end diff --git a/Tests/SyncPropertyMapper/Models/112.xcdatamodeld/hypbug.xcdatamodel/contents b/Tests/SyncPropertyMapper/Models/112.xcdatamodeld/hypbug.xcdatamodel/contents new file mode 100644 index 00000000..a0bb9ea1 --- /dev/null +++ b/Tests/SyncPropertyMapper/Models/112.xcdatamodeld/hypbug.xcdatamodel/contents @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/SyncPropertyMapper/Models/121.xcdatamodeld/Model.xcdatamodel/contents b/Tests/SyncPropertyMapper/Models/121.xcdatamodeld/Model.xcdatamodel/contents new file mode 100644 index 00000000..bce56a99 --- /dev/null +++ b/Tests/SyncPropertyMapper/Models/121.xcdatamodeld/Model.xcdatamodel/contents @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Tests/SyncPropertyMapper/Models/123.xcdatamodeld/Model.xcdatamodel/contents b/Tests/SyncPropertyMapper/Models/123.xcdatamodeld/Model.xcdatamodel/contents new file mode 100755 index 00000000..5bd4ec7f --- /dev/null +++ b/Tests/SyncPropertyMapper/Models/123.xcdatamodeld/Model.xcdatamodel/contents @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/SyncPropertyMapper/Models/129.xcdatamodeld/hypbug.xcdatamodel/contents b/Tests/SyncPropertyMapper/Models/129.xcdatamodeld/hypbug.xcdatamodel/contents new file mode 100755 index 00000000..ad7fe137 --- /dev/null +++ b/Tests/SyncPropertyMapper/Models/129.xcdatamodeld/hypbug.xcdatamodel/contents @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/SyncPropertyMapper/Models/137.xcdatamodeld/hypbug.xcdatamodel/contents b/Tests/SyncPropertyMapper/Models/137.xcdatamodeld/hypbug.xcdatamodel/contents new file mode 100755 index 00000000..2e7a2c99 --- /dev/null +++ b/Tests/SyncPropertyMapper/Models/137.xcdatamodeld/hypbug.xcdatamodel/contents @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/SyncPropertyMapper/Models/140.xcdatamodeld/.xccurrentversion b/Tests/SyncPropertyMapper/Models/140.xcdatamodeld/.xccurrentversion new file mode 100755 index 00000000..9b33435d --- /dev/null +++ b/Tests/SyncPropertyMapper/Models/140.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + smartworkout.xcdatamodel + + diff --git a/Tests/SyncPropertyMapper/Models/140.xcdatamodeld/smartworkout.xcdatamodel/contents b/Tests/SyncPropertyMapper/Models/140.xcdatamodeld/smartworkout.xcdatamodel/contents new file mode 100755 index 00000000..3d8de2dd --- /dev/null +++ b/Tests/SyncPropertyMapper/Models/140.xcdatamodeld/smartworkout.xcdatamodel/contents @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/SyncPropertyMapper/Models/142.xcdatamodeld/hypbug.xcdatamodel/contents b/Tests/SyncPropertyMapper/Models/142.xcdatamodeld/hypbug.xcdatamodel/contents new file mode 100755 index 00000000..e835928a --- /dev/null +++ b/Tests/SyncPropertyMapper/Models/142.xcdatamodeld/hypbug.xcdatamodel/contents @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/Tests/SyncPropertyMapper/Models/Model.xcdatamodeld/Model.xcdatamodel/contents b/Tests/SyncPropertyMapper/Models/Model.xcdatamodeld/Model.xcdatamodel/contents new file mode 100644 index 00000000..49e034ca --- /dev/null +++ b/Tests/SyncPropertyMapper/Models/Model.xcdatamodeld/Model.xcdatamodel/contents @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/SyncPropertyMapper/Models/Ordered.xcdatamodeld/Ordered.xcdatamodel/contents b/Tests/SyncPropertyMapper/Models/Ordered.xcdatamodeld/Ordered.xcdatamodel/contents new file mode 100644 index 00000000..41134017 --- /dev/null +++ b/Tests/SyncPropertyMapper/Models/Ordered.xcdatamodeld/Ordered.xcdatamodel/contents @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/SyncPropertyMapper/SyncDictionaryTests.m b/Tests/SyncPropertyMapper/SyncDictionaryTests.m new file mode 100755 index 00000000..d5ad5fd8 --- /dev/null +++ b/Tests/SyncPropertyMapper/SyncDictionaryTests.m @@ -0,0 +1,398 @@ +@import CoreData; +@import XCTest; +@import Sync; + +#import "SyncPropertyMapper.h" +#import "SyncTestValueTransformer.h" + + +@interface SyncDictionaryTests : XCTestCase + +@property (nonatomic) NSDate *testDate; + +@end + +@implementation SyncDictionaryTests + +- (NSDate *)testDate { + if (!_testDate) { + _testDate = [NSDate date]; + } + + return _testDate; +} + +#pragma mark - Set up + +- (DataStack *)dataStack { + return [[DataStack alloc] initWithModelName:@"Model" + bundle:[NSBundle bundleForClass:[self class]] + storeType:DataStackStoreTypeInMemory]; +} + +- (id)entityNamed:(NSString *)entityName inContext:(NSManagedObjectContext *)context { + return [NSEntityDescription insertNewObjectForEntityForName:entityName + inManagedObjectContext:context]; +} + +- (NSManagedObject *)userUsingDataStack:(DataStack *)dataStack { + NSManagedObject *user = [self entityNamed:@"User" inContext:dataStack.mainContext]; + [user setValue:@25 forKey:@"age"]; + [user setValue:self.testDate forKey:@"birthDate"]; + [user setValue:@235 forKey:@"contractID"]; + [user setValue:@"ABC8283" forKey:@"driverIdentifier"]; + [user setValue:@"John" forKey:@"firstName"]; + [user setValue:@"Hyperseed" forKey:@"lastName"]; + [user setValue:@"John Description" forKey:@"userDescription"]; + [user setValue:@111 forKey:@"remoteID"]; + [user setValue:@"Manager" forKey:@"userType"]; + [user setValue:self.testDate forKey:@"createdAt"]; + [user setValue:self.testDate forKey:@"updatedAt"]; + [user setValue:@30 forKey:@"numberOfAttendes"]; + [user setValue:@"raw" forKey:@"rawSigned"]; + + NSData *hobbies = [NSKeyedArchiver archivedDataWithRootObject:@[@"Football", + @"Soccer", + @"Code", + @"More code"]]; + [user setValue:hobbies forKey:@"hobbies"]; + + NSData *expenses = [NSKeyedArchiver archivedDataWithRootObject:@{@"cake" : @12.50, + @"juice" : @0.50}]; + [user setValue:expenses forKey:@"expenses"]; + + NSManagedObject *note = [self noteWithID:@1 inContext:dataStack.mainContext]; + [note setValue:user forKey:@"user"]; + + note = [self noteWithID:@14 inContext:dataStack.mainContext]; + [note setValue:user forKey:@"user"]; + [note setValue:@YES forKey:@"destroy"]; + + note = [self noteWithID:@7 inContext:dataStack.mainContext]; + [note setValue:user forKey:@"user"]; + + NSManagedObject *company = [self companyWithID:@1 andName:@"Facebook" inContext:dataStack.mainContext]; + [company setValue:user forKey:@"user"]; + + return user; +} + +- (NSManagedObject *)noteWithID:(NSNumber *)remoteID + inContext:(NSManagedObjectContext *)context { + NSManagedObject *note = [self entityNamed:@"Note" inContext:context]; + [note setValue:remoteID forKey:@"remoteID"]; + [note setValue:[NSString stringWithFormat:@"This is the text for the note %@", remoteID] forKey:@"text"]; + + return note; +} + +- (NSManagedObject *)orderedNoteWithID:(NSNumber *)remoteID + inContext:(NSManagedObjectContext *)context { + NSManagedObject *note = [self entityNamed:@"OrderedNote" inContext:context]; + [note setValue:remoteID forKey:@"remoteID"]; + [note setValue:[NSString stringWithFormat:@"This is the text for the note %@", remoteID] forKey:@"text"]; + + return note; +} + +- (NSManagedObject *)companyWithID:(NSNumber *)remoteID + andName:(NSString *)name + inContext:(NSManagedObjectContext *)context { + NSManagedObject *company = [self entityNamed:@"Company" inContext:context]; + [company setValue:remoteID forKey:@"remoteID"]; + [company setValue:name forKey:@"name"]; + + return company; +} + +#pragma mark - hyp_dictionary + +- (NSDictionary *)userDictionaryWithNoRelationships { + NSDateFormatter *formatter = [NSDateFormatter new]; + formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; + formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZZZZZ"; + NSString *resultDateString = [formatter stringFromDate:self.testDate]; + + NSMutableDictionary *comparedDictionary = [NSMutableDictionary new]; + comparedDictionary[@"age_of_person"] = @25; + comparedDictionary[@"birth_date"] = resultDateString; + comparedDictionary[@"contract_id"] = @235; + comparedDictionary[@"created_at"] = resultDateString; + comparedDictionary[@"description"] = @"John Description"; + comparedDictionary[@"driver_identifier_str"] = @"ABC8283"; + comparedDictionary[@"expenses"] = [NSKeyedArchiver archivedDataWithRootObject:@{@"cake" : @12.50, + @"juice" : @0.50}]; + comparedDictionary[@"first_name"] = @"John"; + comparedDictionary[@"hobbies"] = [NSKeyedArchiver archivedDataWithRootObject:@[@"Football", + @"Soccer", + @"Code", + @"More code"]]; + comparedDictionary[@"id"] = @111; + comparedDictionary[@"ignored_parameter"] = [NSNull null]; + comparedDictionary[@"last_name"] = @"Hyperseed"; + comparedDictionary[@"number_of_attendes"] = @30; + comparedDictionary[@"type"] = @"Manager"; + comparedDictionary[@"updated_at"] = resultDateString; + comparedDictionary[@"signed"] = @"raw"; + + return [comparedDictionary copy]; +} + +- (void)testDictionaryNoRelationships { + DataStack *dataStack = [self dataStack]; + NSManagedObject *user = [self userUsingDataStack:dataStack]; + NSDictionary *dictionary = [user hyp_dictionaryUsingRelationshipType:SyncPropertyMapperRelationshipTypeNone]; + NSDictionary *comparedDictionary = [self userDictionaryWithNoRelationships]; + XCTAssertEqualObjects(dictionary, [comparedDictionary copy]); +} + +- (void)testDictionaryArrayRelationships { + DataStack *dataStack = [self dataStack]; + NSManagedObject *user = [self userUsingDataStack:dataStack]; + NSDictionary *dictionary = [user hyp_dictionaryUsingRelationshipType:SyncPropertyMapperRelationshipTypeArray]; + NSMutableDictionary *comparedDictionary = [[self userDictionaryWithNoRelationships] mutableCopy]; + comparedDictionary[@"company"] = @{@"id" : @1, + @"name" : @"Facebook"}; + + NSArray *notes = dictionary[@"notes"]; + NSSortDescriptor *nameDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"id" ascending:YES]; + NSArray *sortedNotes = [notes sortedArrayUsingDescriptors:[NSArray arrayWithObject:nameDescriptor]]; + NSMutableDictionary *mutableDictionary = [dictionary mutableCopy]; + mutableDictionary[@"notes"] = sortedNotes; + dictionary = [mutableDictionary copy]; + + NSDictionary *note1 = @{@"destroy" : [NSNull null], + @"id" : @1, + @"text" : @"This is the text for the note 1"}; + NSDictionary *note2 = @{@"destroy" : [NSNull null], + @"id" : @7, + @"text" : @"This is the text for the note 7"}; + NSDictionary *note3 = @{@"destroy" : @1, + @"id" : @14, + @"text" : @"This is the text for the note 14"}; + comparedDictionary[@"notes"] = @[note1, note2, note3]; + + XCTAssertEqualObjects(dictionary, [comparedDictionary copy]); +} + +- (void)testDictionaryArrayRelationshipsOrdered { + DataStack *dataStack = [[DataStack alloc] initWithModelName:@"Ordered" + bundle:[NSBundle bundleForClass:[self class]] + storeType:DataStackStoreTypeInMemory]; + + NSManagedObject *user = [self entityNamed:@"OrderedUser" inContext:dataStack.mainContext]; + [user setValue:@"raw" forKey:@"rawSigned"]; + + [user setValue:@"raw" forKey:@"rawSigned"]; + [user setValue:@25 forKey:@"age"]; + [user setValue:self.testDate forKey:@"birthDate"]; + [user setValue:@235 forKey:@"contractID"]; + [user setValue:@"ABC8283" forKey:@"driverIdentifier"]; + [user setValue:@"John" forKey:@"firstName"]; + [user setValue:@"Hyperseed" forKey:@"lastName"]; + [user setValue:@"John Description" forKey:@"orderedUserDescription"]; + [user setValue:@111 forKey:@"remoteID"]; + [user setValue:@"Manager" forKey:@"orderedUserType"]; + [user setValue:self.testDate forKey:@"createdAt"]; + [user setValue:self.testDate forKey:@"updatedAt"]; + [user setValue:@30 forKey:@"numberOfAttendes"]; + + NSData *hobbies = [NSKeyedArchiver archivedDataWithRootObject:@[@"Football", + @"Soccer", + @"Code", + @"More code"]]; + [user setValue:hobbies forKey:@"hobbies"]; + + NSData *expenses = [NSKeyedArchiver archivedDataWithRootObject:@{@"cake" : @12.50, + @"juice" : @0.50}]; + [user setValue:expenses forKey:@"expenses"]; + + NSManagedObject *note = [self orderedNoteWithID:@1 inContext:dataStack.mainContext]; + [note setValue:user forKey:@"user"]; + + note = [self orderedNoteWithID:@14 inContext:dataStack.mainContext]; + [note setValue:user forKey:@"user"]; + [note setValue:@YES forKey:@"destroy"]; + + note = [self orderedNoteWithID:@7 inContext:dataStack.mainContext]; + [note setValue:user forKey:@"user"]; + + NSDictionary *dictionary = [user hyp_dictionaryUsingRelationshipType:SyncPropertyMapperRelationshipTypeArray]; + NSMutableDictionary *comparedDictionary = [[self userDictionaryWithNoRelationships] mutableCopy]; + + NSArray *notes = dictionary[@"notes"]; + NSSortDescriptor *nameDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"id" ascending:YES]; + NSArray *sortedNotes = [notes sortedArrayUsingDescriptors:[NSArray arrayWithObject:nameDescriptor]]; + NSMutableDictionary *mutableDictionary = [dictionary mutableCopy]; + mutableDictionary[@"notes"] = sortedNotes; + dictionary = [mutableDictionary copy]; + + NSDictionary *note1 = @{@"destroy" : [NSNull null], + @"id" : @1, + @"text" : @"This is the text for the note 1"}; + NSDictionary *note2 = @{@"destroy" : [NSNull null], + @"id" : @7, + @"text" : @"This is the text for the note 7"}; + NSDictionary *note3 = @{@"destroy" : @1, + @"id" : @14, + @"text" : @"This is the text for the note 14"}; + comparedDictionary[@"notes"] = @[note1, note2, note3]; + + XCTAssertEqualObjects(dictionary, [comparedDictionary copy]); + + NSString *description = (NSString *)dictionary[@"description"]; + XCTAssertEqualObjects(description, @"John Description"); + + NSString *type = (NSString *)dictionary[@"type"]; + XCTAssertEqualObjects(type, @"Manager"); +} + +- (void)testDictionaryNestedRelationships { + DataStack *dataStack = [self dataStack]; + NSManagedObject *user = [self userUsingDataStack:dataStack]; + NSDictionary *dictionary = [user hyp_dictionary]; + NSMutableDictionary *comparedDictionary = [[self userDictionaryWithNoRelationships] mutableCopy]; + comparedDictionary[@"company_attributes"] = @{@"id" : @1, + @"name" : @"Facebook"}; + + NSDictionary *notesDictionary = dictionary[@"notes_attributes"]; + NSArray *notes = notesDictionary.allValues; + NSSortDescriptor *nameDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"id" ascending:YES]; + NSArray *sortedNotes = [notes sortedArrayUsingDescriptors:[NSArray arrayWithObject:nameDescriptor]]; + NSMutableDictionary *mutableDictionary = [dictionary mutableCopy]; + mutableDictionary[@"notes_attributes"] = sortedNotes; + dictionary = [mutableDictionary copy]; + + NSDictionary *note1 = @{@"_destroy" : [NSNull null], + @"id" : @1, + @"text" : @"This is the text for the note 1"}; + NSDictionary *note2 = @{@"_destroy" : [NSNull null], + @"id" : @7, + @"text" : @"This is the text for the note 7"}; + NSDictionary *note3 = @{@"_destroy" : @1, + @"id" : @14, + @"text" : @"This is the text for the note 14"}; + comparedDictionary[@"notes_attributes"] = @[note1, note2, note3]; + + XCTAssertEqualObjects(dictionary, comparedDictionary); +} + +- (void)testDictionaryDeepRelationships { + DataStack *dataStack = [self dataStack]; + + NSManagedObject *building = [self entityNamed:@"Building" inContext:dataStack.mainContext]; + [building setValue:@1 forKey:@"remoteID"]; + + NSManagedObject *park = [self entityNamed:@"Park" inContext:dataStack.mainContext]; + [park setValue:@1 forKey:@"remoteID"]; + + NSMutableSet *parks = [building valueForKey:@"parks"]; + [parks addObject:park]; + [building setValue:parks forKey:@"parks"]; + + NSManagedObject *apartment = [self entityNamed:@"Apartment" inContext:dataStack.mainContext]; + [apartment setValue:@1 forKey:@"remoteID"]; + + NSManagedObject *room = [self entityNamed:@"Room" inContext:dataStack.mainContext]; + [room setValue:@1 forKey:@"remoteID"]; + + NSMutableSet *rooms = [apartment valueForKey:@"rooms"]; + [rooms addObject:room]; + [apartment setValue:rooms forKey:@"rooms"]; + + NSMutableSet *apartments = [building valueForKey:@"apartments"]; + [apartments addObject:apartment]; + [building setValue:apartments forKey:@"apartments"]; + + NSDictionary *buildingDictionary = [building hyp_dictionaryUsingRelationshipType:SyncPropertyMapperRelationshipTypeArray]; + NSMutableDictionary *compared = [NSMutableDictionary new]; + NSArray *roomsArray = @[@{@"id" : @1}]; + NSArray *apartmentsArray = @[@{@"id" : @1, + @"rooms" : roomsArray}]; + NSArray *parksArray = @[@{@"id" : @1}]; + compared[@"id"] = @1; + compared[@"apartments"] = apartmentsArray; + compared[@"parks"] = parksArray; + + XCTAssertEqualObjects(buildingDictionary, compared); +} + +- (void)testDictionaryValuesKindOfClass { + DataStack *dataStack = [self dataStack]; + NSManagedObject *user = [self userUsingDataStack:dataStack]; + NSDictionary *dictionary = [user hyp_dictionary]; + + XCTAssertTrue([dictionary[@"age_of_person"] isKindOfClass:[NSNumber class]]); + + XCTAssertTrue([dictionary[@"birth_date"] isKindOfClass:[NSString class]]); + + XCTAssertTrue([dictionary[@"contract_id"] isKindOfClass:[NSNumber class]]); + + XCTAssertTrue([dictionary[@"created_at"] isKindOfClass:[NSString class]]); + + XCTAssertTrue([dictionary[@"description"] isKindOfClass:[NSString class]]); + + XCTAssertTrue([dictionary[@"driver_identifier_str"] isKindOfClass:[NSString class]]); + + XCTAssertTrue([dictionary[@"expenses"] isKindOfClass:[NSData class]]); + + XCTAssertTrue([dictionary[@"first_name"] isKindOfClass:[NSString class]]); + + XCTAssertTrue([dictionary[@"hobbies"] isKindOfClass:[NSData class]]); + + XCTAssertTrue([dictionary[@"id"] isKindOfClass:[NSNumber class]]); + + XCTAssertNil(dictionary[@"ignore_transformable"]); + + XCTAssertTrue([dictionary[@"ignored_parameter"] isKindOfClass:[NSNull class]]); + + XCTAssertTrue([dictionary[@"last_name"] isKindOfClass:[NSString class]]); + + XCTAssertTrue([dictionary[@"notes_attributes"] isKindOfClass:[NSDictionary class]]); + + XCTAssertTrue([dictionary[@"number_of_attendes"] isKindOfClass:[NSNumber class]]); + + XCTAssertTrue([dictionary[@"type"] isKindOfClass:[NSString class]]); + + XCTAssertTrue([dictionary[@"updated_at"] isKindOfClass:[NSString class]]); +} + +- (void)testRecursive { + DataStack *dataStack = [self dataStack]; + + NSManagedObject *megachild = [self entityNamed:@"Recursive" inContext:dataStack.mainContext]; + [megachild setValue:@"megachild" forKey:@"remoteID"]; + + NSManagedObject *grandchild = [self entityNamed:@"Recursive" inContext:dataStack.mainContext]; + [grandchild setValue:@"grandchild" forKey:@"remoteID"]; + + NSMutableSet *recursives = [grandchild valueForKey:@"recursives"]; + [recursives addObject:megachild]; + [grandchild setValue:recursives forKey:@"recursives"]; + [megachild setValue:grandchild forKey:@"recursive"]; + + NSManagedObject *child = [self entityNamed:@"Recursive" inContext:dataStack.mainContext]; + [child setValue:@"child" forKey:@"remoteID"]; + + recursives = [child valueForKey:@"recursives"]; + [recursives addObject:grandchild]; + [child setValue:recursives forKey:@"recursives"]; + [grandchild setValue:child forKey:@"recursive"]; + + NSManagedObject *parent = [self entityNamed:@"Recursive" inContext:dataStack.mainContext]; + [parent setValue:@"Parent" forKey:@"remoteID"]; + + recursives = [parent valueForKey:@"recursives"]; + [recursives addObject:child]; + [parent setValue:recursives forKey:@"recursives"]; + [child setValue:parent forKey:@"recursive"]; + + NSDictionary *dictionary = [parent hyp_dictionaryUsingRelationshipType:SyncPropertyMapperRelationshipTypeArray]; + NSArray *megachildArray = @[@{@"id" : @"megachild", @"recursives": @[]}]; + NSArray *grandchildArray = @[@{@"id" : @"grandchild", @"recursives": megachildArray}]; + NSArray *childArray = @[@{@"id" : @"child", @"recursives": grandchildArray}]; + NSDictionary *parentDictionary = @{@"id" : @"Parent", @"recursives" : childArray}; + XCTAssertEqualObjects(dictionary, parentDictionary); +} + +@end diff --git a/Tests/SyncPropertyMapper/SyncFillWithDictionaryTests.m b/Tests/SyncPropertyMapper/SyncFillWithDictionaryTests.m new file mode 100755 index 00000000..77f830f9 --- /dev/null +++ b/Tests/SyncPropertyMapper/SyncFillWithDictionaryTests.m @@ -0,0 +1,461 @@ +@import CoreData; +@import XCTest; +@import Sync; + +#import "SyncPropertyMapper.h" +#import "SyncTestValueTransformer.h" + +@interface SyncFillWithDictionaryTests : XCTestCase + +@property (nonatomic) NSDate *testDate; + +@end + +@implementation SyncFillWithDictionaryTests + +- (NSDate *)testDate { + if (!_testDate) { + _testDate = [NSDate date]; + } + + return _testDate; +} + +#pragma mark - Set up + +- (DataStack *)dataStack { + return [[DataStack alloc] initWithModelName:@"Model" + bundle:[NSBundle bundleForClass:[self class]] + storeType:DataStackStoreTypeInMemory]; +} + +- (id)entityNamed:(NSString *)entityName inContext:(NSManagedObjectContext *)context { + return [NSEntityDescription insertNewObjectForEntityForName:entityName + inManagedObjectContext:context]; +} + +- (NSManagedObject *)userUsingDataStack:(DataStack *)dataStack { + NSManagedObject *user = [self entityNamed:@"User" inContext:dataStack.mainContext]; + [user setValue:@25 forKey:@"age"]; + [user setValue:self.testDate forKey:@"birthDate"]; + [user setValue:@235 forKey:@"contractID"]; + [user setValue:@"ABC8283" forKey:@"driverIdentifier"]; + [user setValue:@"John" forKey:@"firstName"]; + [user setValue:@"Hyperseed" forKey:@"lastName"]; + [user setValue:@"John Description" forKey:@"userDescription"]; + [user setValue:@111 forKey:@"remoteID"]; + [user setValue:@"Manager" forKey:@"userType"]; + [user setValue:self.testDate forKey:@"createdAt"]; + [user setValue:self.testDate forKey:@"updatedAt"]; + [user setValue:@30 forKey:@"numberOfAttendes"]; + [user setValue:@"raw" forKey:@"rawSigned"]; + + NSData *hobbies = [NSKeyedArchiver archivedDataWithRootObject:@[@"Football", + @"Soccer", + @"Code", + @"More code"]]; + [user setValue:hobbies forKey:@"hobbies"]; + + NSData *expenses = [NSKeyedArchiver archivedDataWithRootObject:@{@"cake" : @12.50, + @"juice" : @0.50}]; + [user setValue:expenses forKey:@"expenses"]; + + NSManagedObject *note = [self noteWithID:@1 inContext:dataStack.mainContext]; + [note setValue:user forKey:@"user"]; + + note = [self noteWithID:@14 inContext:dataStack.mainContext]; + [note setValue:user forKey:@"user"]; + [note setValue:@YES forKey:@"destroy"]; + + note = [self noteWithID:@7 inContext:dataStack.mainContext]; + [note setValue:user forKey:@"user"]; + + NSManagedObject *company = [self companyWithID:@1 andName:@"Facebook" inContext:dataStack.mainContext]; + [company setValue:user forKey:@"user"]; + + return user; +} + +- (NSManagedObject *)noteWithID:(NSNumber *)remoteID + inContext:(NSManagedObjectContext *)context { + NSManagedObject *note = [self entityNamed:@"Note" inContext:context]; + [note setValue:remoteID forKey:@"remoteID"]; + [note setValue:[NSString stringWithFormat:@"This is the text for the note %@", remoteID] forKey:@"text"]; + + return note; +} + +- (NSManagedObject *)companyWithID:(NSNumber *)remoteID + andName:(NSString *)name + inContext:(NSManagedObjectContext *)context { + NSManagedObject *company = [self entityNamed:@"Company" inContext:context]; + [company setValue:remoteID forKey:@"remoteID"]; + [company setValue:name forKey:@"name"]; + + return company; +} + +#pragma mark - hyp_fillWithDictionary + +- (void)testAllAttributes { + NSDictionary *values = @{@"integer_string" : @"16", + @"integer16" : @16, + @"integer32" : @32, + @"integer64" : @64, + @"decimal_string" : @"12.2", + @"decimal" : @12.2, + @"double_value_string": @"12.2", + @"double_value": @12.2, + @"float_value_string" : @"12.2", + @"float_value" : @12.2, + @"string" : @"string", + @"boolean" : @YES, + @"binary_data" : @"Data", + @"transformable" : @"Ignore me, too", + @"custom_transformer_string" : @"Foo & bar"}; + + [NSValueTransformer setValueTransformer:[[SyncTestValueTransformer alloc] init] forName:@"SyncTestValueTransformer"]; + + DataStack *dataStack = [self dataStack]; + NSManagedObject *attributes = [self entityNamed:@"Attribute" inContext:dataStack.mainContext]; + [attributes hyp_fillWithDictionary:values]; + + XCTAssertEqualObjects([attributes valueForKey:@"integerString"], @16); + XCTAssertEqualObjects([attributes valueForKey:@"integer16"], @16); + XCTAssertEqualObjects([attributes valueForKey:@"integer32"], @32); + XCTAssertEqualObjects([attributes valueForKey:@"integer64"], @64); + + XCTAssertEqualObjects([attributes valueForKey:@"decimalString"], [NSDecimalNumber decimalNumberWithString:@"12.2"]); + XCTAssertEqualObjects(NSStringFromClass([[attributes valueForKey:@"decimalString"] class]), NSStringFromClass([NSDecimalNumber class])); + XCTAssertNotEqualObjects(NSStringFromClass([[attributes valueForKey:@"decimalString"] class]), NSStringFromClass([NSNumber class])); + + XCTAssertEqualObjects([attributes valueForKey:@"decimal"], [NSDecimalNumber decimalNumberWithString:@"12.2"]); + XCTAssertEqualObjects(NSStringFromClass([[attributes valueForKey:@"decimal"] class]), NSStringFromClass([NSDecimalNumber class])); + XCTAssertNotEqualObjects(NSStringFromClass([[attributes valueForKey:@"decimal"] class]), NSStringFromClass([NSNumber class])); + + XCTAssertEqualObjects([attributes valueForKey:@"doubleValueString"], @12.2); + XCTAssertEqualObjects([attributes valueForKey:@"doubleValue"], @12.2); + XCTAssertEqualWithAccuracy([[attributes valueForKey:@"floatValueString"] longValue], [@12 longValue], 1.0); + XCTAssertEqualWithAccuracy([[attributes valueForKey:@"floatValue"] longValue], [@12 longValue], 1.0); + XCTAssertEqualObjects([attributes valueForKey:@"string"], @"string"); + XCTAssertEqualObjects([attributes valueForKey:@"boolean"], @YES); + XCTAssertEqualObjects([attributes valueForKey:@"binaryData"], [NSKeyedArchiver archivedDataWithRootObject:@"Data"]); + XCTAssertNil([attributes valueForKey:@"transformable"]); + XCTAssertEqualObjects([attributes valueForKey:@"customTransformerString"], @"Foo & bar"); +} + +- (void)testAllAttributesInCamelCase { + NSDictionary *values = @{@"integerString" : @"16", + @"integer16" : @16, + @"integer32" : @32, + @"integer64" : @64, + @"decimalString" : @"12.2", + @"decimal" : @12.2, + @"doubleValueString": @"12.2", + @"doubleValue": @12.2, + @"floatValueString" : @"12.2", + @"floatValue" : @12.2, + @"string" : @"string", + @"boolean" : @YES, + @"binaryData" : @"Data", + @"transformable" : @"Ignore me, too", + @"customTransformerString" : @"Foo & bar"}; + + [NSValueTransformer setValueTransformer:[[SyncTestValueTransformer alloc] init] forName:@"SyncTestValueTransformer"]; + + DataStack *dataStack = [self dataStack]; + NSManagedObject *attributes = [self entityNamed:@"Attribute" inContext:dataStack.mainContext]; + [attributes hyp_fillWithDictionary:values]; + + XCTAssertEqualObjects([attributes valueForKey:@"integerString"], @16); + XCTAssertEqualObjects([attributes valueForKey:@"integer16"], @16); + XCTAssertEqualObjects([attributes valueForKey:@"integer32"], @32); + XCTAssertEqualObjects([attributes valueForKey:@"integer64"], @64); + + XCTAssertEqualObjects([attributes valueForKey:@"decimalString"], [NSDecimalNumber decimalNumberWithString:@"12.2"]); + XCTAssertEqualObjects(NSStringFromClass([[attributes valueForKey:@"decimalString"] class]), NSStringFromClass([NSDecimalNumber class])); + XCTAssertNotEqualObjects(NSStringFromClass([[attributes valueForKey:@"decimalString"] class]), NSStringFromClass([NSNumber class])); + + XCTAssertEqualObjects([attributes valueForKey:@"decimal"], [NSDecimalNumber decimalNumberWithString:@"12.2"]); + XCTAssertEqualObjects(NSStringFromClass([[attributes valueForKey:@"decimal"] class]), NSStringFromClass([NSDecimalNumber class])); + XCTAssertNotEqualObjects(NSStringFromClass([[attributes valueForKey:@"decimal"] class]), NSStringFromClass([NSNumber class])); + + XCTAssertEqualObjects([attributes valueForKey:@"doubleValueString"], @12.2); + XCTAssertEqualObjects([attributes valueForKey:@"doubleValue"], @12.2); + XCTAssertEqualWithAccuracy([[attributes valueForKey:@"floatValueString"] longValue], [@12 longValue], 1.0); + XCTAssertEqualWithAccuracy([[attributes valueForKey:@"floatValue"] longValue], [@12 longValue], 1.0); + XCTAssertEqualObjects([attributes valueForKey:@"string"], @"string"); + XCTAssertEqualObjects([attributes valueForKey:@"boolean"], @YES); + XCTAssertEqualObjects([attributes valueForKey:@"binaryData"], [NSKeyedArchiver archivedDataWithRootObject:@"Data"]); + XCTAssertNil([attributes valueForKey:@"transformable"]); + XCTAssertEqualObjects([attributes valueForKey:@"customTransformerString"], @"Foo & bar"); +} + +- (void)testFillManagedObjectWithDictionary { + NSDictionary *values = @{@"first_name" : @"Jane", + @"last_name" : @"Hyperseed"}; + + DataStack *dataStack = [self dataStack]; + NSManagedObject *user = [self userUsingDataStack:dataStack]; + [user hyp_fillWithDictionary:values]; + + XCTAssertEqualObjects([user valueForKey:@"firstName"], values[@"first_name"]); +} + +- (void)testUpdatingExistingValueWithNull { + NSDictionary *values = @{@"first_name" : @"Jane", + @"last_name" : @"Hyperseed"}; + + DataStack *dataStack = [self dataStack]; + NSManagedObject *user = [self userUsingDataStack:dataStack]; + [user hyp_fillWithDictionary:values]; + + NSDictionary *updatedValues = @{@"first_name" : [NSNull new], + @"last_name" : @"Hyperseed"}; + + [user hyp_fillWithDictionary:updatedValues]; + + XCTAssertNil([user valueForKey:@"firstName"]); +} + +- (void)testAgeNumber { + NSDictionary *values = @{@"age" : @24}; + + DataStack *dataStack = [self dataStack]; + NSManagedObject *user = [self userUsingDataStack:dataStack]; + [user hyp_fillWithDictionary:values]; + + XCTAssertEqualObjects([user valueForKey:@"age"], values[@"age"]); +} + +- (void)testAgeString { + NSDictionary *values = @{@"age" : @"24"}; + + DataStack *dataStack = [self dataStack]; + NSManagedObject *user = [self userUsingDataStack:dataStack]; + [user hyp_fillWithDictionary:values]; + + NSNumberFormatter *formatter = [NSNumberFormatter new]; + NSNumber *age = [formatter numberFromString:values[@"age"]]; + + XCTAssertEqualObjects([user valueForKey:@"age"], age); +} + +- (void)testBornDate { + NSDictionary *values = @{@"birth_date" : @"1989-02-14T00:00:00+00:00"}; + + DataStack *dataStack = [self dataStack]; + NSManagedObject *user = [self userUsingDataStack:dataStack]; + [user hyp_fillWithDictionary:values]; + + NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init]; + dateFormat.dateFormat = @"yyyy-MM-dd"; + dateFormat.timeZone = [NSTimeZone timeZoneWithName:@"GMT"]; + NSDate *date = [dateFormat dateFromString:@"1989-02-14"]; + + XCTAssertEqualObjects([user valueForKey:@"birthDate"], date); +} + +- (void)testUpdate { + NSDictionary *values = @{@"first_name" : @"Jane", + @"last_name" : @"Hyperseed", + @"age" : @30}; + + DataStack *dataStack = [self dataStack]; + NSManagedObject *user = [self userUsingDataStack:dataStack]; + [user hyp_fillWithDictionary:values]; + + NSDictionary *updatedValues = @{@"first_name" : @"Jeanet"}; + + [user hyp_fillWithDictionary:updatedValues]; + + XCTAssertEqualObjects([user valueForKey:@"firstName"], updatedValues[@"first_name"]); + + XCTAssertEqualObjects([user valueForKey:@"lastName"], values[@"last_name"]); +} + +- (void)testUpdateIgnoringEqualValues { + NSDictionary *values = @{@"first_name" : @"Jane", + @"last_name" : @"Hyperseed", + @"age" : @30}; + + DataStack *dataStack = [self dataStack]; + NSManagedObject *user = [self userUsingDataStack:dataStack]; + [user hyp_fillWithDictionary:values]; + + [user.managedObjectContext save:nil]; + + NSDictionary *updatedValues = @{@"first_name" : @"Jane", + @"last_name" : @"Hyperseed", + @"age" : @30}; + + [user hyp_fillWithDictionary:updatedValues]; + + XCTAssertFalse(user.hasChanges); +} + +- (void)testAcronyms { + NSDictionary *values = @{@"contract_id" : @100}; + + DataStack *dataStack = [self dataStack]; + NSManagedObject *user = [self userUsingDataStack:dataStack]; + [user hyp_fillWithDictionary:values]; + + XCTAssertEqualObjects([user valueForKey:@"contractID"], @100); +} + +- (void)testArrayStorage { + NSDictionary *values = @{@"hobbies" : @[@"football", + @"soccer", + @"code"]}; + + DataStack *dataStack = [self dataStack]; + NSManagedObject *user = [self userUsingDataStack:dataStack]; + [user hyp_fillWithDictionary:values]; + + XCTAssertEqualObjects([NSKeyedUnarchiver unarchiveObjectWithData:[user valueForKey:@"hobbies"]][0], @"football"); + + XCTAssertEqualObjects([NSKeyedUnarchiver unarchiveObjectWithData:[user valueForKey:@"hobbies"]][1], @"soccer"); + + XCTAssertEqualObjects([NSKeyedUnarchiver unarchiveObjectWithData:[user valueForKey:@"hobbies"]][2], @"code"); +} + +- (void)testDictionaryStorage { + NSDictionary *values = @{@"expenses" : @{@"cake" : @12.50, + @"juice" : @0.50}}; + + DataStack *dataStack = [self dataStack]; + NSManagedObject *user = [self userUsingDataStack:dataStack]; + [user hyp_fillWithDictionary:values]; + + XCTAssertEqualObjects([NSKeyedUnarchiver unarchiveObjectWithData:[user valueForKey:@"expenses"]][@"cake"], @12.50); + + XCTAssertEqualObjects([NSKeyedUnarchiver unarchiveObjectWithData:[user valueForKey:@"expenses"]][@"juice"], @0.50); +} + +- (void)testReservedWords { + NSDictionary *values = @{@"id": @100, + @"description": @"This is the description?", + @"type": @"user type"}; + DataStack *dataStack = [self dataStack]; + NSManagedObject *user = [self userUsingDataStack:dataStack]; + [user hyp_fillWithDictionary:values]; + + XCTAssertEqualObjects([user valueForKey:@"remoteID"], @100); + XCTAssertEqualObjects([user valueForKey:@"userDescription"], @"This is the description?"); + XCTAssertEqualObjects([user valueForKey:@"userType"], @"user type"); +} + +- (void)testCreatedAt { + NSDictionary *values = @{@"created_at" : @"2014-01-01T00:00:00+00:00", + @"updated_at" : @"2014-01-02", + @"number_of_attendes": @20}; + + DataStack *dataStack = [self dataStack]; + NSManagedObject *user = [self userUsingDataStack:dataStack]; + [user hyp_fillWithDictionary:values]; + + NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init]; + dateFormat.dateFormat = @"yyyy-MM-dd"; + dateFormat.timeZone = [NSTimeZone timeZoneWithName:@"GMT"]; + NSDate *createdAt = [dateFormat dateFromString:@"2014-01-01"]; + NSDate *updatedAt = [dateFormat dateFromString:@"2014-01-02"]; + + XCTAssertEqualObjects([user valueForKey:@"createdAt"], createdAt); + + XCTAssertEqualObjects([user valueForKey:@"updatedAt"], updatedAt); + + XCTAssertEqualObjects([user valueForKey:@"numberOfAttendes"], @20); +} + +- (void)testCustomRemoteKeys { + NSDictionary *values = @{@"age_of_person" : @20, + @"driver_identifier_str" : @"123", + @"signed" : @"salesman"}; + + DataStack *dataStack = [self dataStack]; + NSManagedObject *user = [self userUsingDataStack:dataStack]; + [user hyp_fillWithDictionary:values]; + + XCTAssertEqualObjects([user valueForKey:@"age"], @20); + XCTAssertEqualObjects([user valueForKey:@"driverIdentifier"], @"123"); + XCTAssertEqualObjects([user valueForKey:@"rawSigned"], @"salesman"); +} + +- (void)testIgnoredTransformables { + NSDictionary *values = @{@"ignoreTransformable" : @"I'm going to be ignored"}; + + DataStack *dataStack = [self dataStack]; + NSManagedObject *user = [self userUsingDataStack:dataStack]; + [user hyp_fillWithDictionary:values]; + + XCTAssertNil([user valueForKey:@"ignoreTransformable"]); +} + +- (void)testRegisteredTransformables { + NSDictionary *values = @{@"registeredTransformable" : @"/Date(1451606400000)/"}; + + DataStack *dataStack = [self dataStack]; + NSManagedObject *user = [self userUsingDataStack:dataStack]; + [user hyp_fillWithDictionary:values]; + + NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init]; + dateFormat.dateFormat = @"yyyy-MM-dd"; + dateFormat.timeZone = [NSTimeZone timeZoneWithName:@"GMT"]; + NSDate *date = [dateFormat dateFromString:@"2016-01-01"]; + XCTAssertNotNil([user valueForKey:@"registeredTransformable"]); + XCTAssertEqualObjects([user valueForKey:@"registeredTransformable"], date); + XCTAssertTrue([[user valueForKey:@"registeredTransformable"] isKindOfClass:[NSDate class]]); +} + +- (void)testCustomKey { + DataStack *dataStack = [self dataStack]; + + NSDictionary *values = @{@"id": @"1", + @"other_attribute": @"Market 1"}; + + NSManagedObject *market = [self entityNamed:@"Market" inContext:dataStack.mainContext]; + + [market hyp_fillWithDictionary:values]; + + XCTAssertEqualObjects([market valueForKey:@"uniqueId"], @"1"); + XCTAssertEqualObjects([market valueForKey:@"otherAttribute"], @"Market 1"); +} + +- (void)testCustomKeyPathSnakeCase { + DataStack *dataStack = [self dataStack]; + + NSDictionary *values = @{@"snake_parent": @{ + @"value_one": @"Value 1", + @"depth_one": @{ + @"depth_two": @"Value 2" } + } + }; + + NSManagedObject *keyPaths = [self entityNamed:@"KeyPath" inContext:dataStack.mainContext]; + + [keyPaths hyp_fillWithDictionary:values]; + + XCTAssertEqualObjects([keyPaths valueForKey:@"snakeCaseDepthOne"], @"Value 1"); + XCTAssertEqualObjects([keyPaths valueForKey:@"snakeCaseDepthTwo"], @"Value 2"); +} + +- (void)testCustomKeyPathCamelCase { + DataStack *dataStack = [self dataStack]; + + NSDictionary *values = @{@"camelParent": @{ + @"valueOne": @"Value 1", + @"depthOne": @{ + @"depthTwo": @"Value 2" } + } + }; + + NSManagedObject *keyPaths = [self entityNamed:@"KeyPath" inContext:dataStack.mainContext]; + + [keyPaths hyp_fillWithDictionary:values]; + + XCTAssertEqualObjects([keyPaths valueForKey:@"camelCaseDepthOne"], @"Value 1"); + XCTAssertEqualObjects([keyPaths valueForKey:@"camelCaseDepthTwo"], @"Value 2"); +} + +@end diff --git a/Tests/SyncPropertyMapper/Transformers/BadAPIValueTransformer.swift b/Tests/SyncPropertyMapper/Transformers/BadAPIValueTransformer.swift new file mode 100755 index 00000000..ceaaa1b9 --- /dev/null +++ b/Tests/SyncPropertyMapper/Transformers/BadAPIValueTransformer.swift @@ -0,0 +1,27 @@ +import Foundation + +class BadAPIValueTransformer : ValueTransformer { + override class func transformedValueClass() -> AnyClass { + return String.self as! AnyClass + } + + override class func allowsReverseTransformation() -> Bool { + return true + } + + // Used to transform before inserting into Core Data using `hyp_fill(with:) + override func transformedValue(_ value: Any?) -> Any? { + guard let valueToTransform = value as? Array else { + return value + } + + return valueToTransform.first! + } + + // Used to transform before exporting into JSON using `hyp_dictionary` + override func reverseTransformedValue(_ value: Any?) -> Any? { + guard let stringValue = value as? String else { return value } + + return [stringValue] + } +} diff --git a/Tests/SyncPropertyMapper/Transformers/DateStringTransformer.h b/Tests/SyncPropertyMapper/Transformers/DateStringTransformer.h new file mode 100755 index 00000000..7fd1227e --- /dev/null +++ b/Tests/SyncPropertyMapper/Transformers/DateStringTransformer.h @@ -0,0 +1,8 @@ +@import Foundation; + +/** + This class is transforming "/Date(1460537233000)/" string into an NSDate object that can be stored in Core Data + */ +@interface DateStringTransformer : NSValueTransformer + +@end diff --git a/Tests/SyncPropertyMapper/Transformers/DateStringTransformer.m b/Tests/SyncPropertyMapper/Transformers/DateStringTransformer.m new file mode 100755 index 00000000..40242457 --- /dev/null +++ b/Tests/SyncPropertyMapper/Transformers/DateStringTransformer.m @@ -0,0 +1,26 @@ +#import "DateStringTransformer.h" + +@implementation DateStringTransformer + ++ (Class) transformedValueClass { + return [NSDate class]; +} + ++ (BOOL)allowsReverseTransformation { + return NO; +} + +- (nullable id)transformedValue:(nullable id)value { + if ([value isKindOfClass:[NSString class]]) { + // in this example string should be of "/Date(1460537233000)/" format + NSString *intStr = [[(NSString*)value stringByReplacingOccurrencesOfString:@"/Date(" withString:@""] stringByReplacingOccurrencesOfString:@")/" withString:@""]; + NSInteger timestampMS = [intStr integerValue]; + float timestamp = timestampMS / 1000.0; + NSDate *date = [[NSDate alloc] initWithTimeIntervalSince1970:timestamp]; + return date; + } else { + return value; + } +} + +@end diff --git a/Tests/SyncPropertyMapper/Transformers/SyncTestValueTransformer.h b/Tests/SyncPropertyMapper/Transformers/SyncTestValueTransformer.h new file mode 100755 index 00000000..9ae32ca4 --- /dev/null +++ b/Tests/SyncPropertyMapper/Transformers/SyncTestValueTransformer.h @@ -0,0 +1,5 @@ +@import Foundation; + +@interface SyncTestValueTransformer : NSValueTransformer + +@end diff --git a/Tests/SyncPropertyMapper/Transformers/SyncTestValueTransformer.m b/Tests/SyncPropertyMapper/Transformers/SyncTestValueTransformer.m new file mode 100755 index 00000000..8c331fc4 --- /dev/null +++ b/Tests/SyncPropertyMapper/Transformers/SyncTestValueTransformer.m @@ -0,0 +1,43 @@ +#import "SyncTestValueTransformer.h" + +@implementation SyncTestValueTransformer + ++ (Class)transformedValueClass { + return [NSString class]; +} + ++ (BOOL)allowsReverseTransformation { + return YES; +} + +- (id)transformedValue:(id)value { + if (value == nil) return nil; + + NSString *stringValue = nil; + + if ([value isKindOfClass:[NSString class]]) { + stringValue = (NSString *)value; + } else { + [NSException raise:NSInternalInconsistencyException + format:@"Value (%@) is not of type NSString.", [value class]]; + } + + return [stringValue stringByReplacingOccurrencesOfString:@"&" withString:@"&"]; +} + +- (id)reverseTransformedValue:(id)value { + if (value == nil) return nil; + + NSString *stringValue = nil; + + if ([value isKindOfClass:[NSString class]]) { + stringValue = (NSString *)value; + } else { + [NSException raise:NSInternalInconsistencyException + format:@"Value (%@) is not of type NSString.", [value class]]; + } + + return [stringValue stringByReplacingOccurrencesOfString:@"&" withString:@"&"]; +} + +@end diff --git a/Tests/TestCheck/TestCheckTests.swift b/Tests/TestCheck/TestCheckTests.swift new file mode 100755 index 00000000..ee7e3ce6 --- /dev/null +++ b/Tests/TestCheck/TestCheckTests.swift @@ -0,0 +1,8 @@ +import XCTest +import Sync + +class TestCheckTests: XCTestCase { + func testIsRunning() { + XCTAssertTrue(TestCheck.isTesting) + } +} diff --git a/Tests/Vendor/JSON.swift b/Tests/Vendor/JSON.swift new file mode 100755 index 00000000..51d30dfc --- /dev/null +++ b/Tests/Vendor/JSON.swift @@ -0,0 +1,43 @@ +import Foundation + +/// The ParsingError codes generated by JSON. +public enum ParsingError: Error { + case notFound, failed +} + +public class JSON { + /** + Returns a JSON object from a file. + - parameter fileName: The name of the file, the expected extension is `.json`. + - parameter bundle: The NSBundle where the file is located, by default is the main bundle. + - returns: A JSON object, it can be either a Dictionary or an Array. + */ + public class func from(_ fileName: String, bundle: Bundle = Bundle.main) throws -> Any? { + var JSON: Any? + + guard let url = URL(string: fileName), let filePath = bundle.path(forResource: url.deletingPathExtension().absoluteString, ofType: url.pathExtension) else { throw ParsingError.notFound } + + guard let data = try? Data(contentsOf: URL(fileURLWithPath: filePath)) else { throw ParsingError.failed } + + JSON = try data.toJSON() + + return JSON + } +} + +public extension Data { + /** + Converts NSData to a JSON object. + - returns: A JSON object, it can be either a Dictionary or an Array. + */ + public func toJSON() throws -> Any? { + var JSON: Any? + do { + JSON = try JSONSerialization.jsonObject(with: self, options: []) as Any + } catch { + throw ParsingError.failed + } + + return JSON + } +}