Skip to content

Commit 9fa4ae4

Browse files
authored
Merge pull request #17 from roanutil/transaction-author-is-not-applied-to-the-context-for-some-functions
Transaction author is not applied to the context for some functions
2 parents 5b4681b + 60d25c9 commit 9fa4ae4

6 files changed

Lines changed: 90 additions & 18 deletions

File tree

Sources/CoreDataRepository/CoreDataRepository+Batch.swift

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@ extension CoreDataRepository {
2222
_ request: NSBatchInsertRequest,
2323
transactionAuthor: String? = nil
2424
) async -> Result<NSBatchInsertResult, CoreDataRepositoryError> {
25-
await context.performInScratchPad { scratchPad in
26-
scratchPad.transactionAuthor = transactionAuthor
25+
await context.performInScratchPad { [context] scratchPad in
26+
context.transactionAuthor = transactionAuthor
2727
guard let result = try scratchPad.execute(request) as? NSBatchInsertResult else {
28+
context.transactionAuthor = nil
2829
throw CoreDataRepositoryError.fetchedObjectFailedToCastToExpectedType
2930
}
31+
context.transactionAuthor = nil
3032
return result
3133
}
3234
}
@@ -127,11 +129,13 @@ extension CoreDataRepository {
127129
_ request: NSBatchUpdateRequest,
128130
transactionAuthor: String? = nil
129131
) async -> Result<NSBatchUpdateResult, CoreDataRepositoryError> {
130-
await context.performInScratchPad { scratchPad in
131-
scratchPad.transactionAuthor = transactionAuthor
132+
await context.performInScratchPad { [context] scratchPad in
133+
context.transactionAuthor = transactionAuthor
132134
guard let result = try scratchPad.execute(request) as? NSBatchUpdateResult else {
135+
context.transactionAuthor = nil
133136
throw CoreDataRepositoryError.fetchedObjectFailedToCastToExpectedType
134137
}
138+
context.transactionAuthor = nil
135139
return result
136140
}
137141
}
@@ -193,11 +197,13 @@ extension CoreDataRepository {
193197
_ request: NSBatchDeleteRequest,
194198
transactionAuthor: String? = nil
195199
) async -> Result<NSBatchDeleteResult, CoreDataRepositoryError> {
196-
await context.performInScratchPad { scratchPad in
197-
scratchPad.transactionAuthor = transactionAuthor
200+
await context.performInScratchPad { [context] scratchPad in
201+
context.transactionAuthor = transactionAuthor
198202
guard let result = try scratchPad.execute(request) as? NSBatchDeleteResult else {
203+
context.transactionAuthor = nil
199204
throw CoreDataRepositoryError.fetchedObjectFailedToCastToExpectedType
200205
}
206+
context.transactionAuthor = nil
201207
return result
202208
}
203209
}

Sources/CoreDataRepository/CoreDataRepository+CRUD.swift

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,13 @@ extension CoreDataRepository {
2727
transactionAuthor: String? = nil
2828
) async -> Result<Model, CoreDataRepositoryError> {
2929
await context.performInScratchPad(schedule: .enqueued) { [context] scratchPad in
30-
scratchPad.transactionAuthor = transactionAuthor
3130
let object = Model.RepoManaged(context: scratchPad)
3231
object.create(from: item)
3332
try scratchPad.save()
3433
try context.performAndWait {
34+
context.transactionAuthor = transactionAuthor
3535
try context.save()
36+
context.transactionAuthor = nil
3637
}
3738
try scratchPad.obtainPermanentIDs(for: [object])
3839
return object.asUnmanaged
@@ -70,16 +71,19 @@ extension CoreDataRepository {
7071
public func update<Model: UnmanagedModel>(
7172
_ url: URL,
7273
with item: Model,
73-
transactionAuthor _: String? = nil
74+
transactionAuthor: String? = nil
7475
) async -> Result<Model, CoreDataRepositoryError> {
7576
await context.performInScratchPad(schedule: .enqueued) { [context] scratchPad in
77+
scratchPad.transactionAuthor = transactionAuthor
7678
let id = try scratchPad.tryObjectId(from: url)
7779
let object = try scratchPad.notDeletedObject(for: id)
7880
let repoManaged: Model.RepoManaged = try object.asRepoManaged()
7981
repoManaged.update(from: item)
8082
try scratchPad.save()
8183
try context.performAndWait {
84+
context.transactionAuthor = transactionAuthor
8285
try context.save()
86+
context.transactionAuthor = nil
8387
}
8488
return repoManaged.asUnmanaged
8589
}
@@ -97,16 +101,19 @@ extension CoreDataRepository {
97101
///
98102
public func delete(
99103
_ url: URL,
100-
transactionAuthor _: String? = nil
104+
transactionAuthor: String? = nil
101105
) async -> Result<Void, CoreDataRepositoryError> {
102106
await context.performInScratchPad(schedule: .enqueued) { [context] scratchPad in
107+
scratchPad.transactionAuthor = transactionAuthor
103108
let id = try scratchPad.tryObjectId(from: url)
104109
let object = try scratchPad.notDeletedObject(for: id)
105110
object.prepareForDeletion()
106111
scratchPad.delete(object)
107112
try scratchPad.save()
108113
try context.performAndWait {
114+
context.transactionAuthor = transactionAuthor
109115
try context.save()
116+
context.transactionAuthor = nil
110117
}
111118
return ()
112119
}

Tests/CoreDataRepositoryTests/BatchRepositoryTests.swift

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,12 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
5757
XCTAssertEqual(count, 0, "Count of objects in CoreData should be zero at the start of each test.")
5858
}
5959

60+
let historyTimeStamp = Date()
61+
let transactionAuthor: String = #function
62+
6063
let request = try NSBatchInsertRequest(entityName: XCTUnwrap(RepoMovie.entity().name), objects: movies)
61-
let result: Result<NSBatchInsertResult, CoreDataRepositoryError> = try await repository().insert(request)
64+
let result: Result<NSBatchInsertResult, CoreDataRepositoryError> = try await repository()
65+
.insert(request, transactionAuthor: transactionAuthor)
6266

6367
switch result {
6468
case .success:
@@ -75,6 +79,8 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
7579
"Inserted titles should match expectation"
7680
)
7781
}
82+
83+
try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp)
7884
}
7985

8086
func testInsertFailure() async throws {
@@ -110,8 +116,12 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
110116
XCTAssertEqual(count, 0, "Count of objects in CoreData should be zero at the start of each test.")
111117
}
112118

119+
let historyTimeStamp = Date()
120+
let transactionAuthor: String = #function
121+
113122
let newMovies = try movies.map(mapDictToMovie(_:))
114-
let result: (success: [Movie], failed: [Movie]) = try await repository().create(newMovies)
123+
let result: (success: [Movie], failed: [Movie]) = try await repository()
124+
.create(newMovies, transactionAuthor: transactionAuthor)
115125

116126
XCTAssertEqual(result.success.count, newMovies.count)
117127
XCTAssertEqual(result.failed.count, 0)
@@ -128,6 +138,8 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
128138
"Inserted titles should match expectation"
129139
)
130140
}
141+
142+
try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp)
131143
}
132144

133145
func testReadSuccess() async throws {
@@ -167,7 +179,11 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
167179
request.predicate = predicate
168180
request.propertiesToUpdate = ["title": "Updated!", "boxOffice": 1]
169181

170-
let _: Result<NSBatchUpdateResult, CoreDataRepositoryError> = try await repository().update(request)
182+
let historyTimeStamp = Date()
183+
let transactionAuthor: String = #function
184+
185+
let _: Result<NSBatchUpdateResult, CoreDataRepositoryError> = try await repository()
186+
.update(request, transactionAuthor: transactionAuthor)
171187

172188
try await repositoryContext().perform {
173189
let data = try self.repositoryContext().fetch(fetchRequest)
@@ -177,6 +193,7 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
177193
"Updated titles should match request"
178194
)
179195
}
196+
try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp)
180197
}
181198

