Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
}
}
}