Skip to content

Commit

Permalink
Merge pull request #17 from roanutil/transaction-author-is-not-applie…
Browse files Browse the repository at this point in the history
…d-to-the-context-for-some-functions

Transaction author is not applied to the context for some functions
  • Loading branch information
roanutil authored Jun 11, 2023
2 parents 5b4681b + 60d25c9 commit 9fa4ae4
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 18 deletions.
18 changes: 12 additions & 6 deletions Sources/CoreDataRepository/CoreDataRepository+Batch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ extension CoreDataRepository {
_ request: NSBatchInsertRequest,
transactionAuthor: String? = nil
) async -> Result<NSBatchInsertResult, CoreDataRepositoryError> {
await context.performInScratchPad { scratchPad in
scratchPad.transactionAuthor = transactionAuthor
await context.performInScratchPad { [context] scratchPad in
context.transactionAuthor = transactionAuthor
guard let result = try scratchPad.execute(request) as? NSBatchInsertResult else {
context.transactionAuthor = nil
throw CoreDataRepositoryError.fetchedObjectFailedToCastToExpectedType
}
context.transactionAuthor = nil
return result
}
}
Expand Down Expand Up @@ -127,11 +129,13 @@ extension CoreDataRepository {
_ request: NSBatchUpdateRequest,
transactionAuthor: String? = nil
) async -> Result<NSBatchUpdateResult, CoreDataRepositoryError> {
await context.performInScratchPad { scratchPad in
scratchPad.transactionAuthor = transactionAuthor
await context.performInScratchPad { [context] scratchPad in
context.transactionAuthor = transactionAuthor
guard let result = try scratchPad.execute(request) as? NSBatchUpdateResult else {
context.transactionAuthor = nil
throw CoreDataRepositoryError.fetchedObjectFailedToCastToExpectedType
}
context.transactionAuthor = nil
return result
}
}
Expand Down Expand Up @@ -193,11 +197,13 @@ extension CoreDataRepository {
_ request: NSBatchDeleteRequest,
transactionAuthor: String? = nil
) async -> Result<NSBatchDeleteResult, CoreDataRepositoryError> {
await context.performInScratchPad { scratchPad in
scratchPad.transactionAuthor = transactionAuthor
await context.performInScratchPad { [context] scratchPad in
context.transactionAuthor = transactionAuthor
guard let result = try scratchPad.execute(request) as? NSBatchDeleteResult else {
context.transactionAuthor = nil
throw CoreDataRepositoryError.fetchedObjectFailedToCastToExpectedType
}
context.transactionAuthor = nil
return result
}
}
Expand Down
13 changes: 10 additions & 3 deletions Sources/CoreDataRepository/CoreDataRepository+CRUD.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@ extension CoreDataRepository {
transactionAuthor: String? = nil
) async -> Result<Model, CoreDataRepositoryError> {
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 {
context.transactionAuthor = transactionAuthor
try context.save()
context.transactionAuthor = nil
}
try scratchPad.obtainPermanentIDs(for: [object])
return object.asUnmanaged
Expand Down Expand Up @@ -70,16 +71,19 @@ extension CoreDataRepository {
public func update<Model: UnmanagedModel>(
_ url: URL,
with item: Model,
transactionAuthor _: String? = nil
transactionAuthor: String? = nil
) async -> Result<Model, CoreDataRepositoryError> {
await context.performInScratchPad(schedule: .enqueued) { [context] scratchPad in
scratchPad.transactionAuthor = transactionAuthor
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 {
context.transactionAuthor = transactionAuthor
try context.save()
context.transactionAuthor = nil
}
return repoManaged.asUnmanaged
}
Expand All @@ -97,16 +101,19 @@ extension CoreDataRepository {
///
public func delete(
_ url: URL,
transactionAuthor _: String? = nil
transactionAuthor: String? = nil
) async -> Result<Void, CoreDataRepositoryError> {
await context.performInScratchPad(schedule: .enqueued) { [context] scratchPad in
scratchPad.transactionAuthor = transactionAuthor
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 {
context.transactionAuthor = transactionAuthor
try context.save()
context.transactionAuthor = nil
}
return ()
}
Expand Down
45 changes: 39 additions & 6 deletions Tests/CoreDataRepositoryTests/BatchRepositoryTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,12 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
XCTAssertEqual(count, 0, "Count of objects in CoreData should be zero at the start of each test.")
}

let historyTimeStamp = Date()
let transactionAuthor: String = #function

let request = try NSBatchInsertRequest(entityName: XCTUnwrap(RepoMovie.entity().name), objects: movies)
let result: Result<NSBatchInsertResult, CoreDataRepositoryError> = try await repository().insert(request)
let result: Result<NSBatchInsertResult, CoreDataRepositoryError> = try await repository()
.insert(request, transactionAuthor: transactionAuthor)

switch result {
case .success:
Expand All @@ -75,6 +79,8 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
"Inserted titles should match expectation"
)
}

try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp)
}

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

let historyTimeStamp = Date()
let transactionAuthor: String = #function

let newMovies = try movies.map(mapDictToMovie(_:))
let result: (success: [Movie], failed: [Movie]) = try await repository().create(newMovies)
let result: (success: [Movie], failed: [Movie]) = try await repository()
.create(newMovies, transactionAuthor: transactionAuthor)

XCTAssertEqual(result.success.count, newMovies.count)
XCTAssertEqual(result.failed.count, 0)
Expand All @@ -128,6 +138,8 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
"Inserted titles should match expectation"
)
}

try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp)
}

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

