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
8 changes: 4 additions & 4 deletions Sources/CoreDataRepository/CoreDataRepository+CRUD.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ extension CoreDataRepository {
object.create(from: item)
let result: Result<NSManagedObject, CoreDataRepositoryError> = .success(object)
return result
.map(to: Model.RepoManaged.self, context: scratchPad)
.map(to: Model.RepoManaged.self)
.save(context: scratchPad)
.map(\.asUnmanaged)
}
Expand All @@ -55,7 +55,7 @@ extension CoreDataRepository {
promise(
Self.getObjectId(fromUrl: url, context: readContext)
.mapToNSManagedObject(context: readContext)
.map(to: Model.RepoManaged.self, context: readContext)
.map(to: Model.RepoManaged.self)
.map(\.asUnmanaged)
)
}
Expand Down Expand Up @@ -83,7 +83,7 @@ extension CoreDataRepository {
scratchPad.transactionAuthor = transactionAuthor
return Self.getObjectId(fromUrl: url, context: scratchPad)
.mapToNSManagedObject(context: scratchPad)
.map(to: Model.RepoManaged.self, context: scratchPad)
.map(to: Model.RepoManaged.self)
.map { repoManaged -> Model.RepoManaged in
repoManaged.update(from: item)
return repoManaged
Expand Down Expand Up @@ -195,7 +195,7 @@ extension CoreDataRepository {
readContext.performAndWait {
let result = Self.getObjectId(fromUrl: url, context: readContext)
.mapToNSManagedObject(context: readContext)
.map(to: T.self, context: readContext)
.map(to: T.self)
promise(result)
}
}.eraseToAnyPublisher()
Expand Down
12 changes: 9 additions & 3 deletions Sources/CoreDataRepository/Result+CRUDHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ extension Result where Success == NSManagedObjectID, Failure == CoreDataReposito
}

extension Result where Success == NSManagedObject, Failure == CoreDataRepositoryError {
func map<T>(to _: T.Type, context _: NSManagedObjectContext) -> Result<T, CoreDataRepositoryError>
func map<T>(to _: T.Type) -> Result<T, CoreDataRepositoryError>
where T: RepositoryManagedModel
{
flatMap { _object in
Expand All @@ -44,9 +44,15 @@ extension Result where Failure == CoreDataRepositoryError {
do {
try context.save()
if let parentContext = context.parent {
try DispatchQueue.main.sync {
try parentContext.save()
var result: Result<Success, CoreDataRepositoryError> = .success(success)
parentContext.performAndWait {
do {
try parentContext.save()
} catch {
result = .failure(.coreData(error as NSError))
}
}
return result
}
return .success(success)
} catch {
Expand Down
18 changes: 7 additions & 11 deletions Tests/CoreDataRepositoryTests/AggregateRepositoryTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,25 +34,21 @@ final class AggregateRepositoryTests: CoreDataXCTestCase {
Movie(id: UUID(), title: "E", releaseDate: Date(), boxOffice: 50),
]
var objectIDs = [NSManagedObjectID]()
var _repository: CoreDataRepository?
var repository: CoreDataRepository { _repository! }

override func setUpWithError() throws {
try super.setUpWithError()
_repository = CoreDataRepository(context: viewContext)
objectIDs = movies.map { $0.asRepoManaged(in: self.viewContext).objectID }
try! viewContext.save()
objectIDs = try movies.map { $0.asRepoManaged(in: try self.viewContext()).objectID }
try viewContext().save()
}

override func tearDownWithError() throws {
try super.tearDownWithError()
_repository = nil
objectIDs = []
}

func testCountSuccess() throws {
let exp = expectation(description: "Get count of movies from CoreData")
let result: AnyPublisher<[[String: Int]], CoreDataRepositoryError> = repository
let result: AnyPublisher<[[String: Int]], CoreDataRepositoryError> = try repository()
.count(predicate: NSPredicate(value: true), entityDesc: RepoMovie.entity())
var values: [[String: Int]] = []
result.subscribe(on: backgroundQueue)
Expand All @@ -77,7 +73,7 @@ final class AggregateRepositoryTests: CoreDataXCTestCase {
func testSumSuccess() throws {
let exp = expectation(description: "Get sum of CoreData Movies boxOffice")
var values: [[String: Decimal]] = []
let result: AnyPublisher<[[String: Decimal]], CoreDataRepositoryError> = repository.sum(
let result: AnyPublisher<[[String: Decimal]], CoreDataRepositoryError> = try repository().sum(
predicate: NSPredicate(value: true),
entityDesc: RepoMovie.entity(),
attributeDesc: RepoMovie.entity().attributesByName.values.first(where: { $0.name == "boxOffice" })!
Expand Down Expand Up @@ -107,7 +103,7 @@ final class AggregateRepositoryTests: CoreDataXCTestCase {
func testAverageSuccess() throws {
let exp = expectation(description: "Get average of CoreData Movies boxOffice")
var values: [[String: Decimal]] = []
let result: AnyPublisher<[[String: Decimal]], CoreDataRepositoryError> = repository.average(
let result: AnyPublisher<[[String: Decimal]], CoreDataRepositoryError> = try repository().average(
predicate: NSPredicate(value: true),
entityDesc: RepoMovie.entity(),
attributeDesc: RepoMovie.entity().attributesByName.values.first(where: { $0.name == "boxOffice" })!
Expand Down Expand Up @@ -137,7 +133,7 @@ final class AggregateRepositoryTests: CoreDataXCTestCase {
func testMinSuccess() throws {
let exp = expectation(description: "Get average of CoreData Movies boxOffice")
var values: [[String: Decimal]] = []
let result: AnyPublisher<[[String: Decimal]], CoreDataRepositoryError> = repository.min(
let result: AnyPublisher<[[String: Decimal]], CoreDataRepositoryError> = try repository().min(
predicate: NSPredicate(value: true),
entityDesc: RepoMovie.entity(),
attributeDesc: RepoMovie.entity().attributesByName.values.first(where: { $0.name == "boxOffice" })!
Expand Down Expand Up @@ -167,7 +163,7 @@ final class AggregateRepositoryTests: CoreDataXCTestCase {
func testMaxSuccess() throws {
let exp = expectation(description: "Get average of CoreData Movies boxOffice")
var values: [[String: Decimal]] = []
let result: AnyPublisher<[[String: Decimal]], CoreDataRepositoryError> = repository.max(
let result: AnyPublisher<[[String: Decimal]], CoreDataRepositoryError> = try repository().max(
predicate: NSPredicate(value: true),
entityDesc: RepoMovie.entity(),
attributeDesc: RepoMovie.entity().attributesByName.values.first(where: { $0.name == "boxOffice" })!
Expand Down
71 changes: 29 additions & 42 deletions Tests/CoreDataRepositoryTests/BatchRepositoryTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,22 +49,9 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
]
}()

var _repository: CoreDataRepository?
var repository: CoreDataRepository { _repository! }

override func setUp() {
super.setUp()
_repository = CoreDataRepository(context: viewContext)
}

override func tearDown() {
super.tearDown()
_repository = nil
}

func mapDictToRepoMovie(_ dict: [String: Any]) throws -> RepoMovie {
try mapDictToMovie(dict)
.asRepoManaged(in: viewContext)
.asRepoManaged(in: try viewContext())
}

func mapDictToMovie(_ dict: [String: Any]) throws -> Movie {
Expand All @@ -76,12 +63,12 @@ final class BatchRepositoryTests: CoreDataXCTestCase {

func testInsertSuccess() throws {
let fetchRequest = NSFetchRequest<RepoMovie>(entityName: "RepoMovie")
let count = try viewContext.count(for: fetchRequest)
let count = try viewContext().count(for: fetchRequest)
XCTAssert(count == 0, "Count of objects in CoreData should be zero at the start of each test.")

let exp = expectation(description: "Successfully batch insert movies.")
let request = NSBatchInsertRequest(entityName: try XCTUnwrap(RepoMovie.entity().name), objects: movies)
repository.insert(request)
try repository().insert(request)
.subscribe(on: backgroundQueue)
.receive(on: mainQueue)
.sink(
Expand All @@ -101,7 +88,7 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
.store(in: &cancellables)
wait(for: [exp], timeout: 5)

let data = try viewContext.fetch(fetchRequest)
let data = try viewContext().fetch(fetchRequest)
XCTAssert(
data.map { $0.title ?? "" }.sorted() == ["A", "B", "C", "D", "E"],
"Inserted titles should match expectation"
Expand All @@ -110,15 +97,15 @@ final class BatchRepositoryTests: CoreDataXCTestCase {

func testInsertFailure() throws {
let fetchRequest = NSFetchRequest<RepoMovie>(entityName: "RepoMovie")
let count = try viewContext.count(for: fetchRequest)
let count = try viewContext().count(for: fetchRequest)
XCTAssert(count == 0, "Count of objects in CoreData should be zero at the start of each test.")

let exp = expectation(description: "Fail to batch insert movies.")
let request = NSBatchInsertRequest(
entityName: try XCTUnwrap(RepoMovie.entity().name),
objects: failureInsertMovies
)
repository.insert(request)
try repository().insert(request)
.subscribe(on: backgroundQueue)
.receive(on: mainQueue)
.sink(
Expand All @@ -136,18 +123,18 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
.store(in: &cancellables)
wait(for: [exp], timeout: 5)

let data = try viewContext.fetch(fetchRequest)
let data = try viewContext().fetch(fetchRequest)
assert(data.map { $0.title ?? "" }.sorted() == [], "There should be no inserted values.")
}

func testCreateSuccess() throws {
let fetchRequest = NSFetchRequest<RepoMovie>(entityName: "RepoMovie")
let count = try viewContext.count(for: fetchRequest)
let count = try viewContext().count(for: fetchRequest)
XCTAssert(count == 0, "Count of objects in CoreData should be zero at the start of each test.")

let exp = expectation(description: "Successfully batch insert movies.")
let newMovies = try movies.map(mapDictToMovie(_:))
let publisher: AnyPublisher<(success: [Movie], failed: [Movie]), Never> = repository.create(newMovies)
let publisher: AnyPublisher<(success: [Movie], failed: [Movie]), Never> = try repository().create(newMovies)
publisher
.subscribe(on: backgroundQueue)
.receive(on: mainQueue)
Expand All @@ -168,7 +155,7 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
.store(in: &cancellables)
wait(for: [exp], timeout: 5)

let data = try viewContext.fetch(fetchRequest)
let data = try viewContext().fetch(fetchRequest)
XCTAssert(
data.map { $0.title ?? "" }.sorted() == ["A", "B", "C", "D", "E"],
"Inserted titles should match expectation"
Expand All @@ -177,17 +164,17 @@ final class BatchRepositoryTests: CoreDataXCTestCase {

func testReadSuccess() throws {
let fetchRequest = NSFetchRequest<RepoMovie>(entityName: "RepoMovie")
let count = try viewContext.count(for: fetchRequest)
let count = try viewContext().count(for: fetchRequest)
XCTAssert(count == 0, "Count of objects in CoreData should be zero at the start of each test.")

let repoMovies = try movies
.map(mapDictToRepoMovie(_:))
try viewContext.save()
try viewContext().save()

let exp = expectation(description: "Successfully batch update movies.")
let urlsToRead = repoMovies.map(\.asUnmanaged).compactMap(\.url)
var resultingMovies = [Movie]()
let publisher: AnyPublisher<(success: [Movie], failed: [URL]), Never> = repository.read(urls: urlsToRead)
let publisher: AnyPublisher<(success: [Movie], failed: [URL]), Never> = try repository().read(urls: urlsToRead)
publisher
.subscribe(on: backgroundQueue)
.receive(on: mainQueue)
Expand Down Expand Up @@ -215,19 +202,19 @@ final class BatchRepositoryTests: CoreDataXCTestCase {

func testUpdateSuccess() throws {
let fetchRequest = NSFetchRequest<RepoMovie>(entityName: "RepoMovie")
let count = try viewContext.count(for: fetchRequest)
let count = try viewContext().count(for: fetchRequest)
XCTAssert(count == 0, "Count of objects in CoreData should be zero at the start of each test.")

_ = try movies
.map(mapDictToRepoMovie(_:))
try viewContext.save()
try viewContext().save()

let exp = expectation(description: "Successfully batch update movies.")
let predicate = NSPredicate(value: true)
let request = NSBatchUpdateRequest(entityName: try XCTUnwrap(RepoMovie.entity().name))
request.predicate = predicate
request.propertiesToUpdate = ["title": "Updated!", "boxOffice": 1]
repository.update(request)
try repository().update(request)
.subscribe(on: backgroundQueue)
.receive(on: mainQueue)
.sink(
Expand All @@ -247,7 +234,7 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
.store(in: &cancellables)
wait(for: [exp], timeout: 5)

let data = try viewContext.fetch(fetchRequest)
let data = try viewContext().fetch(fetchRequest)
XCTAssert(
data.map { $0.title ?? "" }.sorted() == ["Updated!", "Updated!", "Updated!", "Updated!", "Updated!"],
"Updated titles should match request"
Expand All @@ -256,19 +243,19 @@ final class BatchRepositoryTests: CoreDataXCTestCase {

func testAltUpdateSuccess() throws {
let fetchRequest = NSFetchRequest<RepoMovie>(entityName: "RepoMovie")
let count = try viewContext.count(for: fetchRequest)
let count = try viewContext().count(for: fetchRequest)
XCTAssert(count == 0, "Count of objects in CoreData should be zero at the start of each test.")

let repoMovies = try movies
.map(mapDictToRepoMovie(_:))
try viewContext.save()
try viewContext().save()

let exp = expectation(description: "Successfully batch update movies.")
var editedMovies = repoMovies.map(\.asUnmanaged)
let newTitles = ["ZA", "ZB", "ZC", "ZD", "ZE"]
var resultingMovies = [Movie]()
newTitles.enumerated().forEach { index, title in editedMovies[index].title = title }
repository.update(editedMovies)
try repository().update(editedMovies)
.subscribe(on: backgroundQueue)
.receive(on: mainQueue)
.sink(
Expand All @@ -295,20 +282,20 @@ final class BatchRepositoryTests: CoreDataXCTestCase {

func testDeleteSuccess() throws {
let fetchRequest = NSFetchRequest<RepoMovie>(entityName: "RepoMovie")
let count = try viewContext.count(for: fetchRequest)
let count = try viewContext().count(for: fetchRequest)
XCTAssert(count == 0, "Count of objects in CoreData should be zero at the start of each test.")

_ = try movies
.map(mapDictToRepoMovie(_:))
try viewContext.save()
try viewContext().save()

let exp = expectation(description: "Successfully batch delete movies.")
let request =
NSBatchDeleteRequest(fetchRequest: NSFetchRequest<NSFetchRequestResult>(entityName: try XCTUnwrap(
RepoMovie
.entity().name
)))
repository.delete(request)
try repository().delete(request)
.subscribe(on: backgroundQueue)
.receive(on: mainQueue)
.sink(
Expand All @@ -327,26 +314,26 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
)
.store(in: &cancellables)
wait(for: [exp], timeout: 5)
viewContext.reset()
try viewContext().reset()

let data = try viewContext.fetch(fetchRequest)
let data = try viewContext().fetch(fetchRequest)
XCTAssert(data.map { $0.title ?? "" }.sorted() == [], "There should be no remaining values.")
}

// TODO: Add test for delete failure

func testAltDeleteSuccess() throws {
let fetchRequest = NSFetchRequest<RepoMovie>(entityName: "RepoMovie")
let count = try viewContext.count(for: fetchRequest)
let count = try viewContext().count(for: fetchRequest)
XCTAssert(count == 0, "Count of objects in CoreData should be zero at the start of each test.")

let repoMovies = try movies
.map(mapDictToRepoMovie(_:))
try viewContext.save()
try viewContext().save()

let exp = expectation(description: "Successfully batch update movies.")
let urlsToDelete = repoMovies.map(\.asUnmanaged).compactMap(\.url)
repository.delete(urls: urlsToDelete)
try repository().delete(urls: urlsToDelete)
.subscribe(on: backgroundQueue)
.receive(on: mainQueue)
.sink(
Expand All @@ -367,7 +354,7 @@ final class BatchRepositoryTests: CoreDataXCTestCase {
.store(in: &cancellables)
wait(for: [exp], timeout: 5)

let data = try viewContext.fetch(fetchRequest)
let data = try viewContext().fetch(fetchRequest)
XCTAssert(data.map { $0.title ?? "" }.sorted() == [], "There should be no remaining values.")
}
}
Loading