182199
func testAltUpdateSuccess() async throws {
@@ -196,12 +213,18 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
196213
let newTitles = ["ZA", "ZB", "ZC", "ZD", "ZE"]
197214
newTitles.enumerated().forEach { index, title in editedMovies[index].title = title }
198215

199-
let result: (success: [Movie], failed: [Movie]) = try await repository().update(editedMovies)
216+
let historyTimeStamp = Date()
217+
let transactionAuthor: String = #function
218+
219+
let result: (success: [Movie], failed: [Movie]) = try await repository()
220+
.update(editedMovies, transactionAuthor: transactionAuthor)
200221

201222
XCTAssertEqual(result.success.count, movies.count)
202223
XCTAssertEqual(result.failed.count, 0)
203224

204225
XCTAssertEqual(Set(editedMovies), Set(result.success))
226+
227+
try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp)
205228
}
206229

207230
func testDeleteSuccess() async throws {
@@ -221,12 +244,17 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
221244
.entity().name
222245
)))
223246

224-
let _: Result<NSBatchDeleteResult, CoreDataRepositoryError> = try await repository().delete(request)
247+
let historyTimeStamp = Date()
248+
let transactionAuthor: String = #function
249+
250+
let _: Result<NSBatchDeleteResult, CoreDataRepositoryError> = try await repository()
251+
.delete(request, transactionAuthor: transactionAuthor)
225252

226253
try await repositoryContext().perform {
227254
let data = try self.repositoryContext().fetch(fetchRequest)
228255
XCTAssertEqual(data.map { $0.title ?? "" }.sorted(), [], "There should be no remaining values.")
229256
}
257+
try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp)
230258
}
231259

232260
func testAltDeleteSuccess() async throws {
@@ -242,7 +270,11 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
242270
movies = repoMovies.map(\.asUnmanaged)
243271
}
244272

