diff --git a/Sources/CoreDataRepository/CoreDataRepository+CRUD.swift b/Sources/CoreDataRepository/CoreDataRepository+CRUD.swift index 3629cfa..10d9d36 100644 --- a/Sources/CoreDataRepository/CoreDataRepository+CRUD.swift +++ b/Sources/CoreDataRepository/CoreDataRepository+CRUD.swift @@ -26,11 +26,15 @@ extension CoreDataRepository { _ item: Model, transactionAuthor: String? = nil ) async -> Result { - await context.performInScratchPad(schedule: .enqueued) { scratchPad in + await context.performInScratchPad(schedule: .enqueued) { [context] scratchPad in scratchPad.transactionAuthor = transactionAuthor let object = Model.RepoManaged(context: scratchPad) object.create(from: item) try scratchPad.save() + try context.performAndWait { + try context.save() + } + try scratchPad.obtainPermanentIDs(for: [object]) return object.asUnmanaged } } @@ -68,12 +72,15 @@ extension CoreDataRepository { with item: Model, transactionAuthor _: String? = nil ) async -> Result { - await context.performInScratchPad(schedule: .enqueued) { scratchPad in + await context.performInScratchPad(schedule: .enqueued) { [context] scratchPad in let id = try scratchPad.tryObjectId(from: url) let object = try scratchPad.notDeletedObject(for: id) let repoManaged: Model.RepoManaged = try object.asRepoManaged() repoManaged.update(from: item) try scratchPad.save() + try context.performAndWait { + try context.save() + } return repoManaged.asUnmanaged } } @@ -92,12 +99,15 @@ extension CoreDataRepository { _ url: URL, transactionAuthor _: String? = nil ) async -> Result { - await context.performInScratchPad(schedule: .enqueued) { scratchPad in + await context.performInScratchPad(schedule: .enqueued) { [context] scratchPad in let id = try scratchPad.tryObjectId(from: url) let object = try scratchPad.notDeletedObject(for: id) object.prepareForDeletion() scratchPad.delete(object) try scratchPad.save() + try context.performAndWait { + try context.save() + } return () } } diff --git a/Tests/CoreDataRepositoryTests/BatchRepositoryTests.swift b/Tests/CoreDataRepositoryTests/BatchRepositoryTests.swift index 8cd344d..062fa47 100644 --- a/Tests/CoreDataRepositoryTests/BatchRepositoryTests.swift +++ b/Tests/CoreDataRepositoryTests/BatchRepositoryTests.swift @@ -116,6 +116,10 @@ final class BatchRepositoryTests: CoreDataXCTestCase { XCTAssertEqual(result.success.count, newMovies.count) XCTAssertEqual(result.failed.count, 0) + for movie in result.success { + try await verify(movie) + } + try await repositoryContext().perform { let data = try self.repositoryContext().fetch(fetchRequest) XCTAssertEqual( diff --git a/Tests/CoreDataRepositoryTests/CRUDRepositoryTests.swift b/Tests/CoreDataRepositoryTests/CRUDRepositoryTests.swift index eb0db38..1917141 100644 --- a/Tests/CoreDataRepositoryTests/CRUDRepositoryTests.swift +++ b/Tests/CoreDataRepositoryTests/CRUDRepositoryTests.swift @@ -16,15 +16,16 @@ final class CRUDRepositoryTests: CoreDataXCTestCase { func testCreateSuccess() async throws { let movie = Movie(id: UUID(), title: "Create Success", releaseDate: Date(), boxOffice: 100) let result: Result = try await repository().create(movie) - guard case var .success(resultMovie) = result else { + guard case let .success(resultMovie) = result else { XCTFail("Not expecting a failed result") return } + var tempResultMovie = resultMovie + XCTAssertNotNil(tempResultMovie.url) + tempResultMovie.url = nil + XCTAssertNoDifference(tempResultMovie, movie) - XCTAssertNotNil(resultMovie.url) - resultMovie.url = nil - let diff = CustomDump.diff(resultMovie, movie) - XCTAssertNil(diff) + try await verify(resultMovie) } func testReadSuccess() async throws { @@ -39,15 +40,18 @@ final class CRUDRepositoryTests: CoreDataXCTestCase { let result: Result = try await repository() .read(try XCTUnwrap(createdMovie.url)) - guard case var .success(resultMovie) = result else { + guard case let .success(resultMovie) = result else { XCTFail("Not expecting a failed result") return } - XCTAssertNotNil(resultMovie.url) - resultMovie.url = nil - let diff = CustomDump.diff(resultMovie, movie) - XCTAssertNil(diff) + var tempResultMovie = resultMovie + + XCTAssertNotNil(tempResultMovie.url) + tempResultMovie.url = nil + XCTAssertNoDifference(tempResultMovie, movie) + + try await verify(resultMovie) } func testReadFailure() async throws { @@ -91,15 +95,18 @@ final class CRUDRepositoryTests: CoreDataXCTestCase { let result: Result = try await repository() .update(try XCTUnwrap(createdMovie.url), with: movie) - guard case var .success(resultMovie) = result else { + guard case let .success(resultMovie) = result else { XCTFail("Not expecting a failed result") return } - XCTAssertNotNil(resultMovie.url) - resultMovie.url = nil - let diff = CustomDump.diff(resultMovie, movie) - XCTAssertNil(diff) + var tempResultMovie = resultMovie + + XCTAssertNotNil(tempResultMovie.url) + tempResultMovie.url = nil + XCTAssertNoDifference(tempResultMovie, movie) + + try await verify(resultMovie) } func testUpdateFailure() async throws { diff --git a/Tests/CoreDataRepositoryTests/CoreDataXCTestCase.swift b/Tests/CoreDataRepositoryTests/CoreDataXCTestCase.swift index 6320d06..103fcf7 100644 --- a/Tests/CoreDataRepositoryTests/CoreDataXCTestCase.swift +++ b/Tests/CoreDataRepositoryTests/CoreDataXCTestCase.swift @@ -9,6 +9,7 @@ import Combine import CoreData import CoreDataRepository +import CustomDump import XCTest class CoreDataXCTestCase: XCTestCase { @@ -49,4 +50,40 @@ class CoreDataXCTestCase: XCTestCase { _repository = nil cancellables.forEach { $0.cancel() } } + + func verify(_ item: T) async throws where T: UnmanagedModel { + guard let url = item.managedRepoUrl else { + XCTFail("Failed to verify item in store because it has no URL") + return + } + + let context = try repositoryContext() + let coordinator = try container().persistentStoreCoordinator + context.performAndWait { + guard let objectID = coordinator.managedObjectID(forURIRepresentation: url) else { + XCTFail("Failed to verify item in store because no NSManagedObjectID found in viewContext from URL.") + return + } + var _object: NSManagedObject? + do { + _object = try context.existingObject(with: objectID) + } catch { + XCTFail( + "Failed to verify item in store because it was not found by its NSManagedObjectID. Error: \(error.localizedDescription)" + ) + return + } + + guard let object = _object else { + XCTFail("Failed to verify item in store because it was not found by its NSManagedObjectID") + return + } + + guard let managedItem = object as? T.RepoManaged else { + XCTFail("Failed to verify item in store because it failed to cast to RepoManaged type.") + return + } + XCTAssertNoDifference(item, managedItem.asUnmanaged) + } + } }