From 76a2bc1da28f2e399ca82f661f4c134627c2a3fe Mon Sep 17 00:00:00 2001 From: John Rommel Estropia Date: Sun, 12 Jun 2016 20:34:13 +0900 Subject: [PATCH] importing unit tests --- CoreStore.xcodeproj/project.pbxproj | 24 +- CoreStoreTests/BaseTests/BaseTestCase.swift | 16 +- CoreStoreTests/CoreStoreTests.swift | 364 ------- CoreStoreTests/FetchTests.swift | 190 ++++ CoreStoreTests/ImportTests.swift | 983 ++++++++++++++++++ .../Model.xcdatamodel/contents | 12 +- CoreStoreTests/ObjectObserverTests.swift | 37 + CoreStoreTests/TransactionTests.swift | 314 +++++- .../BaseDataTransaction+Importing.swift | 2 +- .../CoreStoreFetchedResultsController.swift | 2 +- .../Internal/NSManagedObjectModel+Setup.swift | 8 +- Sources/Logging/CoreStore+Logging.swift | 12 + Sources/Logging/CoreStoreLogger.swift | 16 + Sources/Logging/DefaultLogger.swift | 17 +- .../Transactions/BaseDataTransaction.swift | 10 +- 15 files changed, 1603 insertions(+), 404 deletions(-) delete mode 100644 CoreStoreTests/CoreStoreTests.swift create mode 100644 CoreStoreTests/ImportTests.swift create mode 100644 CoreStoreTests/ObjectObserverTests.swift diff --git a/CoreStore.xcodeproj/project.pbxproj b/CoreStore.xcodeproj/project.pbxproj index c562705..037ea5c 100644 --- a/CoreStore.xcodeproj/project.pbxproj +++ b/CoreStore.xcodeproj/project.pbxproj @@ -8,7 +8,6 @@ /* Begin PBXBuildFile section */ 2F03A53619C5C6DA005002A5 /* CoreStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F03A53519C5C6DA005002A5 /* CoreStore.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 2F03A54019C5C6DA005002A5 /* CoreStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F03A53F19C5C6DA005002A5 /* CoreStoreTests.swift */; }; 2F03A54D19C5C872005002A5 /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2F03A54C19C5C872005002A5 /* CoreData.framework */; }; 2F291E2719C6D3CF007AF63F /* CoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F291E2619C6D3CF007AF63F /* CoreStore.swift */; }; 82BA18931C4BBCBA00A0916E /* CoreStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 82BA18891C4BBCBA00A0916E /* CoreStore.framework */; }; @@ -66,7 +65,6 @@ 82BA18D61C4BBD7100A0916E /* NSManagedObjectContext+Transaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F331AFF85470064E85B /* NSManagedObjectContext+Transaction.swift */; }; 82BA18D71C4BBD7100A0916E /* NSManagedObjectModel+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51BE0691B47FC4B0069F532 /* NSManagedObjectModel+Setup.swift */; }; 82BA18D81C4BBD7100A0916E /* WeakObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F2D1AFF849C0064E85B /* WeakObject.swift */; }; - 82BA18D91C4BBD9700A0916E /* CoreStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F03A53F19C5C6DA005002A5 /* CoreStoreTests.swift */; }; 82BA18DC1C4BBD9C00A0916E /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B5D372821A39CD6900F583D9 /* Model.xcdatamodeld */; }; 82BA18DD1C4BBE1400A0916E /* NSFetchedResultsController+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5202CF91C04688100DED140 /* NSFetchedResultsController+Convenience.swift */; }; 82BA18DF1C4BBE2600A0916E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 82BA18DE1C4BBE2600A0916E /* Foundation.framework */; }; @@ -99,6 +97,12 @@ B51FE5AF1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51FE5AA1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift */; }; B5202CFA1C04688100DED140 /* NSFetchedResultsController+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5202CF91C04688100DED140 /* NSFetchedResultsController+Convenience.swift */; }; B5202CFD1C046E8400DED140 /* NSFetchedResultsController+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5202CF91C04688100DED140 /* NSFetchedResultsController+Convenience.swift */; }; + B5220E081D0C5F8D009BC71E /* ObjectObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5220E071D0C5F8D009BC71E /* ObjectObserverTests.swift */; }; + B5220E091D0C5F8D009BC71E /* ObjectObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5220E071D0C5F8D009BC71E /* ObjectObserverTests.swift */; }; + B5220E0A1D0C5F8D009BC71E /* ObjectObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5220E071D0C5F8D009BC71E /* ObjectObserverTests.swift */; }; + B5220E0C1D0D0D19009BC71E /* ImportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5220E0B1D0D0D19009BC71E /* ImportTests.swift */; }; + B5220E0D1D0D0D19009BC71E /* ImportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5220E0B1D0D0D19009BC71E /* ImportTests.swift */; }; + B5220E0E1D0D0D19009BC71E /* ImportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5220E0B1D0D0D19009BC71E /* ImportTests.swift */; }; B525576C1CFAF18F00E51965 /* IntoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B525576B1CFAF18F00E51965 /* IntoTests.swift */; }; B525576D1CFAF18F00E51965 /* IntoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B525576B1CFAF18F00E51965 /* IntoTests.swift */; }; B525576E1CFAF18F00E51965 /* IntoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B525576B1CFAF18F00E51965 /* IntoTests.swift */; }; @@ -181,7 +185,6 @@ B52DD1C91BE1F94600949AFE /* NSManagedObjectContext+Transaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F331AFF85470064E85B /* NSManagedObjectContext+Transaction.swift */; }; B52DD1CA1BE1F94600949AFE /* NSManagedObjectModel+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51BE0691B47FC4B0069F532 /* NSManagedObjectModel+Setup.swift */; }; B52DD1CB1BE1F94600949AFE /* WeakObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F2D1AFF849C0064E85B /* WeakObject.swift */; }; - B52DD1CC1BE1F94D00949AFE /* CoreStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F03A53F19C5C6DA005002A5 /* CoreStoreTests.swift */; }; B53FB9FE1CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53FB9FD1CAB2D2F00F0D40A /* CSMigrationResult.swift */; }; B53FB9FF1CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53FB9FD1CAB2D2F00F0D40A /* CSMigrationResult.swift */; }; B53FBA001CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53FB9FD1CAB2D2F00F0D40A /* CSMigrationResult.swift */; }; @@ -660,7 +663,6 @@ 2F03A53519C5C6DA005002A5 /* CoreStore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CoreStore.h; sourceTree = ""; }; 2F03A53B19C5C6DA005002A5 /* CoreStoreTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CoreStoreTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 2F03A53E19C5C6DA005002A5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 2F03A53F19C5C6DA005002A5 /* CoreStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = CoreStoreTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 2F03A54C19C5C872005002A5 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 2F291E2619C6D3CF007AF63F /* CoreStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = CoreStore.swift; sourceTree = ""; }; 82BA18891C4BBCBA00A0916E /* CoreStore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CoreStore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -676,6 +678,8 @@ B51BE0691B47FC4B0069F532 /* NSManagedObjectModel+Setup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectModel+Setup.swift"; sourceTree = ""; }; B51FE5AA1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStore+CustomDebugStringConvertible.swift"; sourceTree = ""; }; B5202CF91C04688100DED140 /* NSFetchedResultsController+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSFetchedResultsController+Convenience.swift"; sourceTree = ""; }; + B5220E071D0C5F8D009BC71E /* ObjectObserverTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectObserverTests.swift; sourceTree = ""; }; + B5220E0B1D0D0D19009BC71E /* ImportTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportTests.swift; sourceTree = ""; }; B525576B1CFAF18F00E51965 /* IntoTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntoTests.swift; sourceTree = ""; }; B525576F1D02561A00E51965 /* SelectTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectTests.swift; sourceTree = ""; }; B52557731D02791400E51965 /* WhereTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WhereTests.swift; sourceTree = ""; }; @@ -954,13 +958,14 @@ B5489F4A1CF5F743008B4978 /* BaseTests */, B5DBE2DD1C9939E100B5CEFA /* BridgingTests.h */, B5DBE2DE1C9939E100B5CEFA /* BridgingTests.m */, - 2F03A53F19C5C6DA005002A5 /* CoreStoreTests.swift */, B5519A3F1CA1B17B002BEF78 /* ErrorTests.swift */, B52557871D02DE8100E51965 /* FetchTests.swift */, B5489F4F1CF603D5008B4978 /* FromTests.swift */, B525577B1D0291FE00E51965 /* GroupByTests.swift */, + B5220E0B1D0D0D19009BC71E /* ImportTests.swift */, B525576B1CFAF18F00E51965 /* IntoTests.swift */, B5DC47C51C93D22900FA3BF3 /* MigrationChainTests.swift */, + B5220E071D0C5F8D009BC71E /* ObjectObserverTests.swift */, B52557771D02826E00E51965 /* OrderByTests.swift */, B57D27C11D0BC20100539C58 /* QueryTests.swift */, B52557831D02A07400E51965 /* SectionByTests.swift */, @@ -1764,13 +1769,14 @@ B5519A401CA1B17B002BEF78 /* ErrorTests.swift in Sources */, B525577C1D0291FE00E51965 /* GroupByTests.swift in Sources */, B52557741D02791400E51965 /* WhereTests.swift in Sources */, - 2F03A54019C5C6DA005002A5 /* CoreStoreTests.swift in Sources */, B5DC47C61C93D22900FA3BF3 /* MigrationChainTests.swift in Sources */, B525576C1CFAF18F00E51965 /* IntoTests.swift in Sources */, + B5220E0C1D0D0D19009BC71E /* ImportTests.swift in Sources */, B5D372841A39CD6900F583D9 /* Model.xcdatamodeld in Sources */, B52557881D02DE8100E51965 /* FetchTests.swift in Sources */, B5489F501CF603D5008B4978 /* FromTests.swift in Sources */, B52557781D02826E00E51965 /* OrderByTests.swift in Sources */, + B5220E081D0C5F8D009BC71E /* ObjectObserverTests.swift in Sources */, B5489F421CF5EEBC008B4978 /* TestEntity2.swift in Sources */, B52557701D02561A00E51965 /* SelectTests.swift in Sources */, B5489F461CF5F017008B4978 /* TransactionTests.swift in Sources */, @@ -1911,16 +1917,17 @@ B52557751D02791400E51965 /* WhereTests.swift in Sources */, B5DC47C71C93D22900FA3BF3 /* MigrationChainTests.swift in Sources */, B5DBE2E01C9939E100B5CEFA /* BridgingTests.m in Sources */, + B5220E0D1D0D0D19009BC71E /* ImportTests.swift in Sources */, B525576D1CFAF18F00E51965 /* IntoTests.swift in Sources */, B580857B1CDF808D004C2EEB /* SetupTests.swift in Sources */, B52557891D02DE8100E51965 /* FetchTests.swift in Sources */, B5489F511CF603D5008B4978 /* FromTests.swift in Sources */, + B5220E091D0C5F8D009BC71E /* ObjectObserverTests.swift in Sources */, B52557791D02826E00E51965 /* OrderByTests.swift in Sources */, B5489F431CF5EEBC008B4978 /* TestEntity2.swift in Sources */, B52557711D02561A00E51965 /* SelectTests.swift in Sources */, B5489F471CF5F017008B4978 /* TransactionTests.swift in Sources */, B52557811D029D2500E51965 /* TweakTests.swift in Sources */, - 82BA18D91C4BBD9700A0916E /* CoreStoreTests.swift in Sources */, B5489F4D1CF5F743008B4978 /* BaseTestCase.swift in Sources */, 82BA18DC1C4BBD9C00A0916E /* Model.xcdatamodeld in Sources */, B57D27BF1D0BBE8200539C58 /* BaseTestDataTestCase.swift in Sources */, @@ -2037,16 +2044,17 @@ B52557761D02791400E51965 /* WhereTests.swift in Sources */, B5DC47C81C93D22900FA3BF3 /* MigrationChainTests.swift in Sources */, B5DBE2E11C9939E100B5CEFA /* BridgingTests.m in Sources */, + B5220E0E1D0D0D19009BC71E /* ImportTests.swift in Sources */, B525576E1CFAF18F00E51965 /* IntoTests.swift in Sources */, B580857C1CDF808F004C2EEB /* SetupTests.swift in Sources */, B525578A1D02DE8100E51965 /* FetchTests.swift in Sources */, B5489F521CF603D5008B4978 /* FromTests.swift in Sources */, + B5220E0A1D0C5F8D009BC71E /* ObjectObserverTests.swift in Sources */, B525577A1D02826E00E51965 /* OrderByTests.swift in Sources */, B5489F441CF5EEBC008B4978 /* TestEntity2.swift in Sources */, B52557721D02561A00E51965 /* SelectTests.swift in Sources */, B5489F481CF5F017008B4978 /* TransactionTests.swift in Sources */, B52557821D029D2500E51965 /* TweakTests.swift in Sources */, - B52DD1CC1BE1F94D00949AFE /* CoreStoreTests.swift in Sources */, B5489F4E1CF5F743008B4978 /* BaseTestCase.swift in Sources */, B5598BCC1BE2093D0092EFCE /* Model.xcdatamodeld in Sources */, B57D27C01D0BBE8200539C58 /* BaseTestDataTestCase.swift in Sources */, diff --git a/CoreStoreTests/BaseTests/BaseTestCase.swift b/CoreStoreTests/BaseTests/BaseTestCase.swift index 305e6c3..022b05b 100644 --- a/CoreStoreTests/BaseTests/BaseTestCase.swift +++ b/CoreStoreTests/BaseTests/BaseTestCase.swift @@ -70,7 +70,7 @@ class BaseTestCase: XCTestCase { CoreStore.logger = TestLogger(self.prepareLoggerExpectations(expectations)) defer { - self.waitForExpectationsWithTimeout(0, handler: nil) + self.checkExpectationsImmediately() CoreStore.logger = TestLogger([:]) } return closure() @@ -93,6 +93,17 @@ class BaseTestCase: XCTestCase { return testExpectations } + @nonobjc + func checkExpectationsImmediately() { + + self.waitForExpectationsWithTimeout(0, handler: nil) + } + + @nonobjc + func waitAndCheckExpectations() { + + self.waitForExpectationsWithTimeout(10, handler: nil) + } // MARK: XCTestCase @@ -165,10 +176,9 @@ class TestLogger: CoreStoreLogger { self.fulfill(.AssertionFailure) } - @noreturn func fatalError(message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) { + func abort(message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) { self.fulfill(.FatalError) - Swift.fatalError() } diff --git a/CoreStoreTests/CoreStoreTests.swift b/CoreStoreTests/CoreStoreTests.swift deleted file mode 100644 index 0b5fa95..0000000 --- a/CoreStoreTests/CoreStoreTests.swift +++ /dev/null @@ -1,364 +0,0 @@ -// -// CoreStoreTests.swift -// CoreStoreTests -// -// Copyright © 2014 John Rommel Estropia -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// - -import XCTest - -@testable -import CoreStore - -class CoreStoreTests: XCTestCase { - - override func setUp() { - - super.setUp() - self.deleteStores() - } - - override func tearDown() { - - self.deleteStores() - super.tearDown() - } - - func testExample() { - - let stack = DataStack( - modelName: "Model", - bundle: NSBundle(forClass: self.dynamicType) - ) - CoreStore.defaultStack = stack - XCTAssert(CoreStore.defaultStack === stack, "CoreStore.defaultStack === stack") - - do { - - try stack.addStorageAndWait( - SQLiteStore( - fileName: "ConfigStore1.sqlite", - configuration: "Config1", - localStorageOptions: .RecreateStoreOnModelMismatch - ) - ) - } - catch let error as NSError { - - XCTFail(error.description) - } - - do { - - try stack.addStorageAndWait( - SQLiteStore( - fileName: "ConfigStore2.sqlite", - configuration: "Config2", - localStorageOptions: .RecreateStoreOnModelMismatch - ) - ) - } - catch let error as NSError { - - XCTFail(error.description) - } - - let unsafeTransaction = CoreStore.beginUnsafe() - - let createExpectation = self.expectationWithDescription("Entity creation") - CoreStore.beginAsynchronous { (transaction) -> Void in - - let obj1 = transaction.create(Into(TestEntity1)) - obj1.testEntityID = 1 - obj1.testString = "lololol" - obj1.testNumber = 42 - obj1.testDate = NSDate() - - let count = transaction.queryValue( - From(), - Select(.Count("testNumber")) - ) - XCTAssertTrue(count == 0, "count == 0 (actual: \(count))") // counts only objects in store - - let obj2 = transaction.create(Into()) - obj2.testEntityID = 2 - obj2.testString = "hahaha" - obj2.testNumber = 100 - obj2.testDate = NSDate() - - let obj3 = transaction.create(Into("Config2")) - obj3.testEntityID = 3 - obj3.testString = "hahaha" - obj3.testNumber = 90 - obj3.testDate = NSDate() - - let obj4 = transaction.create(Into(TestEntity2.self, "Config2")) - obj4.testEntityID = 5 - obj4.testString = "hohoho" - obj4.testNumber = 80 - obj4.testDate = NSDate() - - - transaction.beginSynchronous { (transaction) -> Void in - - let obj4 = transaction.create(Into()) - obj4.testEntityID = 4 - obj4.testString = "hehehehe" - obj4.testNumber = 80 - obj4.testDate = NSDate() - - let objs4test = transaction.fetchOne( - From("Config2"), - Where("testEntityID", isEqualTo: 4), - Tweak { (fetchRequest) -> Void in - - fetchRequest.includesPendingChanges = true - } - ) - XCTAssertNotNil(objs4test, "objs4test != nil") - - let objs5test = transaction.fetchOne( - From(TestEntity2), - Where("testEntityID", isEqualTo: 4), - Tweak { (fetchRequest) -> Void in - - fetchRequest.includesPendingChanges = false - } - ) - XCTAssertNil(objs5test, "objs5test == nil") - - // Dont commit1 - } - - transaction.commit { (result) -> Void in - - let objs4test = CoreStore.fetchOne( - From(TestEntity2), - Where("testEntityID", isEqualTo: 4), - Tweak { (fetchRequest) -> Void in - - fetchRequest.includesPendingChanges = false - } - ) - XCTAssertNil(objs4test, "objs4test == nil") - - let objs5test = unsafeTransaction.fetchCount(From(TestEntity2)) - XCTAssertTrue(objs5test == 3, "objs5test == 3") - - XCTAssertTrue(NSThread.isMainThread(), "NSThread.isMainThread()") - switch result { - - case .Success(let hasChanges): - XCTAssertTrue(hasChanges, "hasChanges == true") - createExpectation.fulfill() - - case .Failure(let error): - XCTFail("\(error)") - } - } - } - - let queryExpectation = self.expectationWithDescription("Query creation") - CoreStore.beginAsynchronous { (transaction) -> Void in - - let obj1 = transaction.fetchOne(From(TestEntity1)) - XCTAssertNotNil(obj1, "obj1 != nil") - - var orderBy = OrderBy(.Ascending("testEntityID")) - orderBy += OrderBy(.Descending("testString")) - let objs2 = transaction.fetchAll( - From(TestEntity2), - Where("testNumber", isEqualTo: 100) || Where("%K == %@", "testNumber", 90), - orderBy, - Tweak { (fetchRequest) -> Void in - - fetchRequest.includesPendingChanges = true - } - ) - XCTAssertNotNil(objs2, "objs2 != nil") - XCTAssertTrue(objs2?.count == 2, "objs2?.count == 2") - - transaction.commit { (result) -> Void in - - let counts = CoreStore.queryAttributes( - From(TestEntity2), - Select("testString", .Count("testString", As: "count")), - GroupBy("testString") - ) - - XCTAssertTrue(NSThread.isMainThread(), "NSThread.isMainThread()") - switch result { - - case .Success(let hasChanges): - XCTAssertFalse(hasChanges, "hasChanges == false") - queryExpectation.fulfill() - - case .Failure(let error): - XCTFail("\(error)") - } - } - } - - self.waitForExpectationsWithTimeout(100, handler: nil) - - let max1 = CoreStore.queryValue( - From(TestEntity2), - Select(.Maximum("testNumber")) - ) - XCTAssertTrue(max1 == 100, "max == 100 (actual: \(max1))") - - let max2 = CoreStore.queryValue( - From(TestEntity2), - Select(.Maximum("testNumber")), - Where("%K > %@", "testEntityID", 2) - ) - XCTAssertTrue(max2 == 90, "max == 90 (actual: \(max2))") - - CoreStore.beginSynchronous { (transaction) -> Void in - - let numberOfDeletedObjects1 = transaction.deleteAll(From(TestEntity1)) - XCTAssertTrue(numberOfDeletedObjects1 == 1, "numberOfDeletedObjects1 == 1 (actual: \(numberOfDeletedObjects1))") - - let numberOfDeletedObjects2 = transaction.deleteAll( - From(TestEntity2), - Where("%K > %@", "testEntityID", 2) - ) - XCTAssertTrue(numberOfDeletedObjects2 == 2, "numberOfDeletedObjects2 == 2 (actual: \(numberOfDeletedObjects2))") - - transaction.commitAndWait() - } - - CoreStore.beginSynchronous({ (transaction) -> Void in - - if let obj = CoreStore.fetchOne(From(TestEntity2)) { - - let oldID = obj.testEntityID - obj.testEntityID = 0 - obj.testEntityID = oldID - } - - transaction.commitAndWait() - }) - - let objs1 = CoreStore.fetchAll(From(TestEntity1)) - XCTAssertNotNil(objs1, "objs1 != nil") - XCTAssertTrue(objs1?.count == 0, "objs1?.count == 0") - - let objs2 = CoreStore.fetchAll(From(TestEntity2)) - XCTAssertNotNil(objs2, "objs2 != nil") - XCTAssertTrue(objs2?.count == 1, "objs2?.count == 1") - - let unsafeExpectation = self.expectationWithDescription("Query creation") - - let obj5 = unsafeTransaction.create(Into("Config1")) - obj5.testEntityID = 5 - obj5.testString = "hihihi" - obj5.testNumber = 70 - obj5.testDate = NSDate() - XCTAssert(unsafeTransaction === obj5.unsafeDataTransaction, "unsafeTransaction === obj5.unsafeDataTransaction") - - unsafeTransaction.commit { (result) -> Void in - - XCTAssertTrue(NSThread.isMainThread(), "NSThread.isMainThread()") - switch result { - - case .Success(let hasChanges): - XCTAssertTrue(hasChanges, "hasChanges == true") - - CoreStore.beginSynchronous { (transaction) -> Void in - - let obj5Copy1 = transaction.edit(obj5) - XCTAssertTrue(obj5.objectID == obj5Copy1?.objectID, "obj5.objectID == obj5Copy1?.objectID") - XCTAssertFalse(obj5 == obj5Copy1, "obj5 == obj5Copy1") - XCTAssertNil(obj5Copy1?.unsafeDataTransaction) - - let obj5Copy2 = transaction.edit(Into(TestEntity1), obj5.objectID) - XCTAssertTrue(obj5.objectID == obj5Copy2?.objectID, "obj5.objectID == obj5Copy2?.objectID") - XCTAssertFalse(obj5 == obj5Copy2, "obj5 == obj5Copy2") - } - - let count: Int? = CoreStore.queryValue( - From(TestEntity1), - Select(.Count("testNumber")) - ) - XCTAssertTrue(count == 1, "count == 1 (actual: \(count))") - - let obj6 = unsafeTransaction.create(Into()) - obj6.testEntityID = 6 - obj6.testString = "huehuehue" - obj6.testNumber = 130 - obj6.testDate = NSDate() - XCTAssert(unsafeTransaction === obj6.unsafeDataTransaction, "unsafeTransaction === obj6.unsafeDataTransaction") - - unsafeTransaction.commit { (result) -> Void in - - XCTAssertTrue(NSThread.isMainThread(), "NSThread.isMainThread()") - switch result { - - case .Success(let hasChanges): - XCTAssertTrue(hasChanges, "hasChanges == true") - - let count = CoreStore.queryValue( - From(TestEntity1), - Select(.Count("testNumber")) - ) - XCTAssertTrue(count == 2, "count == 2 (actual: \(count))") - - - CoreStore.beginSynchronous { (transaction) -> Void in - - let obj6 = transaction.edit(obj6) - let obj5 = transaction.edit(obj5) - transaction.delete(obj5, obj6) - - transaction.commitAndWait() - } - - let count2 = CoreStore.queryValue( - From(TestEntity1), - Select(.Count("testNumber")) - ) - XCTAssertTrue(count2 == 0, "count == 0 (actual: \(count2))") - - unsafeExpectation.fulfill() - - case .Failure(let error): - XCTFail("\(error)") - } - } - - case .Failure(let error): - XCTFail("\(error)") - } - } - - self.waitForExpectationsWithTimeout(100, handler: nil) - } - - private func deleteStores() { - - do { - - try NSFileManager.defaultManager().removeItemAtURL(SQLiteStore.defaultRootDirectory) - } - catch _ { } - } -} diff --git a/CoreStoreTests/FetchTests.swift b/CoreStoreTests/FetchTests.swift index 8c2bdc6..4b03fd0 100644 --- a/CoreStoreTests/FetchTests.swift +++ b/CoreStoreTests/FetchTests.swift @@ -24,6 +24,7 @@ // import XCTest +import GCDKit @testable import CoreStore @@ -33,6 +34,195 @@ import CoreStore final class FetchTests: BaseTestDataTestCase { + @objc + dynamic func test_ThatDataStacksAndTransactions_CanFetchOneExisting() { + + let configurations: [String?] = ["Config1"] + self.prepareStack(configurations: configurations) { (stack) in + + self.prepareTestDataForStack(stack, configurations: configurations) + + let from = From(TestEntity1) + let fetchClauses: [FetchClause] = [ + OrderBy(.Ascending("testEntityID")) + ] + let object = stack.fetchOne(from, fetchClauses)! + do { + + let existing = stack.fetchExisting(object) + XCTAssertNotNil(existing) + XCTAssertEqual(existing!.objectID, object.objectID) + XCTAssertEqual(existing!.managedObjectContext, stack.mainContext) + } + do { + + let transaction = stack.beginUnsafe() + + let existing1 = transaction.fetchExisting(object) + XCTAssertNotNil(existing1) + XCTAssertEqual(existing1!.objectID, object.objectID) + XCTAssertEqual(existing1!.managedObjectContext, transaction.context) + + let existing2 = stack.fetchExisting(existing1!) + XCTAssertNotNil(existing2) + XCTAssertEqual(existing2!.objectID, object.objectID) + XCTAssertEqual(existing2!.managedObjectContext, stack.mainContext) + } + do { + + let fetchExpectation = self.expectationWithDescription("fetch") + + var existing1: TestEntity1? + stack.beginSynchronous { (transaction) in + + existing1 = transaction.fetchExisting(object) + XCTAssertNotNil(existing1) + XCTAssertEqual(existing1!.objectID, object.objectID) + XCTAssertEqual(existing1!.managedObjectContext, transaction.context) + + fetchExpectation.fulfill() + } + let existing2 = stack.fetchExisting(existing1!) + XCTAssertNotNil(existing2) + XCTAssertEqual(existing2!.objectID, object.objectID) + XCTAssertEqual(existing2!.managedObjectContext, stack.mainContext) + } + do { + + let fetchExpectation = self.expectationWithDescription("fetch") + stack.beginAsynchronous { (transaction) in + + let existing1 = transaction.fetchExisting(object) + XCTAssertNotNil(existing1) + XCTAssertEqual(existing1!.objectID, object.objectID) + XCTAssertEqual(existing1!.managedObjectContext, transaction.context) + + GCDQueue.Main.async { + + let existing2 = stack.fetchExisting(existing1!) + XCTAssertNotNil(existing2) + XCTAssertEqual(existing2!.objectID, object.objectID) + XCTAssertEqual(existing2!.managedObjectContext, stack.mainContext) + + fetchExpectation.fulfill() + } + } + } + } + self.waitAndCheckExpectations() + } + + @objc + dynamic func test_ThatDataStacksAndTransactions_CanFetchAllExisting() { + + let configurations: [String?] = ["Config1"] + self.prepareStack(configurations: configurations) { (stack) in + + self.prepareTestDataForStack(stack, configurations: configurations) + + let from = From(TestEntity1) + let fetchClauses: [FetchClause] = [ + OrderBy(.Ascending("testEntityID")) + ] + let objects = stack.fetchAll(from, fetchClauses)! + do { + + let existing = stack.fetchExisting(objects) + XCTAssertEqual( + existing.map { $0.objectID }, + objects.map { $0.objectID } + ) + for object in existing { + + XCTAssertEqual(object.managedObjectContext, stack.mainContext) + } + } + do { + + let transaction = stack.beginUnsafe() + + let existing1 = transaction.fetchExisting(objects) + XCTAssertEqual( + existing1.map { $0.objectID }, + objects.map { $0.objectID } + ) + for object in existing1 { + + XCTAssertEqual(object.managedObjectContext, transaction.context) + } + + let existing2 = stack.fetchExisting(existing1) + XCTAssertEqual( + existing2.map { $0.objectID }, + objects.map { $0.objectID } + ) + for object in existing2 { + + XCTAssertEqual(object.managedObjectContext, stack.mainContext) + } + } + do { + + let fetchExpectation = self.expectationWithDescription("fetch") + + var existing1 = [TestEntity1]() + stack.beginSynchronous { (transaction) in + + existing1 = transaction.fetchExisting(objects) + XCTAssertEqual( + existing1.map { $0.objectID }, + objects.map { $0.objectID } + ) + for object in existing1 { + + XCTAssertEqual(object.managedObjectContext, transaction.context) + } + + fetchExpectation.fulfill() + } + let existing2 = stack.fetchExisting(existing1) + XCTAssertEqual( + existing2.map { $0.objectID }, + objects.map { $0.objectID } + ) + for object in existing2 { + + XCTAssertEqual(object.managedObjectContext, stack.mainContext) + } + } + do { + + let fetchExpectation = self.expectationWithDescription("fetch") + stack.beginAsynchronous { (transaction) in + + let existing1 = transaction.fetchExisting(objects) + XCTAssertEqual( + existing1.map { $0.objectID }, + objects.map { $0.objectID } + ) + for object in existing1 { + + XCTAssertEqual(object.managedObjectContext, transaction.context) + } + GCDQueue.Main.async { + + let existing2 = stack.fetchExisting(existing1) + XCTAssertEqual( + existing2.map { $0.objectID }, + objects.map { $0.objectID } + ) + for object in existing2 { + + XCTAssertEqual(object.managedObjectContext, stack.mainContext) + } + fetchExpectation.fulfill() + } + } + } + } + self.waitAndCheckExpectations() + } + @objc dynamic func test_ThatDataStacks_CanFetchOneFromDefaultConfiguration() { diff --git a/CoreStoreTests/ImportTests.swift b/CoreStoreTests/ImportTests.swift new file mode 100644 index 0000000..0667059 --- /dev/null +++ b/CoreStoreTests/ImportTests.swift @@ -0,0 +1,983 @@ +// +// ImportTests.swift +// CoreStore +// +// Copyright © 2016 John Rommel Estropia +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import XCTest + +@testable +import CoreStore + + +// MARK: - ImportTests + +class ImportTests: BaseTestDataTestCase { + + @objc + dynamic func test_ThatImportObject_CanSkipImport() { + + self.prepareStack { (stack) in + + stack.beginSynchronous { (transaction) in + + do { + + let object = try transaction.importObject( + Into(TestEntity1), + source: [ + "testBoolean": NSNumber(bool: true), + "testNumber": NSNumber(integer: 1), + "testDecimal": NSDecimalNumber(string: "1"), + "testString": "nil:TestEntity1:1", + "testData": ("nil:TestEntity1:1" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!, + "testDate": self.dateFormatter.dateFromString("2000-01-01T00:00:00Z")!, + "skip_insert": "" + ] + ) + XCTAssertNil(object) + XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 0) + } + catch { + + XCTFail() + } + } + } + } + + @objc + dynamic func test_ThatImportObject_CanThrowError() { + + self.prepareStack { (stack) in + + stack.beginSynchronous { (transaction) in + + let errorExpectation = self.expectationWithDescription("error") + do { + + let _ = try transaction.importObject( + Into(TestEntity1), + source: [ + "testBoolean": NSNumber(bool: true), + "testNumber": NSNumber(integer: 1), + "testDecimal": NSDecimalNumber(string: "1"), + "testString": "nil:TestEntity1:1", + "testData": ("nil:TestEntity1:1" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!, + "testDate": self.dateFormatter.dateFromString("2000-01-01T00:00:00Z")!, + "throw_on_insert": "" + ] + ) + XCTFail() + } + catch _ as TestInsertError { + + errorExpectation.fulfill() + XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 1) + + let object = transaction.fetchOne(From(TestEntity1)) + XCTAssertNotNil(object) + XCTAssertNil(object?.testEntityID) + XCTAssertNil(object?.testBoolean) + XCTAssertNil(object?.testNumber) + XCTAssertNil(object?.testDecimal) + XCTAssertNil(object?.testString) + XCTAssertNil(object?.testData) + XCTAssertNil(object?.testDate) + } + catch { + + XCTFail() + } + self.checkExpectationsImmediately() + transaction.context.reset() + } + } + } + + @objc + dynamic func test_ThatImportObject_CanImportCorrectly() { + + self.prepareStack { (stack) in + + stack.beginSynchronous { (transaction) in + + do { + + let object = try transaction.importObject( + Into(TestEntity1), + source: [ + "testBoolean": NSNumber(bool: true), + "testNumber": NSNumber(integer: 1), + "testDecimal": NSDecimalNumber(string: "1"), + "testString": "nil:TestEntity1:1", + "testData": ("nil:TestEntity1:1" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!, + "testDate": self.dateFormatter.dateFromString("2000-01-01T00:00:00Z")! + ] + ) + XCTAssertNotNil(object) + XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 1) + XCTAssertNil(object?.testEntityID) + XCTAssertEqual(object?.testBoolean, NSNumber(bool: true)) + XCTAssertEqual(object?.testNumber, NSNumber(integer: 1)) + XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "1")) + XCTAssertEqual(object?.testString, "nil:TestEntity1:1") + XCTAssertEqual(object?.testData, ("nil:TestEntity1:1" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!) + XCTAssertEqual(object?.testDate, self.dateFormatter.dateFromString("2000-01-01T00:00:00Z")!) + + try transaction.importObject( + object!, + source: [ + "testBoolean": NSNumber(bool: false), + "testNumber": NSNumber(integer: 2), + "testDecimal": NSDecimalNumber(string: "2"), + "testString": "nil:TestEntity1:2", + "testData": ("nil:TestEntity1:2" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!, + "testDate": self.dateFormatter.dateFromString("2000-01-02T00:00:00Z")! + ] + ) + XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 1) + XCTAssertNil(object?.testEntityID) + XCTAssertEqual(object?.testBoolean, NSNumber(bool: false)) + XCTAssertEqual(object?.testNumber, NSNumber(integer: 2)) + XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "2")) + XCTAssertEqual(object?.testString, "nil:TestEntity1:2") + XCTAssertEqual(object?.testData, ("nil:TestEntity1:2" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!) + XCTAssertEqual(object?.testDate, self.dateFormatter.dateFromString("2000-01-02T00:00:00Z")!) + } + catch { + + XCTFail() + } + transaction.context.reset() + } + } + } + + @objc + dynamic func test_ThatImportObjects_CanSkipImport() { + + self.prepareStack { (stack) in + + stack.beginSynchronous { (transaction) in + + do { + + let sourceArray: [TestEntity1.ImportSource] = [ + [ + "testBoolean": NSNumber(bool: true), + "testNumber": NSNumber(integer: 1), + "testDecimal": NSDecimalNumber(string: "1"), + "testString": "nil:TestEntity1:1", + "testData": ("nil:TestEntity1:1" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!, + "testDate": self.dateFormatter.dateFromString("2000-01-01T00:00:00Z")!, + "skip_insert": "" + ], + [ + "testBoolean": NSNumber(bool: false), + "testNumber": NSNumber(integer: 2), + "testDecimal": NSDecimalNumber(string: "2"), + "testString": "nil:TestEntity1:2", + "testData": ("nil:TestEntity1:2" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!, + "testDate": self.dateFormatter.dateFromString("2000-01-02T00:00:00Z")! + ] + ] + let objects = try transaction.importObjects( + Into(TestEntity1), + sourceArray: sourceArray + ) + XCTAssertEqual(objects.count, 1) + XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 1) + + let object = objects[0] + let dictionary = sourceArray[1] + XCTAssertNil(object.testEntityID) + XCTAssertEqual(object.testBoolean, dictionary["testBoolean"] as? NSNumber) + XCTAssertEqual(object.testNumber, dictionary["testNumber"] as? NSNumber) + XCTAssertEqual(object.testDecimal, dictionary["testDecimal"] as? NSDecimalNumber) + XCTAssertEqual(object.testString, dictionary["testString"] as? String) + XCTAssertEqual(object.testData, dictionary["testData"] as? NSData) + XCTAssertEqual(object.testDate, dictionary["testDate"] as? NSDate) + } + catch { + + XCTFail() + } + transaction.context.reset() + } + } + } + + @objc + dynamic func test_ThatImportObjects_CanThrowError() { + + self.prepareStack { (stack) in + + stack.beginSynchronous { (transaction) in + + let errorExpectation = self.expectationWithDescription("error") + do { + + let sourceArray: [TestEntity1.ImportSource] = [ + [ + "testBoolean": NSNumber(bool: true), + "testNumber": NSNumber(integer: 1), + "testDecimal": NSDecimalNumber(string: "1"), + "testString": "nil:TestEntity1:1", + "testData": ("nil:TestEntity1:1" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!, + "testDate": self.dateFormatter.dateFromString("2000-01-01T00:00:00Z")!, + "throw_on_insert": "" + ], + [ + "testBoolean": NSNumber(bool: false), + "testNumber": NSNumber(integer: 2), + "testDecimal": NSDecimalNumber(string: "2"), + "testString": "nil:TestEntity1:2", + "testData": ("nil:TestEntity1:2" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!, + "testDate": self.dateFormatter.dateFromString("2000-01-02T00:00:00Z")! + ] + ] + let _ = try transaction.importObjects( + Into(TestEntity1), + sourceArray: sourceArray + ) + XCTFail() + } + catch _ as TestInsertError { + + errorExpectation.fulfill() + XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 1) + + let object = transaction.fetchOne(From(TestEntity1)) + XCTAssertNotNil(object) + XCTAssertNil(object?.testEntityID) + XCTAssertNil(object?.testBoolean) + XCTAssertNil(object?.testNumber) + XCTAssertNil(object?.testDecimal) + XCTAssertNil(object?.testString) + XCTAssertNil(object?.testData) + XCTAssertNil(object?.testDate) + } + catch { + + XCTFail() + } + self.checkExpectationsImmediately() + transaction.context.reset() + } + } + } + + @objc + dynamic func test_ThatImportObjects_CanImportCorrectly() { + + self.prepareStack { (stack) in + + stack.beginSynchronous { (transaction) in + + do { + + let sourceArray: [TestEntity1.ImportSource] = [ + [ + "testBoolean": NSNumber(bool: true), + "testNumber": NSNumber(integer: 1), + "testDecimal": NSDecimalNumber(string: "1"), + "testString": "nil:TestEntity1:1", + "testData": ("nil:TestEntity1:1" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!, + "testDate": self.dateFormatter.dateFromString("2000-01-01T00:00:00Z")! + ], + [ + "testBoolean": NSNumber(bool: false), + "testNumber": NSNumber(integer: 2), + "testDecimal": NSDecimalNumber(string: "2"), + "testString": "nil:TestEntity1:2", + "testData": ("nil:TestEntity1:2" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!, + "testDate": self.dateFormatter.dateFromString("2000-01-02T00:00:00Z")! + ] + ] + let objects = try transaction.importObjects( + Into(TestEntity1), + sourceArray: sourceArray + ) + XCTAssertEqual(objects.count, sourceArray.count) + XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 2) + + for i in 0 ..< sourceArray.count { + + let object = objects[i] + let dictionary = sourceArray[i] + + XCTAssertNil(object.testEntityID) + XCTAssertEqual(object.testBoolean, dictionary["testBoolean"] as? NSNumber) + XCTAssertEqual(object.testNumber, dictionary["testNumber"] as? NSNumber) + XCTAssertEqual(object.testDecimal, dictionary["testDecimal"] as? NSDecimalNumber) + XCTAssertEqual(object.testString, dictionary["testString"] as? String) + XCTAssertEqual(object.testData, dictionary["testData"] as? NSData) + XCTAssertEqual(object.testDate, dictionary["testDate"] as? NSDate) + } + } + catch { + + XCTFail() + } + transaction.context.reset() + } + } + } + + @objc + dynamic func test_ThatImportUniqueObject_CanSkipImport() { + + self.prepareStack { (stack) in + + self.prepareTestDataForStack(stack, configurations: [nil]) + + stack.beginSynchronous { (transaction) in + + do { + + let object = try transaction.importUniqueObject( + Into(TestEntity1), + source: [ + "testEntityID": NSNumber(integer: 106), + "testBoolean": NSNumber(bool: true), + "testNumber": NSNumber(integer: 6), + "testDecimal": NSDecimalNumber(string: "6"), + "testString": "nil:TestEntity1:6", + "testData": ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!, + "testDate": self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")!, + "skip_insert": "" + ] + ) + XCTAssertNil(object) + XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 5) + } + catch { + + XCTFail() + } + do { + + let object = try transaction.importUniqueObject( + Into(TestEntity1), + source: [ + "testEntityID": NSNumber(integer: 105), + "testBoolean": NSNumber(bool: false), + "testNumber": NSNumber(integer: 6), + "testDecimal": NSDecimalNumber(string: "6"), + "testString": "nil:TestEntity1:6", + "testData": ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!, + "testDate": self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")!, + "skip_update": "" + ] + ) + XCTAssertNil(object) + XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 5) + + let existingObjects = transaction.fetchAll(From(TestEntity1), Where("testEntityID", isEqualTo: 105)) + XCTAssertNotNil(existingObjects) + XCTAssertEqual(existingObjects?.count, 1) + + let existingObject = existingObjects?[0] + XCTAssertEqual(existingObject?.testEntityID, NSNumber(integer: 105)) + XCTAssertEqual(existingObject?.testBoolean, NSNumber(bool: true)) + XCTAssertEqual(existingObject?.testNumber, NSNumber(integer: 5)) + XCTAssertEqual(existingObject?.testDecimal, NSDecimalNumber(string: "5")) + XCTAssertEqual(existingObject?.testString, "nil:TestEntity1:5") + XCTAssertEqual(existingObject?.testData, ("nil:TestEntity1:5" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!) + XCTAssertEqual(existingObject?.testDate, self.dateFormatter.dateFromString("2000-01-05T00:00:00Z")!) + } + catch { + + XCTFail() + } + } + } + } + + @objc + dynamic func test_ThatImportUniqueObject_CanThrowError() { + + self.prepareStack { (stack) in + + self.prepareTestDataForStack(stack, configurations: [nil]) + + stack.beginSynchronous { (transaction) in + + do { + + let errorExpectation = self.expectationWithDescription("error") + do { + + let _ = try transaction.importUniqueObject( + Into(TestEntity1), + source: [ + "testEntityID": NSNumber(integer: 106), + "testBoolean": NSNumber(bool: true), + "testNumber": NSNumber(integer: 6), + "testDecimal": NSDecimalNumber(string: "6"), + "testString": "nil:TestEntity1:6", + "testData": ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!, + "testDate": self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")!, + "throw_on_insert": "" + ] + ) + XCTFail() + } + catch _ as TestInsertError { + + errorExpectation.fulfill() + XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 6) + + let object = transaction.fetchOne(From(TestEntity1), Where("testEntityID", isEqualTo: 106)) + XCTAssertNotNil(object) + XCTAssertEqual(object?.testEntityID, NSNumber(integer: 106)) + XCTAssertNil(object?.testBoolean) + XCTAssertNil(object?.testNumber) + XCTAssertNil(object?.testDecimal) + XCTAssertNil(object?.testString) + XCTAssertNil(object?.testData) + XCTAssertNil(object?.testDate) + } + catch { + + XCTFail() + } + self.checkExpectationsImmediately() + } + do { + + let errorExpectation = self.expectationWithDescription("error") + do { + + let _ = try transaction.importUniqueObject( + Into(TestEntity1), + source: [ + "testEntityID": NSNumber(integer: 105), + "testBoolean": NSNumber(bool: false), + "testNumber": NSNumber(integer: 6), + "testDecimal": NSDecimalNumber(string: "6"), + "testString": "nil:TestEntity1:6", + "testData": ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!, + "testDate": self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")!, + "throw_on_update": "" + ] + ) + XCTFail() + } + catch _ as TestUpdateError { + + errorExpectation.fulfill() + XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 6) + + let existingObjects = transaction.fetchAll(From(TestEntity1), Where("testEntityID", isEqualTo: 105)) + XCTAssertNotNil(existingObjects) + XCTAssertEqual(existingObjects?.count, 1) + + let existingObject = existingObjects?[0] + XCTAssertNotNil(existingObject) + XCTAssertEqual(existingObject?.testEntityID, NSNumber(integer: 105)) + XCTAssertEqual(existingObject?.testBoolean, NSNumber(bool: true)) + XCTAssertEqual(existingObject?.testNumber, NSNumber(integer: 5)) + XCTAssertEqual(existingObject?.testDecimal, NSDecimalNumber(string: "5")) + XCTAssertEqual(existingObject?.testString, "nil:TestEntity1:5") + XCTAssertEqual(existingObject?.testData, ("nil:TestEntity1:5" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!) + XCTAssertEqual(existingObject?.testDate, self.dateFormatter.dateFromString("2000-01-05T00:00:00Z")!) + } + catch { + + XCTFail() + } + self.checkExpectationsImmediately() + } + transaction.context.reset() + } + } + } + + @objc + dynamic func test_ThatImportUniqueObject_CanImportCorrectly() { + + self.prepareStack { (stack) in + + self.prepareTestDataForStack(stack, configurations: [nil]) + + stack.beginSynchronous { (transaction) in + + do { + + let object = try transaction.importUniqueObject( + Into(TestEntity1), + source: [ + "testEntityID": NSNumber(integer: 106), + "testBoolean": NSNumber(bool: true), + "testNumber": NSNumber(integer: 6), + "testDecimal": NSDecimalNumber(string: "6"), + "testString": "nil:TestEntity1:6", + "testData": ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!, + "testDate": self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")! + ] + ) + XCTAssertNotNil(object) + XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 6) + + XCTAssertEqual(object?.testEntityID, NSNumber(integer: 106)) + XCTAssertEqual(object?.testBoolean, NSNumber(bool: true)) + XCTAssertEqual(object?.testNumber, NSNumber(integer: 6)) + XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "6")) + XCTAssertEqual(object?.testString, "nil:TestEntity1:6") + XCTAssertEqual(object?.testData, ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!) + XCTAssertEqual(object?.testDate, self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")!) + } + catch { + + XCTFail() + } + do { + + let object = try transaction.importUniqueObject( + Into(TestEntity1), + source: [ + "testEntityID": NSNumber(integer: 106), + "testBoolean": NSNumber(bool: false), + "testNumber": NSNumber(integer: 7), + "testDecimal": NSDecimalNumber(string: "7"), + "testString": "nil:TestEntity1:7", + "testData": ("nil:TestEntity1:7" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!, + "testDate": self.dateFormatter.dateFromString("2000-01-07T00:00:00Z")!, + ] + ) + XCTAssertNotNil(object) + XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 6) + + XCTAssertEqual(object?.testEntityID, NSNumber(integer: 106)) + XCTAssertEqual(object?.testBoolean, NSNumber(bool: false)) + XCTAssertEqual(object?.testNumber, NSNumber(integer: 7)) + XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "7")) + XCTAssertEqual(object?.testString, "nil:TestEntity1:7") + XCTAssertEqual(object?.testData, ("nil:TestEntity1:7" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!) + XCTAssertEqual(object?.testDate, self.dateFormatter.dateFromString("2000-01-07T00:00:00Z")!) + + let existingObjects = transaction.fetchAll(From(TestEntity1), Where("testEntityID", isEqualTo: 106)) + XCTAssertNotNil(existingObjects) + XCTAssertEqual(existingObjects?.count, 1) + + let existingObject = existingObjects?[0] + XCTAssertEqual(existingObject, object) + } + catch { + + XCTFail() + } + transaction.context.reset() + } + } + } + + @objc + dynamic func test_ThatImportUniqueObjects_CanSkipImport() { + + self.prepareStack { (stack) in + + self.prepareTestDataForStack(stack, configurations: [nil]) + + stack.beginSynchronous { (transaction) in + + do { + + let sourceArray: [TestEntity1.ImportSource] = [ + [ + "testEntityID": NSNumber(integer: 106), + "testBoolean": NSNumber(bool: true), + "testNumber": NSNumber(integer: 6), + "testDecimal": NSDecimalNumber(string: "6"), + "testString": "nil:TestEntity1:6", + "testData": ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!, + "testDate": self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")!, + "skip_insert": "" + ], + [ + "testEntityID": NSNumber(integer: 107), + "testBoolean": NSNumber(bool: false), + "testNumber": NSNumber(integer: 7), + "testDecimal": NSDecimalNumber(string: "7"), + "testString": "nil:TestEntity1:7", + "testData": ("nil:TestEntity1:7" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!, + "testDate": self.dateFormatter.dateFromString("2000-01-07T00:00:00Z")! + ] + ] + let objects = try transaction.importUniqueObjects( + Into(TestEntity1), + sourceArray: sourceArray + ) + XCTAssertEqual(objects.count, 1) + XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 6) + + let object = objects[0] + let dictionary = sourceArray[1] + XCTAssertEqual(object.testEntityID, dictionary["testEntityID"] as? NSNumber) + XCTAssertEqual(object.testBoolean, dictionary["testBoolean"] as? NSNumber) + XCTAssertEqual(object.testNumber, dictionary["testNumber"] as? NSNumber) + XCTAssertEqual(object.testDecimal, dictionary["testDecimal"] as? NSDecimalNumber) + XCTAssertEqual(object.testString, dictionary["testString"] as? String) + XCTAssertEqual(object.testData, dictionary["testData"] as? NSData) + XCTAssertEqual(object.testDate, dictionary["testDate"] as? NSDate) + } + catch { + + XCTFail() + } + transaction.context.reset() + } + } + } + + @objc + dynamic func test_ThatImportUniqueObjects_CanThrowError() { + + self.prepareStack { (stack) in + + self.prepareTestDataForStack(stack, configurations: [nil]) + + stack.beginSynchronous { (transaction) in + + let errorExpectation = self.expectationWithDescription("error") + do { + + let sourceArray: [TestEntity1.ImportSource] = [ + [ + "testEntityID": NSNumber(integer: 106), + "testBoolean": NSNumber(bool: true), + "testNumber": NSNumber(integer: 6), + "testDecimal": NSDecimalNumber(string: "6"), + "testString": "nil:TestEntity1:6", + "testData": ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!, + "testDate": self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")!, + "throw_on_id": "" + ], + [ + "testEntityID": NSNumber(integer: 107), + "testBoolean": NSNumber(bool: false), + "testNumber": NSNumber(integer: 7), + "testDecimal": NSDecimalNumber(string: "7"), + "testString": "nil:TestEntity1:7", + "testData": ("nil:TestEntity1:7" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!, + "testDate": self.dateFormatter.dateFromString("2000-01-07T00:00:00Z")! + ] + ] + let _ = try transaction.importUniqueObjects( + Into(TestEntity1), + sourceArray: sourceArray + ) + XCTFail() + } + catch _ as TestIDError { + + errorExpectation.fulfill() + XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 5) + + XCTAssertNil(transaction.fetchOne(From(TestEntity1), Where("testEntityID", isEqualTo: 106))) + XCTAssertNil(transaction.fetchOne(From(TestEntity1), Where("testEntityID", isEqualTo: 107))) + } + catch { + + XCTFail() + } + self.checkExpectationsImmediately() + transaction.context.reset() + } + stack.beginSynchronous { (transaction) in + + let errorExpectation = self.expectationWithDescription("error") + do { + + let sourceArray: [TestEntity1.ImportSource] = [ + [ + "testEntityID": NSNumber(integer: 106), + "testBoolean": NSNumber(bool: true), + "testNumber": NSNumber(integer: 6), + "testDecimal": NSDecimalNumber(string: "6"), + "testString": "nil:TestEntity1:6", + "testData": ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!, + "testDate": self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")!, + "throw_on_insert": "" + ], + [ + "testEntityID": NSNumber(integer: 107), + "testBoolean": NSNumber(bool: false), + "testNumber": NSNumber(integer: 7), + "testDecimal": NSDecimalNumber(string: "7"), + "testString": "nil:TestEntity1:7", + "testData": ("nil:TestEntity1:7" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!, + "testDate": self.dateFormatter.dateFromString("2000-01-07T00:00:00Z")! + ] + ] + let _ = try transaction.importUniqueObjects( + Into(TestEntity1), + sourceArray: sourceArray + ) + XCTFail() + } + catch _ as TestInsertError { + + errorExpectation.fulfill() + XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 7) + + let object = transaction.fetchOne(From(TestEntity1), Where("testEntityID", isEqualTo: 106)) + XCTAssertNotNil(object) + XCTAssertEqual(object?.testEntityID, NSNumber(integer: 106)) + XCTAssertNil(object?.testBoolean) + XCTAssertNil(object?.testNumber) + XCTAssertNil(object?.testDecimal) + XCTAssertNil(object?.testString) + XCTAssertNil(object?.testData) + XCTAssertNil(object?.testDate) + } + catch { + + XCTFail() + } + self.checkExpectationsImmediately() + transaction.context.reset() + } + stack.beginSynchronous { (transaction) in + + let errorExpectation = self.expectationWithDescription("error") + do { + + let sourceArray: [TestEntity1.ImportSource] = [ + [ + "testEntityID": NSNumber(integer: 105), + "testBoolean": NSNumber(bool: false), + "testNumber": NSNumber(integer: 6), + "testDecimal": NSDecimalNumber(string: "6"), + "testString": "nil:TestEntity1:6", + "testData": ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!, + "testDate": self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")!, + "throw_on_update": "" + ] + ] + let _ = try transaction.importUniqueObjects( + Into(TestEntity1), + sourceArray: sourceArray + ) + XCTFail() + } + catch _ as TestUpdateError { + + errorExpectation.fulfill() + XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 5) + + let object = transaction.fetchOne(From(TestEntity1), Where("testEntityID", isEqualTo: 105)) + XCTAssertNotNil(object) + XCTAssertEqual(object?.testEntityID, NSNumber(integer: 105)) + XCTAssertEqual(object?.testBoolean, NSNumber(bool: true)) + XCTAssertEqual(object?.testNumber, NSNumber(integer: 5)) + XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "5")) + XCTAssertEqual(object?.testString, "nil:TestEntity1:5") + XCTAssertEqual(object?.testData, ("nil:TestEntity1:5" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!) + XCTAssertEqual(object?.testDate, self.dateFormatter.dateFromString("2000-01-05T00:00:00Z")!) + + let existingObjects = transaction.fetchAll(From(TestEntity1), Where("testEntityID", isEqualTo: 105)) + XCTAssertNotNil(existingObjects) + XCTAssertEqual(existingObjects?.count, 1) + + let existingObject = existingObjects?[0] + XCTAssertEqual(existingObject, object) + } + catch { + + XCTFail() + } + self.checkExpectationsImmediately() + transaction.context.reset() + } + } + } + + @objc + dynamic func test_ThatImportUniqueObjects_CanImportCorrectly() { + + self.prepareStack { (stack) in + + self.prepareTestDataForStack(stack, configurations: [nil]) + + stack.beginSynchronous { (transaction) in + + do { + + let sourceArray: [TestEntity1.ImportSource] = [ + [ + "testEntityID": NSNumber(integer: 105), + "testBoolean": NSNumber(bool: false), + "testNumber": NSNumber(integer: 15), + "testDecimal": NSDecimalNumber(string: "15"), + "testString": "nil:TestEntity1:15", + "testData": ("nil:TestEntity1:15" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!, + "testDate": self.dateFormatter.dateFromString("2000-01-15T00:00:00Z")! + ], + [ + "testEntityID": NSNumber(integer: 106), + "testBoolean": NSNumber(bool: false), + "testNumber": NSNumber(integer: 6), + "testDecimal": NSDecimalNumber(string: "6"), + "testString": "nil:TestEntity1:6", + "testData": ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!, + "testDate": self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")! + ] + ] + let objects = try transaction.importUniqueObjects( + Into(TestEntity1), + sourceArray: sourceArray + ) + XCTAssertEqual(objects.count, sourceArray.count) + XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 6) + for i in 0 ..< sourceArray.count { + + let object = objects[i] + let dictionary = sourceArray[i] + + XCTAssertEqual(object.testEntityID, dictionary["testEntityID"] as? NSNumber) + XCTAssertEqual(object.testBoolean, dictionary["testBoolean"] as? NSNumber) + XCTAssertEqual(object.testNumber, dictionary["testNumber"] as? NSNumber) + XCTAssertEqual(object.testDecimal, dictionary["testDecimal"] as? NSDecimalNumber) + XCTAssertEqual(object.testString, dictionary["testString"] as? String) + XCTAssertEqual(object.testData, dictionary["testData"] as? NSData) + XCTAssertEqual(object.testDate, dictionary["testDate"] as? NSDate) + } + let existingObjects = transaction.fetchAll(From(TestEntity1), Where("testEntityID", isEqualTo: 105)) + XCTAssertNotNil(existingObjects) + XCTAssertEqual(existingObjects?.count, 1) + + let existingObject = existingObjects?[0] + XCTAssertEqual(existingObject, objects[0]) + } + catch { + + XCTFail() + } + transaction.context.reset() + } + } + } +} + + +// MARK: - TestInsertError + +private struct TestInsertError: ErrorType {} + + +// MARK: - TestUpdateError + +private struct TestUpdateError: ErrorType {} + + +// MARK: - TestIDError + +private struct TestIDError: ErrorType {} + + +// MARK: - TestEntity1 + +extension TestEntity1: ImportableUniqueObject { + + // MARK: ImportableObject + + typealias ImportSource = [String: AnyObject] + + static func shouldInsertFromImportSource(source: [String: AnyObject], inTransaction transaction: BaseDataTransaction) -> Bool { + + return source["skip_insert"] == nil + } + + func didInsertFromImportSource(source: [String: AnyObject], inTransaction transaction: BaseDataTransaction) throws { + + if let _ = source["throw_on_insert"] { + + throw TestInsertError() + } + self.testBoolean = source["testBoolean"] as? NSNumber + self.testNumber = source["testNumber"] as? NSNumber + self.testDecimal = source["testDecimal"] as? NSDecimalNumber + self.testString = source["testString"] as? String + self.testData = source["testData"] as? NSData + self.testDate = source["testDate"] as? NSDate + self.testNil = nil + } + + + // MARK: ImportableUniqueObject + + typealias UniqueIDType = NSNumber + + static var uniqueIDKeyPath: String { + + return "testEntityID" + } + + var uniqueIDValue: NSNumber { + + get { + + guard let ID = self.testEntityID else { + + XCTFail() + return 0 + } + return ID + } + set { + + self.testEntityID = newValue + } + } + + static func shouldUpdateFromImportSource(source: [String: AnyObject], inTransaction transaction: BaseDataTransaction) -> Bool { + + return source["skip_update"] == nil + } + + static func uniqueIDFromImportSource(source: [String: AnyObject], inTransaction transaction: BaseDataTransaction) throws -> NSNumber? { + + if let _ = source["throw_on_id"] { + + throw TestIDError() + } + return source["testEntityID"] as? NSNumber + } + + func updateFromImportSource(source: [String: AnyObject], inTransaction transaction: BaseDataTransaction) throws { + + if let _ = source["throw_on_update"] { + + throw TestUpdateError() + } + self.testBoolean = source["testBoolean"] as? NSNumber + self.testNumber = source["testNumber"] as? NSNumber + self.testDecimal = source["testDecimal"] as? NSDecimalNumber + self.testString = source["testString"] as? String + self.testData = source["testData"] as? NSData + self.testDate = source["testDate"] as? NSDate + self.testNil = nil + } +} diff --git a/CoreStoreTests/Model.xcdatamodeld/Model.xcdatamodel/contents b/CoreStoreTests/Model.xcdatamodeld/Model.xcdatamodel/contents index 900d3c5..d88951d 100644 --- a/CoreStoreTests/Model.xcdatamodeld/Model.xcdatamodel/contents +++ b/CoreStoreTests/Model.xcdatamodeld/Model.xcdatamodel/contents @@ -4,20 +4,20 @@ - - + + - + - - + + - + diff --git a/CoreStoreTests/ObjectObserverTests.swift b/CoreStoreTests/ObjectObserverTests.swift new file mode 100644 index 0000000..d7b95c4 --- /dev/null +++ b/CoreStoreTests/ObjectObserverTests.swift @@ -0,0 +1,37 @@ +// +// ObjectObserverTests.swift +// CoreStore +// +// Copyright © 2016 John Rommel Estropia +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import XCTest + +@testable +import CoreStore + + +// MARK: - ObjectObserverTests + +class ObjectObserverTests: BaseTestDataTestCase { + + +} diff --git a/CoreStoreTests/TransactionTests.swift b/CoreStoreTests/TransactionTests.swift index b15440f..ff28ad3 100644 --- a/CoreStoreTests/TransactionTests.swift +++ b/CoreStoreTests/TransactionTests.swift @@ -24,11 +24,10 @@ // import XCTest +import GCDKit @testable import CoreStore -@testable -import GCDKit //MARK: - TransactionTests @@ -62,7 +61,7 @@ final class TransactionTests: BaseTestCase { XCTFail() } } - self.waitForExpectationsWithTimeout(0, handler: nil) + self.checkExpectationsImmediately() XCTAssertEqual(stack.fetchCount(From(TestEntity1)), 1) @@ -97,7 +96,7 @@ final class TransactionTests: BaseTestCase { XCTFail() } } - self.waitForExpectationsWithTimeout(0, handler: nil) + self.checkExpectationsImmediately() XCTAssertEqual(stack.fetchCount(From(TestEntity1)), 1) @@ -126,7 +125,7 @@ final class TransactionTests: BaseTestCase { XCTFail() } } - self.waitForExpectationsWithTimeout(0, handler: nil) + self.checkExpectationsImmediately() XCTAssertEqual(stack.fetchCount(From(TestEntity1)), 0) @@ -136,6 +135,107 @@ final class TransactionTests: BaseTestCase { } } + @objc + dynamic func test_ThatSynchronousTransactions_CanPerformCRUDsInCorrectConfiguration() { + + self.prepareStack(configurations: [nil, "Config1"]) { (stack) in + + let testDate = NSDate() + do { + + let createExpectation = self.expectationWithDescription("create") + stack.beginSynchronous { (transaction) in + + let object = transaction.create(Into("Config1")) + object.testEntityID = NSNumber(integer: 1) + object.testString = "string1" + object.testNumber = 100 + object.testDate = testDate + + switch transaction.commitAndWait() { + + case .Success(let hasChanges): + XCTAssertTrue(hasChanges) + createExpectation.fulfill() + + default: + XCTFail() + } + } + self.checkExpectationsImmediately() + + XCTAssertEqual(stack.fetchCount(From("Config1")), 1) + XCTAssertEqual(stack.fetchCount(From(nil)), 0) + + let object = stack.fetchOne(From("Config1")) + XCTAssertNotNil(object) + XCTAssertEqual(object?.testEntityID, NSNumber(integer: 1)) + XCTAssertEqual(object?.testString, "string1") + XCTAssertEqual(object?.testNumber, 100) + XCTAssertEqual(object?.testDate, testDate) + } + do { + + let updateExpectation = self.expectationWithDescription("update") + stack.beginSynchronous { (transaction) in + + guard let object = transaction.fetchOne(From("Config1")) else { + + XCTFail() + return + } + object.testString = "string1_edit" + object.testNumber = 200 + object.testDate = NSDate.distantFuture() + + switch transaction.commitAndWait() { + + case .Success(let hasChanges): + XCTAssertTrue(hasChanges) + updateExpectation.fulfill() + + default: + XCTFail() + } + } + self.checkExpectationsImmediately() + + XCTAssertEqual(stack.fetchCount(From("Config1")), 1) + XCTAssertEqual(stack.fetchCount(From(nil)), 0) + + let object = stack.fetchOne(From("Config1")) + XCTAssertNotNil(object) + XCTAssertEqual(object?.testEntityID, NSNumber(integer: 1)) + XCTAssertEqual(object?.testString, "string1_edit") + XCTAssertEqual(object?.testNumber, 200) + XCTAssertEqual(object?.testDate, NSDate.distantFuture()) + } + do { + + let deleteExpectation = self.expectationWithDescription("delete") + stack.beginSynchronous { (transaction) in + + let object = transaction.fetchOne(From("Config1")) + transaction.delete(object) + + switch transaction.commitAndWait() { + + case .Success(let hasChanges): + XCTAssertTrue(hasChanges) + deleteExpectation.fulfill() + + default: + XCTFail() + } + } + self.checkExpectationsImmediately() + + XCTAssertEqual(stack.fetchCount(From("Config1")), 0) + XCTAssertEqual(stack.fetchCount(From(nil)), 0) + } + } + } + @objc dynamic func test_ThatSynchronousTransactions_CanDiscardUncommittedChanges() { @@ -156,7 +256,7 @@ final class TransactionTests: BaseTestCase { createDiscardExpectation.fulfill() self.expectLogger(loggerExpectations) } - self.waitForExpectationsWithTimeout(0, handler: nil) + self.checkExpectationsImmediately() XCTAssertEqual(stack.fetchCount(From(TestEntity1)), 0) @@ -184,7 +284,7 @@ final class TransactionTests: BaseTestCase { XCTFail() } } - self.waitForExpectationsWithTimeout(0, handler: nil) + self.checkExpectationsImmediately() } do { @@ -204,7 +304,7 @@ final class TransactionTests: BaseTestCase { updateDiscardExpectation.fulfill() self.expectLogger(loggerExpectations) } - self.waitForExpectationsWithTimeout(0, handler: nil) + self.checkExpectationsImmediately() XCTAssertEqual(stack.fetchCount(From(TestEntity1)), 1) @@ -231,7 +331,7 @@ final class TransactionTests: BaseTestCase { deleteDiscardExpectation.fulfill() self.expectLogger(loggerExpectations) } - self.waitForExpectationsWithTimeout(0, handler: nil) + self.checkExpectationsImmediately() XCTAssertEqual(stack.fetchCount(From(TestEntity1)), 1) @@ -350,7 +450,116 @@ final class TransactionTests: BaseTestCase { } } } - self.waitForExpectationsWithTimeout(NSTimeInterval(Int8.max), handler: nil) + self.waitAndCheckExpectations() + } + + @objc + dynamic func test_ThatAsynchronousTransactions_CanPerformCRUDsInCorrectConfiguration() { + + self.prepareStack(configurations: [nil, "Config1"]) { (stack) in + + let testDate = NSDate() + do { + + let createExpectation = self.expectationWithDescription("create") + stack.beginAsynchronous { (transaction) in + + let object = transaction.create(Into("Config1")) + object.testEntityID = NSNumber(integer: 1) + object.testString = "string1" + object.testNumber = 100 + object.testDate = testDate + + transaction.commit { (result) in + + switch result { + + case .Success(let hasChanges): + XCTAssertTrue(hasChanges) + + XCTAssertEqual(stack.fetchCount(From("Config1")), 1) + XCTAssertEqual(stack.fetchCount(From(nil)), 0) + + let object = stack.fetchOne(From("Config1")) + XCTAssertNotNil(object) + XCTAssertEqual(object?.testEntityID, NSNumber(integer: 1)) + XCTAssertEqual(object?.testString, "string1") + XCTAssertEqual(object?.testNumber, 100) + XCTAssertEqual(object?.testDate, testDate) + createExpectation.fulfill() + + default: + XCTFail() + } + } + } + } + do { + + let updateExpectation = self.expectationWithDescription("update") + stack.beginAsynchronous { (transaction) in + + guard let object = transaction.fetchOne(From("Config1")) else { + + XCTFail() + return + } + object.testString = "string1_edit" + object.testNumber = 200 + object.testDate = NSDate.distantFuture() + + transaction.commit { (result) in + + switch result { + + case .Success(let hasChanges): + XCTAssertTrue(hasChanges) + + XCTAssertEqual(stack.fetchCount(From("Config1")), 1) + XCTAssertEqual(stack.fetchCount(From(nil)), 0) + + let object = stack.fetchOne(From("Config1")) + XCTAssertNotNil(object) + XCTAssertEqual(object?.testEntityID, NSNumber(integer: 1)) + XCTAssertEqual(object?.testString, "string1_edit") + XCTAssertEqual(object?.testNumber, 200) + XCTAssertEqual(object?.testDate, NSDate.distantFuture()) + updateExpectation.fulfill() + + default: + XCTFail() + } + } + } + } + do { + + let deleteExpectation = self.expectationWithDescription("delete") + stack.beginAsynchronous { (transaction) in + + let object = transaction.fetchOne(From("Config1")) + transaction.delete(object) + + transaction.commit { (result) in + + switch result { + + case .Success(let hasChanges): + XCTAssertTrue(hasChanges) + + XCTAssertEqual(stack.fetchCount(From("Config1")), 0) + XCTAssertEqual(stack.fetchCount(From(nil)), 0) + + deleteExpectation.fulfill() + + default: + XCTFail() + } + } + } + } + } + self.waitAndCheckExpectations() } @objc @@ -458,7 +667,7 @@ final class TransactionTests: BaseTestCase { } } } - self.waitForExpectationsWithTimeout(NSTimeInterval(Int8.max), handler: nil) + self.waitAndCheckExpectations() } @objc @@ -542,6 +751,89 @@ final class TransactionTests: BaseTestCase { } } + @objc + dynamic func test_ThatUnsafeTransactions_CanPerformCRUDsInCorrectConfiguration() { + + self.prepareStack(configurations: [nil, "Config1"]) { (stack) in + + let transaction = stack.beginUnsafe() + + let testDate = NSDate() + do { + + let object = transaction.create(Into("Config1")) + object.testEntityID = NSNumber(integer: 1) + object.testString = "string1" + object.testNumber = 100 + object.testDate = testDate + + switch transaction.commitAndWait() { + + case .Success(let hasChanges): + XCTAssertTrue(hasChanges) + XCTAssertEqual(stack.fetchCount(From("Config1")), 1) + XCTAssertEqual(stack.fetchCount(From(nil)), 0) + + let object = stack.fetchOne(From("Config1")) + XCTAssertNotNil(object) + XCTAssertEqual(object?.testEntityID, NSNumber(integer: 1)) + XCTAssertEqual(object?.testString, "string1") + XCTAssertEqual(object?.testNumber, 100) + XCTAssertEqual(object?.testDate, testDate) + + default: + XCTFail() + } + } + do { + + guard let object = transaction.fetchOne(From("Config1")) else { + + XCTFail() + return + } + object.testString = "string1_edit" + object.testNumber = 200 + object.testDate = NSDate.distantFuture() + + switch transaction.commitAndWait() { + + case .Success(let hasChanges): + XCTAssertTrue(hasChanges) + XCTAssertEqual(stack.fetchCount(From("Config1")), 1) + XCTAssertEqual(stack.fetchCount(From(nil)), 0) + + let object = stack.fetchOne(From("Config1")) + XCTAssertNotNil(object) + XCTAssertEqual(object?.testEntityID, NSNumber(integer: 1)) + XCTAssertEqual(object?.testString, "string1_edit") + XCTAssertEqual(object?.testNumber, 200) + XCTAssertEqual(object?.testDate, NSDate.distantFuture()) + + default: + XCTFail() + } + } + do { + + let object = transaction.fetchOne(From("Config1")) + transaction.delete(object) + + switch transaction.commitAndWait() { + + case .Success(let hasChanges): + XCTAssertTrue(hasChanges) + + XCTAssertEqual(stack.fetchCount(From("Config1")), 0) + XCTAssertEqual(stack.fetchCount(From(nil)), 0) + + default: + XCTFail() + } + } + } + } + @objc dynamic func test_ThatUnsafeTransactions_CanRollbackChanges() { diff --git a/Sources/Importing/BaseDataTransaction+Importing.swift b/Sources/Importing/BaseDataTransaction+Importing.swift index 1c6a431..19df388 100644 --- a/Sources/Importing/BaseDataTransaction+Importing.swift +++ b/Sources/Importing/BaseDataTransaction+Importing.swift @@ -208,7 +208,7 @@ public extension BaseDataTransaction { mapping = try cs_autoreleasepool { try preProcess(mapping: mapping) } var objects = Dictionary() - for object in self.fetchAll(From(T), Where(T.uniqueIDKeyPath, isMemberOf: mapping.keys)) ?? [] { + for object in self.fetchAll(From(T), Where(T.uniqueIDKeyPath, isMemberOf: sortedIDs)) ?? [] { try cs_autoreleasepool { diff --git a/Sources/Internal/CoreStoreFetchedResultsController.swift b/Sources/Internal/CoreStoreFetchedResultsController.swift index 5bf7776..01a8500 100644 --- a/Sources/Internal/CoreStoreFetchedResultsController.swift +++ b/Sources/Internal/CoreStoreFetchedResultsController.swift @@ -67,7 +67,7 @@ internal final class CoreStoreFetchedResultsController: NSFetchedResultsControll guard let from = (fetchRequest.entity.flatMap { $0.managedObjectClassName }).flatMap(NSClassFromString).flatMap(From.init) else { - fatalError("Attempted to create an \(cs_typeName(NSFetchedResultsController)) without a \(cs_typeName(From)) clause or an \(cs_typeName(NSEntityDescription)).") + CoreStore.abort("Attempted to create an \(cs_typeName(NSFetchedResultsController)) without a \(cs_typeName(From)) clause or an \(cs_typeName(NSEntityDescription)).") } self.reapplyAffectedStores = { fetchRequest, context in diff --git a/Sources/Internal/NSManagedObjectModel+Setup.swift b/Sources/Internal/NSManagedObjectModel+Setup.swift index 6ceb1da..46b9147 100644 --- a/Sources/Internal/NSManagedObjectModel+Setup.swift +++ b/Sources/Internal/NSManagedObjectModel+Setup.swift @@ -38,7 +38,7 @@ internal extension NSManagedObjectModel { guard let modelFilePath = bundle.pathForResource(modelName, ofType: "momd") else { - fatalError("Could not find \"\(modelName).momd\" from the bundle. \(bundle)") + CoreStore.abort("Could not find \"\(modelName).momd\" from the bundle. \(bundle)") } let modelFileURL = NSURL(fileURLWithPath: modelFilePath) @@ -47,7 +47,7 @@ internal extension NSManagedObjectModel { guard let versionInfo = NSDictionary(contentsOfURL: versionInfoPlistURL), let versionHashes = versionInfo["NSManagedObjectModel_VersionHashes"] as? [String: AnyObject] else { - fatalError("Could not load \(cs_typeName(NSManagedObjectModel)) metadata from path \"\(versionInfoPlistURL)\".") + CoreStore.abort("Could not load \(cs_typeName(NSManagedObjectModel)) metadata from path \"\(versionInfoPlistURL)\".") } let modelVersions = Set(versionHashes.keys) @@ -77,7 +77,7 @@ internal extension NSManagedObjectModel { } else { - fatalError("No model files were found in URL \"\(modelFileURL)\".") + CoreStore.abort("No model files were found in URL \"\(modelFileURL)\".") } var modelVersionFileURL: NSURL? @@ -106,7 +106,7 @@ internal extension NSManagedObjectModel { return rootModel } - fatalError("Could not create an \(cs_typeName(NSManagedObjectModel)) from the model at URL \"\(modelFileURL)\".") + CoreStore.abort("Could not create an \(cs_typeName(NSManagedObjectModel)) from the model at URL \"\(modelFileURL)\".") } @nonobjc diff --git a/Sources/Logging/CoreStore+Logging.swift b/Sources/Logging/CoreStore+Logging.swift index 78ab487..35d6097 100644 --- a/Sources/Logging/CoreStore+Logging.swift +++ b/Sources/Logging/CoreStore+Logging.swift @@ -70,4 +70,16 @@ public extension CoreStore { functionName: functionName ) } + + @noreturn + internal static func abort(message: String, fileName: StaticString = #file, lineNumber: Int = #line, functionName: StaticString = #function) { + + self.logger.abort( + message, + fileName: fileName, + lineNumber: lineNumber, + functionName: functionName + ) + Swift.fatalError(message, file: fileName, line: UInt(lineNumber)) + } } diff --git a/Sources/Logging/CoreStoreLogger.swift b/Sources/Logging/CoreStoreLogger.swift index ced31b4..b6f4ea5 100644 --- a/Sources/Logging/CoreStoreLogger.swift +++ b/Sources/Logging/CoreStoreLogger.swift @@ -80,6 +80,17 @@ public protocol CoreStoreLogger { */ func assert(@autoclosure condition: () -> Bool, @autoclosure message: () -> String, fileName: StaticString, lineNumber: Int, functionName: StaticString) + /** + Handles fatal errors made throughout the `CoreStore` framework. The app wil terminate after this method is called. + - Important: Implementers may guarantee that the function doesn't return, either by calling another `@noreturn` function such as `fatalError()` or `abort()`, or by raising an exception. If the implementation does not terminate the app, CoreStore will call an internal `fatalError()` to do so. + + - parameter message: the fatal error message + - parameter fileName: the source file name + - parameter lineNumber: the source line number + - parameter functionName: the source function name + */ + func abort(message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) + // MARK: Deprecated @@ -100,4 +111,9 @@ extension CoreStoreLogger { self.log(error: error.bridgeToSwift, message: message, fileName: fileName, lineNumber: lineNumber, functionName: functionName) } + + public func abort(message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) { + + Swift.fatalError(message, file: fileName, line: UInt(lineNumber)) + } } diff --git a/Sources/Logging/DefaultLogger.swift b/Sources/Logging/DefaultLogger.swift index afac6ee..bf79b40 100644 --- a/Sources/Logging/DefaultLogger.swift +++ b/Sources/Logging/DefaultLogger.swift @@ -111,7 +111,22 @@ public final class DefaultLogger: CoreStoreLogger { return } Swift.print("❗ [CoreStore: Assertion Failure] \((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message())\n") - Swift.fatalError() + Swift.fatalError(file: fileName, line: UInt(lineNumber)) #endif } + + /** + Handles fatal errors made throughout the `CoreStore` framework. + - Important: This method should be marked `@noreturn` and implementers should guarantee that the function doesn't, either by calling another `@noreturn` function such as `fatalError()` or `abort()`, or by raising an exception. + + - parameter message: the fatal error message + - parameter fileName: the source file name + - parameter lineNumber: the source line number + - parameter functionName: the source function name + */ + public func abort(message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) { + + Swift.print("❗ [CoreStore: Fatal Error] \((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n") + Swift.fatalError(file: fileName, line: UInt(lineNumber)) + } } diff --git a/Sources/Transactions/BaseDataTransaction.swift b/Sources/Transactions/BaseDataTransaction.swift index 446da6f..103642f 100644 --- a/Sources/Transactions/BaseDataTransaction.swift +++ b/Sources/Transactions/BaseDataTransaction.swift @@ -76,10 +76,10 @@ public /*abstract*/ class BaseDataTransaction { return object case (nil, true): - fatalError("Attempted to create an entity of type \(cs_typeName(entityClass)) with ambiguous destination persistent store, but the configuration name was not specified.") + CoreStore.abort("Attempted to create an entity of type \(cs_typeName(entityClass)) with ambiguous destination persistent store, but the configuration name was not specified.") default: - fatalError("Attempted to create an entity of type \(cs_typeName(entityClass)), but a destination persistent store containing the entity type could not be found.") + CoreStore.abort("Attempted to create an entity of type \(cs_typeName(entityClass)), but a destination persistent store containing the entity type could not be found.") } } else { @@ -97,16 +97,16 @@ public /*abstract*/ class BaseDataTransaction { return object case (nil, true): - fatalError("Attempted to create an entity of type \(cs_typeName(entityClass)) with ambiguous destination persistent store, but the configuration name was not specified.") + CoreStore.abort("Attempted to create an entity of type \(cs_typeName(entityClass)) with ambiguous destination persistent store, but the configuration name was not specified.") default: if let configuration = into.configuration { - fatalError("Attempted to create an entity of type \(cs_typeName(entityClass)) into the configuration \"\(configuration)\", which it doesn't belong to.") + CoreStore.abort("Attempted to create an entity of type \(cs_typeName(entityClass)) into the configuration \"\(configuration)\", which it doesn't belong to.") } else { - fatalError("Attempted to create an entity of type \(cs_typeName(entityClass)) into the default configuration, which it doesn't belong to.") + CoreStore.abort("Attempted to create an entity of type \(cs_typeName(entityClass)) into the default configuration, which it doesn't belong to.") } } }