245-
let result: (success: [URL], failed: [URL]) = try await repository().delete(urls: movies.compactMap(\.url))
273+
let historyTimeStamp = Date()
274+
let transactionAuthor: String = #function
275+
276+
let result: (success: [URL], failed: [URL]) = try await repository()
277+
.delete(urls: movies.compactMap(\.url), transactionAuthor: transactionAuthor)
246278

247279
XCTAssertEqual(result.success.count, movies.count)
248280
XCTAssertEqual(result.failed.count, 0)
@@ -251,5 +283,6 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
251283
let data = try self.repositoryContext().fetch(fetchRequest)
252284
XCTAssertEqual(data.map { $0.title ?? "" }.sorted(), [], "There should be no remaining values.")
253285
}
286+
try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp)
254287
}
255288
}

Tests/CoreDataRepositoryTests/CRUDRepositoryTests.swift

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@ import XCTest
1414

1515
final class CRUDRepositoryTests: CoreDataXCTestCase {
1616
func testCreateSuccess() async throws {
17+
let historyTimeStamp = Date()
18+
let transactionAuthor: String = #function
1719
let movie = Movie(id: UUID(), title: "Create Success", releaseDate: Date(), boxOffice: 100)
18-
let result: Result<Movie, CoreDataRepositoryError> = try await repository().create(movie)
20+
let result: Result<Movie, CoreDataRepositoryError> = try await repository()
21+
.create(movie, transactionAuthor: transactionAuthor)
1922
guard case let .success(resultMovie) = result else {
2023
XCTFail("Not expecting a failed result")
2124
return
@@ -26,6 +29,7 @@ final class CRUDRepositoryTests: CoreDataXCTestCase {
2629
XCTAssertNoDifference(tempResultMovie, movie)
2730

2831
try await verify(resultMovie)
32+
try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp)
2933
}
3034

3135
func testReadSuccess() async throws {
@@ -92,8 +96,11 @@ final class CRUDRepositoryTests: CoreDataXCTestCase {
9296

9397
movie.title = "Update Success - Edited"
9498

99+
let historyTimeStamp = Date()
100+
let transactionAuthor: String = #function
101+
95102
let result: Result<Movie, CoreDataRepositoryError> = try await repository()
96-
.update(XCTUnwrap(createdMovie.url), with: movie)
103+
.update(XCTUnwrap(createdMovie.url), with: movie, transactionAuthor: transactionAuthor)
97104

98105
guard case let .success(resultMovie) = result else {
99106
XCTFail("Not expecting a failed result")
@@ -107,6 +114,7 @@ final class CRUDRepositoryTests: CoreDataXCTestCase {
107114
XCTAssertNoDifference(tempResultMovie, movie)
108115

109116
try await verify(resultMovie)
117+
try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp)
110118
}
111119

112120
func testUpdateFailure() async throws {
@@ -148,15 +156,20 @@ final class CRUDRepositoryTests: CoreDataXCTestCase {
148156
return object.asUnmanaged
149157
}
150158

159+
let historyTimeStamp = Date()
160+
let transactionAuthor: String = #function
161+
151162
let result: Result<Void, CoreDataRepositoryError> = try await repository()
152-
.delete(XCTUnwrap(createdMovie.url))
163+
.delete(XCTUnwrap(createdMovie.url), transactionAuthor: transactionAuthor)
153164

154165
switch result {
155166
case .success:
156167
XCTAssert(true)
157168
case .failure:
158169
XCTFail("Not expecting a failed result")
159170
}
171+
172+
try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp)
160173
}
161174

162175
func testDeleteFailure() async throws {

Tests/CoreDataRepositoryTests/CoreDataStack.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class CoreDataStack: NSObject {
1717

1818
static var persistentContainer: NSPersistentContainer {
1919
let desc = NSPersistentStoreDescription()
20+
desc.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
2021
desc.type = NSSQLiteStoreType // NSInMemoryStoreType
2122
desc.shouldAddStoreAsynchronously = false
2223
let model = Self.model

Tests/CoreDataRepositoryTests/CoreDataXCTestCase.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,16 @@ class CoreDataXCTestCase: XCTestCase {
8686
XCTAssertNoDifference(item, managedItem.asUnmanaged)
8787
}
8888
}
89+
90+
func verify(transactionAuthor: String?, timeStamp: Date) throws {
91+
let historyRequest = NSPersistentHistoryChangeRequest.fetchHistory(after: timeStamp)
92+
try repositoryContext().performAndWait {
93+
let historyResult = try XCTUnwrap(repositoryContext().execute(historyRequest) as? NSPersistentHistoryResult)
94+
let history = try XCTUnwrap(historyResult.result as? [NSPersistentHistoryTransaction])
95+
XCTAssertGreaterThan(history.count, 0)
96+
history.forEach { historyTransaction in
97+
XCTAssertEqual(historyTransaction.author, transactionAuthor)
98+
}
99+
}
100+
}
89101
}

0 commit comments

Comments
 (0)