Skip to content

Commit 5298837

Browse files
authored
Merge pull request #9 from roanutil/bugfix/create-returns-items-with-temporary-objectids
Bugfix/create returns items with temporary objectids
2 parents c2a1f6f + 5fde769 commit 5298837

4 files changed

Lines changed: 76 additions & 18 deletions

File tree

Sources/CoreDataRepository/CoreDataRepository+CRUD.swift

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,15 @@ extension CoreDataRepository {
2626
_ item: Model,
2727
transactionAuthor: String? = nil
2828
) async -> Result<Model, CoreDataRepositoryError> {
29-
await context.performInScratchPad(schedule: .enqueued) { scratchPad in
29+
await context.performInScratchPad(schedule: .enqueued) { [context] scratchPad in
3030
scratchPad.transactionAuthor = transactionAuthor
3131
let object = Model.RepoManaged(context: scratchPad)
3232
object.create(from: item)
3333
try scratchPad.save()
34+
try context.performAndWait {
35+
try context.save()
36+
}
37+
try scratchPad.obtainPermanentIDs(for: [object])
3438
return object.asUnmanaged
3539
}
3640
}
@@ -68,12 +72,15 @@ extension CoreDataRepository {
6872
with item: Model,
6973
transactionAuthor _: String? = nil
7074
) async -> Result<Model, CoreDataRepositoryError> {
71-
await context.performInScratchPad(schedule: .enqueued) { scratchPad in
75+
await context.performInScratchPad(schedule: .enqueued) { [context] scratchPad in
7276
let id = try scratchPad.tryObjectId(from: url)
7377
let object = try scratchPad.notDeletedObject(for: id)
7478
let repoManaged: Model.RepoManaged = try object.asRepoManaged()
7579
repoManaged.update(from: item)
7680
try scratchPad.save()
81+
try context.performAndWait {
82+
try context.save()
83+
}
7784
return repoManaged.asUnmanaged
7885
}
7986
}
@@ -92,12 +99,15 @@ extension CoreDataRepository {
9299
_ url: URL,
93100
transactionAuthor _: String? = nil
94101
) async -> Result<Void, CoreDataRepositoryError> {
95-
await context.performInScratchPad(schedule: .enqueued) { scratchPad in
102+
await context.performInScratchPad(schedule: .enqueued) { [context] scratchPad in
96103
let id = try scratchPad.tryObjectId(from: url)
97104
let object = try scratchPad.notDeletedObject(for: id)
98105
object.prepareForDeletion()
99106
scratchPad.delete(object)
100107
try scratchPad.save()
108+
try context.performAndWait {
109+
try context.save()
110+
}
101111
return ()
102112
}
103113
}

Tests/CoreDataRepositoryTests/BatchRepositoryTests.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
116116
XCTAssertEqual(result.success.count, newMovies.count)
117117
XCTAssertEqual(result.failed.count, 0)
118118

119+
for movie in result.success {
120+
try await verify(movie)
121+
}
122+
119123
try await repositoryContext().perform {
120124
let data = try self.repositoryContext().fetch(fetchRequest)
121125
XCTAssertEqual(

Tests/CoreDataRepositoryTests/CRUDRepositoryTests.swift

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,16 @@ final class CRUDRepositoryTests: CoreDataXCTestCase {
1616
func testCreateSuccess() async throws {
1717
let movie = Movie(id: UUID(), title: "Create Success", releaseDate: Date(), boxOffice: 100)
1818
let result: Result<Movie, CoreDataRepositoryError> = try await repository().create(movie)
19-
guard case var .success(resultMovie) = result else {
19+
guard case let .success(resultMovie) = result else {
2020
XCTFail("Not expecting a failed result")
2121
return
2222
}
23+
var tempResultMovie = resultMovie
24+
XCTAssertNotNil(tempResultMovie.url)
25+
tempResultMovie.url = nil
26+
XCTAssertNoDifference(tempResultMovie, movie)
2327

24-
XCTAssertNotNil(resultMovie.url)
25-
resultMovie.url = nil
26-
let diff = CustomDump.diff(resultMovie, movie)
27-
XCTAssertNil(diff)
28+
try await verify(resultMovie)
2829
}
2930

3031
func testReadSuccess() async throws {
@@ -39,15 +40,18 @@ final class CRUDRepositoryTests: CoreDataXCTestCase {
3940
let result: Result<Movie, CoreDataRepositoryError> = try await repository()
4041
.read(try XCTUnwrap(createdMovie.url))
4142

42-
guard case var .success(resultMovie) = result else {
43+
guard case let .success(resultMovie) = result else {
4344
XCTFail("Not expecting a failed result")
4445
return
4546
}
4647

47-
XCTAssertNotNil(resultMovie.url)
48-
resultMovie.url = nil
49-
let diff = CustomDump.diff(resultMovie, movie)
50-
XCTAssertNil(diff)
48+
var tempResultMovie = resultMovie
49+
50+
XCTAssertNotNil(tempResultMovie.url)
51+
tempResultMovie.url = nil
52+
XCTAssertNoDifference(tempResultMovie, movie)
53+
54+
try await verify(resultMovie)
5155
}
5256

5357
func testReadFailure() async throws {
@@ -91,15 +95,18 @@ final class CRUDRepositoryTests: CoreDataXCTestCase {
9195
let result: Result<Movie, CoreDataRepositoryError> = try await repository()
9296
.update(try XCTUnwrap(createdMovie.url), with: movie)
9397

94-
guard case var .success(resultMovie) = result else {
98+
guard case let .success(resultMovie) = result else {
9599
XCTFail("Not expecting a failed result")
96100
return
97101
}
98102

99-
XCTAssertNotNil(resultMovie.url)
100-
resultMovie.url = nil
101-
let diff = CustomDump.diff(resultMovie, movie)
102-
XCTAssertNil(diff)
103+
var tempResultMovie = resultMovie
104+
105+
XCTAssertNotNil(tempResultMovie.url)
106+
tempResultMovie.url = nil
107+
XCTAssertNoDifference(tempResultMovie, movie)
108+
109+
try await verify(resultMovie)
103110
}
104111

105112
func testUpdateFailure() async throws {

Tests/CoreDataRepositoryTests/CoreDataXCTestCase.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import Combine
1010
import CoreData
1111
import CoreDataRepository
12+
import CustomDump
1213
import XCTest
1314

1415
class CoreDataXCTestCase: XCTestCase {
@@ -49,4 +50,40 @@ class CoreDataXCTestCase: XCTestCase {
4950
_repository = nil
5051
cancellables.forEach { $0.cancel() }
5152
}
53+
54+
func verify<T>(_ item: T) async throws where T: UnmanagedModel {
55+
guard let url = item.managedRepoUrl else {
56+
XCTFail("Failed to verify item in store because it has no URL")
57+
return
58+
}
59+
60+
let context = try repositoryContext()
61+
let coordinator = try container().persistentStoreCoordinator
62+
context.performAndWait {
63+
guard let objectID = coordinator.managedObjectID(forURIRepresentation: url) else {
64+
XCTFail("Failed to verify item in store because no NSManagedObjectID found in viewContext from URL.")
65+
return
66+
}
67+
var _object: NSManagedObject?
68+
do {
69+
_object = try context.existingObject(with: objectID)
70+
} catch {
71+
XCTFail(
72+
"Failed to verify item in store because it was not found by its NSManagedObjectID. Error: \(error.localizedDescription)"
73+
)
74+
return
75+
}
76+
77+
guard let object = _object else {
78+
XCTFail("Failed to verify item in store because it was not found by its NSManagedObjectID")
79+
return
80+
}
81+
82+
guard let managedItem = object as? T.RepoManaged else {
83+
XCTFail("Failed to verify item in store because it failed to cast to RepoManaged type.")
84+
return
85+
}
86+
XCTAssertNoDifference(item, managedItem.asUnmanaged)
87+
}
88+
}
5289
}

0 commit comments

Comments
 (0)