let _: Result<NSBatchUpdateResult, CoreDataRepositoryError> = try await repository().update(request)
let historyTimeStamp = Date()
let transactionAuthor: String = #function

let _: Result<NSBatchUpdateResult, CoreDataRepositoryError> = try await repository()
.update(request, transactionAuthor: transactionAuthor)

try await repositoryContext().perform {
let data = try self.repositoryContext().fetch(fetchRequest)
Expand All @@ -177,6 +193,7 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
"Updated titles should match request"
)
}
try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp)
}

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

let result: (success: [Movie], failed: [Movie]) = try await repository().update(editedMovies)
let historyTimeStamp = Date()
let transactionAuthor: String = #function

let result: (success: [Movie], failed: [Movie]) = try await repository()
.update(editedMovies, transactionAuthor: transactionAuthor)

XCTAssertEqual(result.success.count, movies.count)
XCTAssertEqual(result.failed.count, 0)

XCTAssertEqual(Set(editedMovies), Set(result.success))

try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp)
}

func testDeleteSuccess() async throws {
Expand All @@ -221,12 +244,17 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
.entity().name
)))

let _: Result<NSBatchDeleteResult, CoreDataRepositoryError> = try await repository().delete(request)
let historyTimeStamp = Date()
let transactionAuthor: String = #function

let _: Result<NSBatchDeleteResult, CoreDataRepositoryError> = try await repository()
.delete(request, transactionAuthor: transactionAuthor)

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

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

let result: (success: [URL], failed: [URL]) = try await repository().delete(urls: movies.compactMap(\.url))
let historyTimeStamp = Date()
let transactionAuthor: String = #function

let result: (success: [URL], failed: [URL]) = try await repository()
.delete(urls: movies.compactMap(\.url), transactionAuthor: transactionAuthor)

XCTAssertEqual(result.success.count, movies.count)
XCTAssertEqual(result.failed.count, 0)
Expand All @@ -251,5 +283,6 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
let data = try self.repositoryContext().fetch(fetchRequest)
XCTAssertEqual(data.map { $0.title ?? "" }.sorted(), [], "There should be no remaining values.")
}
try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp)
}
}
19 changes: 16 additions & 3 deletions Tests/CoreDataRepositoryTests/CRUDRepositoryTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@ import XCTest

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

try await verify(resultMovie)
try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp)
}

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

movie.title = "Update Success - Edited"

let historyTimeStamp = Date()
let transactionAuthor: String = #function

let result: Result<Movie, CoreDataRepositoryError> = try await repository()
.update(XCTUnwrap(createdMovie.url), with: movie)
.update(XCTUnwrap(createdMovie.url), with: movie, transactionAuthor: transactionAuthor)

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

try await verify(resultMovie)
try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp)
}

func testUpdateFailure() async throws {
Expand Down Expand Up @@ -148,15 +156,20 @@ final class CRUDRepositoryTests: CoreDataXCTestCase {
return object.asUnmanaged
}

let historyTimeStamp = Date()
let transactionAuthor: String = #function

let result: Result<Void, CoreDataRepositoryError> = try await repository()
.delete(XCTUnwrap(createdMovie.url))
.delete(XCTUnwrap(createdMovie.url), transactionAuthor: transactionAuthor)

switch result {
case .success:
XCTAssert(true)
case .failure:
XCTFail("Not expecting a failed result")
}

try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp)
}

func testDeleteFailure() async throws {
Expand Down
1 change: 1 addition & 0 deletions Tests/CoreDataRepositoryTests/CoreDataStack.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class CoreDataStack: NSObject {

static var persistentContainer: NSPersistentContainer {
let desc = NSPersistentStoreDescription()
desc.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
desc.type = NSSQLiteStoreType // NSInMemoryStoreType
desc.shouldAddStoreAsynchronously = false
let model = Self.model
Expand Down
12 changes: 12 additions & 0 deletions Tests/CoreDataRepositoryTests/CoreDataXCTestCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,16 @@ class CoreDataXCTestCase: XCTestCase {
XCTAssertNoDifference(item, managedItem.asUnmanaged)
}
}

func verify(transactionAuthor: String?, timeStamp: Date) throws {
let historyRequest = NSPersistentHistoryChangeRequest.fetchHistory(after: timeStamp)
try repositoryContext().performAndWait {
let historyResult = try XCTUnwrap(repositoryContext().execute(historyRequest) as? NSPersistentHistoryResult)
let history = try XCTUnwrap(historyResult.result as? [NSPersistentHistoryTransaction])
XCTAssertGreaterThan(history.count, 0)
history.forEach { historyTransaction in
XCTAssertEqual(historyTransaction.author, transactionAuthor)
}
}
}
}

0 comments on commit 9fa4ae4

Please sign in to comment.