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
+ }
+}