diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 94caf4c..cc1965c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: - name: Lint run: swiftlint lint --quiet test: - runs-on: macos-latest + runs-on: macos-15 environment: default steps: - uses: actions/checkout@v4 diff --git a/Package.swift b/Package.swift index a827fb1..49a04d4 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.10 +// swift-tools-version:6.0 import Foundation import PackageDescription @@ -22,8 +22,7 @@ let package = Package( targets: [ .target( name: "CoreDataRepository", - resources: [.process("Resources")], - swiftSettings: .swiftSix + resources: [.process("Resources")] ), .testTarget( name: "CoreDataRepositoryTests", @@ -31,15 +30,13 @@ let package = Package( "CoreDataRepository", .product(name: "CustomDump", package: "swift-custom-dump"), "Internal", - ], - swiftSettings: .swiftSix + ] ), .target( name: "Internal", dependencies: [ "CoreDataRepository", - ], - swiftSettings: .swiftSix + ] ), ] ) @@ -66,18 +63,6 @@ extension [SupportedPlatform] { } } -extension [SwiftSetting] { - static let swiftSix: Self = [ - .enableUpcomingFeature("BareSlashRegexLiterals"), - .enableUpcomingFeature("ConciseMagicFile"), - .enableUpcomingFeature("DeprecateApplicationMain"), - .enableUpcomingFeature("DisableOutwardActorInference"), - .enableUpcomingFeature("ForwardTrailingClosures"), - .enableUpcomingFeature("ImportObjcForwardDeclarations"), - .enableUpcomingFeature("StrictConcurrency"), - ] -} - if ProcessInfo.benchmarkingEnabled { package.dependencies += [ .package( diff --git a/Tests/CoreDataRepositoryTests/AggregateTests.swift b/Tests/CoreDataRepositoryTests/AggregateTests.swift index f6022cd..0fd75b9 100644 --- a/Tests/CoreDataRepositoryTests/AggregateTests.swift +++ b/Tests/CoreDataRepositoryTests/AggregateTests.swift @@ -6,596 +6,661 @@ import CoreData import CoreDataRepository +import CustomDump import Internal -import XCTest +import Testing -final class CoreDataRepository_AggregateTests: CoreDataXCTestCase { - let fetchRequest: NSFetchRequest = { - let request = UnmanagedModel_UuidId.managedFetchRequest() - request.sortDescriptors = [NSSortDescriptor(keyPath: \ManagedModel_UuidId.int, ascending: true)] - return request - }() +extension CoreDataRepositoryTests { + @Suite + struct AggregateTests: CoreDataTestSuite, @unchecked Sendable { + let container: NSPersistentContainer + let repositoryContext: NSManagedObjectContext + let repository: CoreDataRepository - let values = [ - FetchableModel_UuidId.seeded(10), - FetchableModel_UuidId.seeded(20), - FetchableModel_UuidId.seeded(30), - FetchableModel_UuidId.seeded(40), - FetchableModel_UuidId.seeded(50), - ] - var expectedValues = [FetchableModel_UuidId]() - var objectIds = [NSManagedObjectID]() + let fetchRequest: NSFetchRequest = { + let request = UnmanagedModel_UuidId.managedFetchRequest() + request.sortDescriptors = [NSSortDescriptor(keyPath: \ManagedModel_UuidId.int, ascending: true)] + return request + }() - override func setUpWithError() throws { - try super.setUpWithError() - let (_expectedValues, _objectIds) = try repositoryContext().performAndWait { - let managedMovies = try self.values - .map { - try ManagedIdUrlModel_UuidId(fetchable: $0) - .asManagedModel(in: repositoryContext()) - } - try self.repositoryContext().save() - return try ( - self.repositoryContext().fetch(fetchRequest).map(FetchableModel_UuidId.init(managed:)), - managedMovies.map(\.objectID) - ) - } - expectedValues = _expectedValues - objectIds = _objectIds - } + let values = [ + FetchableModel_UuidId.seeded(10), + FetchableModel_UuidId.seeded(20), + FetchableModel_UuidId.seeded(30), + FetchableModel_UuidId.seeded(40), + FetchableModel_UuidId.seeded(50), + ] + var expectedValues = [FetchableModel_UuidId]() + var objectIds = [NSManagedObjectID]() - override func tearDownWithError() throws { - try super.tearDownWithError() - expectedValues = [] - objectIds = [] - } - - func testCountSuccess() async throws { - let result = try await repository() - .count( - predicate: NSPredicate(value: true), - entityDesc: ManagedModel_UuidId.entity(), - as: Int.self - ) - switch result { - case let .success(value): - XCTAssertEqual(value, 5, "Result value (count) should equal number of values.") - case .failure: - XCTFail("Not expecting failure") - } - } - - func testCountSuccess_UnifiedEndpoint() async throws { - let result = try await repository().aggregate( - function: .count, - predicate: NSPredicate(value: true), - entityDesc: ManagedModel_UuidId.entity(), - attributeDesc: XCTUnwrap( - ManagedModel_UuidId.entity().attributesByName.values - .first(where: { $0.name == "decimal" }) - ), - as: Double.self - ) - switch result { - case let .success(value): - XCTAssertEqual(value, 5, "Result value (count) should equal number of values.") - case .failure: - XCTFail("Not expecting failure") + mutating func extraSetup() async throws { + let (_expectedValues, _objectIds) = try repositoryContext.performAndWait { + let managedMovies = try values + .map { + try ManagedIdUrlModel_UuidId(fetchable: $0) + .asManagedModel(in: repositoryContext) + } + try repositoryContext.save() + return try ( + repositoryContext.fetch(fetchRequest).map(FetchableModel_UuidId.init(managed:)), + managedMovies.map(\.objectID) + ) + } + expectedValues = _expectedValues + objectIds = _objectIds } - } - func testCountSubscription() async throws { - let task = Task { - var resultCount = 0 - let stream = try repository() - .countSubscription( + @Test + func countSuccess() async throws { + let result = await repository + .count( predicate: NSPredicate(value: true), entityDesc: ManagedModel_UuidId.entity(), as: Int.self ) - for await _count in stream { - let count = try _count.get() - resultCount += 1 - switch resultCount { - case 1: - XCTAssertEqual(count, 5, "Result value (count) should equal number of values.") - try delete(managedId: XCTUnwrap(objectIds.last)) - await Task.yield() - case 2: - XCTAssertEqual(count, 4, "Count should match expected value after deleting one value.") - return resultCount - default: - XCTFail("Not expecting any values past the first two.") - return resultCount - } + switch result { + case let .success(value): + expectNoDifference(value, 5, "Result value (count) should equal number of values.") + case .failure: + Issue.record("Not expecting failure") } - return resultCount } - let finalCount = try await task.value - XCTAssertEqual(finalCount, 2) - } - func testCountThrowingSubscription() async throws { - let task = Task { - var resultCount = 0 - let stream = try repository() - .countThrowingSubscription( - predicate: NSPredicate(value: true), - entityDesc: ManagedModel_UuidId.entity(), - as: Int.self - ) - for try await count in stream { - resultCount += 1 - switch resultCount { - case 1: - XCTAssertEqual(count, 5, "Result value (count) should equal number of values.") - try delete(managedId: XCTUnwrap(objectIds.last)) - await Task.yield() - case 2: - XCTAssertEqual(count, 4, "Count should match expected value after deleting one value.") - return resultCount - default: - XCTFail("Not expecting any values past the first two.") - return resultCount - } + @Test + func countSuccess_UnifiedEndpoint() async throws { + let result = try await repository.aggregate( + function: .count, + predicate: NSPredicate(value: true), + entityDesc: ManagedModel_UuidId.entity(), + attributeDesc: #require( + ManagedModel_UuidId.entity().attributesByName.values + .first(where: { $0.name == "decimal" }) + ), + as: Double.self + ) + switch result { + case let .success(value): + expectNoDifference(value, 5, "Result value (count) should equal number of values.") + case .failure: + Issue.record("Not expecting failure") } - return resultCount } - let finalCount = try await task.value - XCTAssertEqual(finalCount, 2) - } - func testSumSuccess() async throws { - let result = try await repository().sum( - predicate: NSPredicate(value: true), - entityDesc: ManagedModel_UuidId.entity(), - attributeDesc: XCTUnwrap( - ManagedModel_UuidId.entity().attributesByName.values - .first(where: { $0.name == "decimal" }) - ), - as: Decimal.self - ) - switch result { - case let .success(value): - XCTAssertEqual(value, 150, "Result value (sum) should equal number of values.") - case .failure: - XCTFail("Not expecting failure") + @Test + func countSubscription() async throws { + let task = Task { + var resultCount = 0 + let stream = repository + .countSubscription( + predicate: NSPredicate(value: true), + entityDesc: ManagedModel_UuidId.entity(), + as: Int.self + ) + for await _count in stream { + let count = try _count.get() + resultCount += 1 + switch resultCount { + case 1: + expectNoDifference(count, 5, "Result value (count) should equal number of values.") + try delete(managedId: #require(objectIds.last)) + await Task.yield() + case 2: + expectNoDifference(count, 4, "Count should match expected value after deleting one value.") + return resultCount + default: + Issue.record("Not expecting any values past the first two.") + return resultCount + } + } + return resultCount + } + let finalCount = try await task.value + expectNoDifference(finalCount, 2) } - } - func testSumSuccess_UnifiedEndpoint() async throws { - let result = try await repository().aggregate( - function: .sum, - predicate: NSPredicate(value: true), - entityDesc: ManagedModel_UuidId.entity(), - attributeDesc: XCTUnwrap( - ManagedModel_UuidId.entity().attributesByName.values - .first(where: { $0.name == "decimal" }) - ), - as: Decimal.self - ) - switch result { - case let .success(value): - XCTAssertEqual(value, 150, "Result value (sum) should equal number of values.") - case .failure: - XCTFail("Not expecting failure") + @Test + func countThrowingSubscription() async throws { + let task = Task { + var resultCount = 0 + let stream = repository + .countThrowingSubscription( + predicate: NSPredicate(value: true), + entityDesc: ManagedModel_UuidId.entity(), + as: Int.self + ) + for try await count in stream { + resultCount += 1 + switch resultCount { + case 1: + expectNoDifference(count, 5, "Result value (count) should equal number of values.") + try delete(managedId: #require(objectIds.last)) + await Task.yield() + case 2: + expectNoDifference(count, 4, "Count should match expected value after deleting one value.") + return resultCount + default: + Issue.record("Not expecting any values past the first two.") + return resultCount + } + } + return resultCount + } + let finalCount = try await task.value + expectNoDifference(finalCount, 2) } - } - func testSumSubscription() async throws { - let task = Task { - var resultCount = 0 - let stream = try repository().sumSubscription( + @Test + func sumSuccess() async throws { + let result = try await repository.sum( predicate: NSPredicate(value: true), entityDesc: ManagedModel_UuidId.entity(), - attributeDesc: XCTUnwrap( + attributeDesc: #require( ManagedModel_UuidId.entity().attributesByName.values .first(where: { $0.name == "decimal" }) ), as: Decimal.self ) - for await _sum in stream { - let sum = try _sum.get() - resultCount += 1 - switch resultCount { - case 1: - XCTAssertEqual(sum, 150, "Result value (sum) should equal number of values.") - try delete(managedId: XCTUnwrap(objectIds.last)) - await Task.yield() - case 2: - XCTAssertEqual(sum, 100, "Result value (sum) should match expected value after deleting one value.") - return resultCount - default: - XCTFail("Not expecting any values past the first two.") - return resultCount - } + switch result { + case let .success(value): + expectNoDifference(value, 150, "Result value (sum) should equal number of values.") + case .failure: + Issue.record("Not expecting failure") } - return resultCount } - let finalCount = try await task.value - XCTAssertEqual(finalCount, 2) - } - func testSumThrowingSubscription() async throws { - let task = Task { - var resultCount = 0 - let stream = try repository().sumThrowingSubscription( + @Test + func sumSuccess_UnifiedEndpoint() async throws { + let result = try await repository.aggregate( + function: .sum, predicate: NSPredicate(value: true), entityDesc: ManagedModel_UuidId.entity(), - attributeDesc: XCTUnwrap( + attributeDesc: #require( ManagedModel_UuidId.entity().attributesByName.values .first(where: { $0.name == "decimal" }) ), as: Decimal.self ) - for try await sum in stream { - resultCount += 1 - switch resultCount { - case 1: - XCTAssertEqual(sum, 150, "Result value (sum) should equal number of values.") - try delete(managedId: XCTUnwrap(objectIds.last)) - await Task.yield() - case 2: - XCTAssertEqual(sum, 100, "Result value (sum) should match expected value after deleting one value.") - return resultCount - default: - XCTFail("Not expecting any values past the first two.") - return resultCount - } + switch result { + case let .success(value): + expectNoDifference(value, 150, "Result value (sum) should equal number of values.") + case .failure: + Issue.record("Not expecting failure") } - return resultCount } - let finalCount = try await task.value - XCTAssertEqual(finalCount, 2) - } - func testAverageSuccess() async throws { - let result = try await repository().average( - predicate: NSPredicate(value: true), - entityDesc: ManagedModel_UuidId.entity(), - attributeDesc: XCTUnwrap( - ManagedModel_UuidId.entity().attributesByName.values - .first(where: { $0.name == "decimal" }) - ), - as: Decimal.self - ) - switch result { - case let .success(value): - XCTAssertEqual( - value, - 30, - "Result value should equal average of values box office." - ) - case .failure: - XCTFail("Not expecting failure") + @Test + func sumSubscription() async throws { + let task = Task { + var resultCount = 0 + let stream = try repository.sumSubscription( + predicate: NSPredicate(value: true), + entityDesc: ManagedModel_UuidId.entity(), + attributeDesc: #require( + ManagedModel_UuidId.entity().attributesByName.values + .first(where: { $0.name == "decimal" }) + ), + as: Decimal.self + ) + for await _sum in stream { + let sum = try _sum.get() + resultCount += 1 + switch resultCount { + case 1: + expectNoDifference(sum, 150, "Result value (sum) should equal number of values.") + try delete(managedId: #require(objectIds.last)) + await Task.yield() + case 2: + expectNoDifference( + sum, + 100, + "Result value (sum) should match expected value after deleting one value." + ) + return resultCount + default: + Issue.record("Not expecting any values past the first two.") + return resultCount + } + } + return resultCount + } + let finalCount = try await task.value + expectNoDifference(finalCount, 2) } - } - func testAverageSuccess_UnifiedEndpoint() async throws { - let result = try await repository().aggregate( - function: .average, - predicate: NSPredicate(value: true), - entityDesc: ManagedModel_UuidId.entity(), - attributeDesc: XCTUnwrap( - ManagedModel_UuidId.entity().attributesByName.values - .first(where: { $0.name == "decimal" }) - ), - as: Decimal.self - ) - switch result { - case let .success(value): - XCTAssertEqual( - value, - 30, - "Result value should equal average of values box office." - ) - case .failure: - XCTFail("Not expecting failure") + @Test + func sumThrowingSubscription() async throws { + let task = Task { + var resultCount = 0 + let stream = try repository.sumThrowingSubscription( + predicate: NSPredicate(value: true), + entityDesc: ManagedModel_UuidId.entity(), + attributeDesc: #require( + ManagedModel_UuidId.entity().attributesByName.values + .first(where: { $0.name == "decimal" }) + ), + as: Decimal.self + ) + for try await sum in stream { + resultCount += 1 + switch resultCount { + case 1: + expectNoDifference(sum, 150, "Result value (sum) should equal number of values.") + try delete(managedId: #require(objectIds.last)) + await Task.yield() + case 2: + expectNoDifference( + sum, + 100, + "Result value (sum) should match expected value after deleting one value." + ) + return resultCount + default: + Issue.record("Not expecting any values past the first two.") + return resultCount + } + } + return resultCount + } + let finalCount = try await task.value + expectNoDifference(finalCount, 2) } - } - func testAverageSubscription() async throws { - let task = Task { - var resultCount = 0 - let stream = try repository().averageSubscription( + @Test + func averageSuccess() async throws { + let result = try await repository.average( predicate: NSPredicate(value: true), entityDesc: ManagedModel_UuidId.entity(), - attributeDesc: XCTUnwrap( + attributeDesc: #require( ManagedModel_UuidId.entity().attributesByName.values .first(where: { $0.name == "decimal" }) ), as: Decimal.self ) - for await _average in stream { - let average = try _average.get() - resultCount += 1 - switch resultCount { - case 1: - XCTAssertEqual(average, 30, "Result value (average) should equal number of values.") - try delete(managedId: XCTUnwrap(objectIds.last)) - await Task.yield() - case 2: - XCTAssertEqual( - average, - 25, - "Result value (average) should match expected value after deleting one value." - ) - return resultCount - default: - XCTFail("Not expecting any values past the first two.") - return resultCount - } + switch result { + case let .success(value): + expectNoDifference( + value, + 30, + "Result value should equal average of values box office." + ) + case .failure: + Issue.record("Not expecting failure") } - return resultCount } - let finalCount = try await task.value - XCTAssertEqual(finalCount, 2) - } - func testAverageThrowingSubscription() async throws { - let task = Task { - var resultCount = 0 - let stream = try repository().averageThrowingSubscription( + @Test + func averageSuccess_UnifiedEndpoint() async throws { + let result = try await repository.aggregate( + function: .average, predicate: NSPredicate(value: true), entityDesc: ManagedModel_UuidId.entity(), - attributeDesc: XCTUnwrap( + attributeDesc: #require( ManagedModel_UuidId.entity().attributesByName.values .first(where: { $0.name == "decimal" }) ), as: Decimal.self ) - for try await average in stream { - resultCount += 1 - switch resultCount { - case 1: - XCTAssertEqual(average, 30, "Result value (average) should equal number of values.") - try delete(managedId: XCTUnwrap(objectIds.last)) - await Task.yield() - case 2: - XCTAssertEqual( - average, - 25, - "Result value (average) should match expected value after deleting one value." - ) - return resultCount - default: - XCTFail("Not expecting any values past the first two.") - return resultCount - } + switch result { + case let .success(value): + expectNoDifference( + value, + 30, + "Result value should equal average of values box office." + ) + case .failure: + Issue.record("Not expecting failure") } - return resultCount } - let finalCount = try await task.value - XCTAssertEqual(finalCount, 2) - } - func testMinSuccess() async throws { - let result = try await repository().min( - predicate: NSPredicate(value: true), - entityDesc: ManagedModel_UuidId.entity(), - attributeDesc: XCTUnwrap( - ManagedModel_UuidId.entity().attributesByName.values - .first(where: { $0.name == "decimal" }) - ), - as: Decimal.self - ) - switch result { - case let .success(value): - XCTAssertEqual( - value, - 10, - "Result value should equal min of values box office." - ) - case .failure: - XCTFail("Not expecting failure") + @Test + func averageSubscription() async throws { + let task = Task { + var resultCount = 0 + let stream = try repository.averageSubscription( + predicate: NSPredicate(value: true), + entityDesc: ManagedModel_UuidId.entity(), + attributeDesc: #require( + ManagedModel_UuidId.entity().attributesByName.values + .first(where: { $0.name == "decimal" }) + ), + as: Decimal.self + ) + for await _average in stream { + let average = try _average.get() + resultCount += 1 + switch resultCount { + case 1: + expectNoDifference(average, 30, "Result value (average) should equal number of values.") + try delete(managedId: #require(objectIds.last)) + await Task.yield() + case 2: + expectNoDifference( + average, + 25, + "Result value (average) should match expected value after deleting one value." + ) + return resultCount + default: + Issue.record("Not expecting any values past the first two.") + return resultCount + } + } + return resultCount + } + let finalCount = try await task.value + expectNoDifference(finalCount, 2) } - } - func testMinSuccess_UnifiedEndpoint() async throws { - let result = try await repository().aggregate( - function: .min, - predicate: NSPredicate(value: true), - entityDesc: ManagedModel_UuidId.entity(), - attributeDesc: XCTUnwrap( - ManagedModel_UuidId.entity().attributesByName.values - .first(where: { $0.name == "decimal" }) - ), - as: Decimal.self - ) - switch result { - case let .success(value): - XCTAssertEqual( - value, - 10, - "Result value should equal min of values box office." - ) - case .failure: - XCTFail("Not expecting failure") + @Test + func averageThrowingSubscription() async throws { + let task = Task { + var resultCount = 0 + let stream = try repository.averageThrowingSubscription( + predicate: NSPredicate(value: true), + entityDesc: ManagedModel_UuidId.entity(), + attributeDesc: #require( + ManagedModel_UuidId.entity().attributesByName.values + .first(where: { $0.name == "decimal" }) + ), + as: Decimal.self + ) + for try await average in stream { + resultCount += 1 + switch resultCount { + case 1: + expectNoDifference(average, 30, "Result value (average) should equal number of values.") + try delete(managedId: #require(objectIds.last)) + await Task.yield() + case 2: + expectNoDifference( + average, + 25, + "Result value (average) should match expected value after deleting one value." + ) + return resultCount + default: + Issue.record("Not expecting any values past the first two.") + return resultCount + } + } + return resultCount + } + let finalCount = try await task.value + expectNoDifference(finalCount, 2) } - } - func testMinSubscription() async throws { - let task = Task { - var resultCount = 0 - let stream = try repository().minSubscription( + @Test + func minSuccess() async throws { + let result = try await repository.min( predicate: NSPredicate(value: true), entityDesc: ManagedModel_UuidId.entity(), - attributeDesc: XCTUnwrap( + attributeDesc: #require( ManagedModel_UuidId.entity().attributesByName.values .first(where: { $0.name == "decimal" }) ), as: Decimal.self ) - for await _min in stream { - let min = try _min.get() - resultCount += 1 - switch resultCount { - case 1: - XCTAssertEqual(min, 10, "Result value (min) should equal number of values.") - try delete(managedId: XCTUnwrap(objectIds.last)) - await Task.yield() - case 2: - XCTAssertEqual(min, 10, "Result value (min) should match expected value after deleting one value.") - return resultCount - default: - XCTFail("Not expecting any values past the first two.") - return resultCount - } + switch result { + case let .success(value): + expectNoDifference( + value, + 10, + "Result value should equal min of values box office." + ) + case .failure: + Issue.record("Not expecting failure") } - return resultCount } - let finalCount = try await task.value - XCTAssertEqual(finalCount, 2) - } - func testMinThrowingSubscription() async throws { - let task = Task { - var resultCount = 0 - let stream = try repository().minThrowingSubscription( + @Test + func minSuccess_UnifiedEndpoint() async throws { + let result = try await repository.aggregate( + function: .min, predicate: NSPredicate(value: true), entityDesc: ManagedModel_UuidId.entity(), - attributeDesc: XCTUnwrap( + attributeDesc: #require( ManagedModel_UuidId.entity().attributesByName.values .first(where: { $0.name == "decimal" }) ), as: Decimal.self ) - for try await min in stream { - resultCount += 1 - switch resultCount { - case 1: - XCTAssertEqual(min, 10, "Result value (min) should equal number of values.") - try delete(managedId: XCTUnwrap(objectIds.last)) - await Task.yield() - case 2: - XCTAssertEqual(min, 10, "Result value (min) should match expected value after deleting one value.") - return resultCount - default: - XCTFail("Not expecting any values past the first two.") - return resultCount - } + switch result { + case let .success(value): + expectNoDifference( + value, + 10, + "Result value should equal min of values box office." + ) + case .failure: + Issue.record("Not expecting failure") } - return resultCount } - let finalCount = try await task.value - XCTAssertEqual(finalCount, 2) - } - func testMaxSuccess() async throws { - let result = try await repository().max( - predicate: NSPredicate(value: true), - entityDesc: ManagedModel_UuidId.entity(), - attributeDesc: XCTUnwrap( - ManagedModel_UuidId.entity().attributesByName.values - .first(where: { $0.name == "decimal" }) - ), - as: Decimal.self - ) - switch result { - case let .success(value): - XCTAssertEqual( - value, - 50, - "Result value should equal max of values box office." - ) - case .failure: - XCTFail("Not expecting failure") + @Test + func minSubscription() async throws { + let task = Task { + var resultCount = 0 + let stream = try repository.minSubscription( + predicate: NSPredicate(value: true), + entityDesc: ManagedModel_UuidId.entity(), + attributeDesc: #require( + ManagedModel_UuidId.entity().attributesByName.values + .first(where: { $0.name == "decimal" }) + ), + as: Decimal.self + ) + for await _min in stream { + let min = try _min.get() + resultCount += 1 + switch resultCount { + case 1: + expectNoDifference(min, 10, "Result value (min) should equal number of values.") + try delete(managedId: #require(objectIds.last)) + await Task.yield() + case 2: + expectNoDifference( + min, + 10, + "Result value (min) should match expected value after deleting one value." + ) + return resultCount + default: + Issue.record("Not expecting any values past the first two.") + return resultCount + } + } + return resultCount + } + let finalCount = try await task.value + expectNoDifference(finalCount, 2) } - } - func testMaxSuccess_UnifiedEndpoint() async throws { - let result = try await repository().aggregate( - function: .max, - predicate: NSPredicate(value: true), - entityDesc: ManagedModel_UuidId.entity(), - attributeDesc: XCTUnwrap( - ManagedModel_UuidId.entity().attributesByName.values - .first(where: { $0.name == "decimal" }) - ), - as: Decimal.self - ) - switch result { - case let .success(value): - XCTAssertEqual( - value, - 50, - "Result value should equal max of values box office." - ) - case .failure: - XCTFail("Not expecting failure") + @Test + func minThrowingSubscription() async throws { + let task = Task { + var resultCount = 0 + let stream = try repository.minThrowingSubscription( + predicate: NSPredicate(value: true), + entityDesc: ManagedModel_UuidId.entity(), + attributeDesc: #require( + ManagedModel_UuidId.entity().attributesByName.values + .first(where: { $0.name == "decimal" }) + ), + as: Decimal.self + ) + for try await min in stream { + resultCount += 1 + switch resultCount { + case 1: + expectNoDifference(min, 10, "Result value (min) should equal number of values.") + try delete(managedId: #require(objectIds.last)) + await Task.yield() + case 2: + expectNoDifference( + min, + 10, + "Result value (min) should match expected value after deleting one value." + ) + return resultCount + default: + Issue.record("Not expecting any values past the first two.") + return resultCount + } + } + return resultCount + } + let finalCount = try await task.value + expectNoDifference(finalCount, 2) } - } - func testMaxSubscription() async throws { - let task = Task { - var resultCount = 0 - let stream = try repository().maxSubscription( + @Test + func maxSuccess() async throws { + let result = try await repository.max( predicate: NSPredicate(value: true), entityDesc: ManagedModel_UuidId.entity(), - attributeDesc: XCTUnwrap( + attributeDesc: #require( ManagedModel_UuidId.entity().attributesByName.values .first(where: { $0.name == "decimal" }) ), as: Decimal.self ) - for await _max in stream { - let max = try _max.get() - resultCount += 1 - switch resultCount { - case 1: - XCTAssertEqual(max, 50, "Result value (max) should equal number of values.") - try delete(managedId: XCTUnwrap(objectIds.last)) - await Task.yield() - case 2: - XCTAssertEqual(max, 40, "Result value (max) should match expected value after deleting one value.") - return resultCount - default: - XCTFail("Not expecting any values past the first two.") - return resultCount - } + switch result { + case let .success(value): + expectNoDifference( + value, + 50, + "Result value should equal max of values box office." + ) + case .failure: + Issue.record("Not expecting failure") } - return resultCount } - let finalCount = try await task.value - XCTAssertEqual(finalCount, 2) - } - func testMaxThrowingSubscription() async throws { - let task = Task { - var resultCount = 0 - let stream = try repository().maxThrowingSubscription( + @Test + func maxSuccess_UnifiedEndpoint() async throws { + let result = try await repository.aggregate( + function: .max, predicate: NSPredicate(value: true), entityDesc: ManagedModel_UuidId.entity(), - attributeDesc: XCTUnwrap( + attributeDesc: #require( ManagedModel_UuidId.entity().attributesByName.values .first(where: { $0.name == "decimal" }) ), as: Decimal.self ) - for try await max in stream { - resultCount += 1 - switch resultCount { - case 1: - XCTAssertEqual(max, 50, "Result value (max) should equal number of values.") - try delete(managedId: XCTUnwrap(objectIds.last)) - await Task.yield() - case 2: - XCTAssertEqual(max, 40, "Result value (max) should match expected value after deleting one value.") - return resultCount - default: - XCTFail("Not expecting any values past the first two.") - return resultCount + switch result { + case let .success(value): + expectNoDifference( + value, + 50, + "Result value should equal max of values box office." + ) + case .failure: + Issue.record("Not expecting failure") + } + } + + @Test + func maxSubscription() async throws { + let task = Task { + var resultCount = 0 + let stream = try repository.maxSubscription( + predicate: NSPredicate(value: true), + entityDesc: ManagedModel_UuidId.entity(), + attributeDesc: #require( + ManagedModel_UuidId.entity().attributesByName.values + .first(where: { $0.name == "decimal" }) + ), + as: Decimal.self + ) + for await _max in stream { + let max = try _max.get() + resultCount += 1 + switch resultCount { + case 1: + expectNoDifference(max, 50, "Result value (max) should equal number of values.") + try delete(managedId: #require(objectIds.last)) + await Task.yield() + case 2: + expectNoDifference( + max, + 40, + "Result value (max) should match expected value after deleting one value." + ) + return resultCount + default: + Issue.record("Not expecting any values past the first two.") + return resultCount + } } + return resultCount + } + let finalCount = try await task.value + expectNoDifference(finalCount, 2) + } + + @Test + func maxThrowingSubscription() async throws { + let task = Task { + var resultCount = 0 + let stream = try repository.maxThrowingSubscription( + predicate: NSPredicate(value: true), + entityDesc: ManagedModel_UuidId.entity(), + attributeDesc: #require( + ManagedModel_UuidId.entity().attributesByName.values + .first(where: { $0.name == "decimal" }) + ), + as: Decimal.self + ) + for try await max in stream { + resultCount += 1 + switch resultCount { + case 1: + expectNoDifference(max, 50, "Result value (max) should equal number of values.") + try delete(managedId: #require(objectIds.last)) + await Task.yield() + case 2: + expectNoDifference( + max, + 40, + "Result value (max) should match expected value after deleting one value." + ) + return resultCount + default: + Issue.record("Not expecting any values past the first two.") + return resultCount + } + } + return resultCount + } + let finalCount = try await task.value + expectNoDifference(finalCount, 2) + } + + @Test + func countWithPredicate() async throws { + let result = await repository + .count( + predicate: NSComparisonPredicate( + leftExpression: NSExpression(forKeyPath: \ManagedModel_UuidId.string), + rightExpression: NSExpression(forConstantValue: "10"), + modifier: .direct, + type: .notEqualTo + ), + entityDesc: ManagedModel_UuidId.entity(), + as: Int.self + ) + switch result { + case let .success(count): + expectNoDifference(count, 4, "Result value (count) should equal number of values not titled 'A'.") + case .failure: + Issue.record("Not expecting failure") } - return resultCount } - let finalCount = try await task.value - XCTAssertEqual(finalCount, 2) - } - func testCountWithPredicate() async throws { - let result = try await repository() - .count( + @Test + func sumWithPredicate() async throws { + let result = try await repository.sum( predicate: NSComparisonPredicate( leftExpression: NSExpression(forKeyPath: \ManagedModel_UuidId.string), rightExpression: NSExpression(forConstantValue: "10"), @@ -603,40 +668,32 @@ final class CoreDataRepository_AggregateTests: CoreDataXCTestCase { type: .notEqualTo ), entityDesc: ManagedModel_UuidId.entity(), - as: Int.self + attributeDesc: #require( + ManagedModel_UuidId.entity().attributesByName.values + .first(where: { $0.name == "decimal" }) + ), + as: Decimal.self ) - switch result { - case let .success(count): - XCTAssertEqual(count, 4, "Result value (count) should equal number of values not titled 'A'.") - case .failure: - XCTFail("Not expecting failure") + switch result { + case let .success(sum): + expectNoDifference( + sum, + 140, + "Result value should equal sum of values box office that are not titled 'A'." + ) + case .failure: + Issue.record("Not expecting failure") + } } - } - func testSumWithPredicate() async throws { - let result = try await repository().sum( - predicate: NSComparisonPredicate( - leftExpression: NSExpression(forKeyPath: \ManagedModel_UuidId.string), - rightExpression: NSExpression(forConstantValue: "10"), - modifier: .direct, - type: .notEqualTo - ), - entityDesc: ManagedModel_UuidId.entity(), - attributeDesc: XCTUnwrap( - ManagedModel_UuidId.entity().attributesByName.values - .first(where: { $0.name == "decimal" }) - ), - as: Decimal.self - ) - switch result { - case let .success(sum): - XCTAssertEqual( - sum, - 140, - "Result value should equal sum of values box office that are not titled 'A'." - ) - case .failure: - XCTFail("Not expecting failure") + init( + container: NSPersistentContainer, + repositoryContext: NSManagedObjectContext, + repository: CoreDataRepository + ) { + self.container = container + self.repositoryContext = repositoryContext + self.repository = repository } } } diff --git a/Tests/CoreDataRepositoryTests/BatchRequestTests.swift b/Tests/CoreDataRepositoryTests/BatchRequestTests.swift index 4def5e6..2485dc7 100644 --- a/Tests/CoreDataRepositoryTests/BatchRequestTests.swift +++ b/Tests/CoreDataRepositoryTests/BatchRequestTests.swift @@ -8,182 +8,203 @@ import CoreData import CoreDataRepository import CustomDump import Internal -import XCTest - -final class BatchRequestTests: CoreDataXCTestCase { - let values: [[String: Any]] = [ - ManagedIdUrlModel_UuidId.seeded(1).asDict, - ManagedIdUrlModel_UuidId.seeded(2).asDict, - ManagedIdUrlModel_UuidId.seeded(3).asDict, - ManagedIdUrlModel_UuidId.seeded(4).asDict, - ManagedIdUrlModel_UuidId.seeded(5).asDict, - ] - let failureInsertMovies: [[String: Any]] = [ - ["id": "A", "title": 1, "releaseDate": "A"], - ["id": "B", "title": 2, "releaseDate": "B"], - ["id": "C", "title": 3, "releaseDate": "C"], - ["id": "D", "title": 4, "releaseDate": "D"], - ["id": "E", "title": 5, "releaseDate": "E"], - ] - let failureCreateMovies: [[String: Any]] = [ - ["id": UUID(uniform: "A"), "title": "A", "releaseDate": Date()], - ["id": UUID(uniform: "A"), "title": "B", "releaseDate": Date()], - ["id": UUID(uniform: "A"), "title": "C", "releaseDate": Date()], - ["id": UUID(uniform: "A"), "title": "D", "releaseDate": Date()], - ["id": UUID(uniform: "A"), "title": "E", "releaseDate": Date()], - ] - - func mapDictToManagedModel(_ dict: [String: Any]) throws -> ManagedModel_UuidId { - try mapDictToUnmanagedModel(dict).asManagedModel(in: repositoryContext()) - } - - func mapDictToUnmanagedModel(_ dict: [String: Any]) throws -> ManagedIdUrlModel_UuidId { - let id = try XCTUnwrap(dict["id"] as? UUID) - let bool = try XCTUnwrap(dict["bool"] as? Bool) - let date = try XCTUnwrap(dict["date"] as? Date) - let decimal = try XCTUnwrap(dict["decimal"] as? Decimal) - let double = try XCTUnwrap(dict["double"] as? Double) - let float = try XCTUnwrap(dict["float"] as? Float) - let int = try XCTUnwrap(dict["int"] as? Int) - let string = try XCTUnwrap(dict["string"] as? String) - let uuid = try XCTUnwrap(dict["uuid"] as? UUID) - - return .init( - bool: bool, - date: date, - decimal: decimal, - double: double, - float: float, - id: id, - int: int, - managedIdUrl: nil, - string: string, - uuid: uuid - ) - } - - func testInsertSuccess() async throws { - let fetchRequest = ManagedIdUrlModel_UuidId.managedFetchRequest() - try await repositoryContext().perform { - let count = try self.repositoryContext().count(for: fetchRequest) - 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(ManagedModel_UuidId.entity().name), - objects: values - ) - let result: Result = try await repository() - .insert(request, transactionAuthor: transactionAuthor) - - switch result { - case .success: - XCTAssert(true) - case .failure: - XCTFail("Not expecting a failure result") +import Testing + +extension CoreDataRepositoryTests { + @Suite + struct BatchRequestTests: CoreDataTestSuite { + let container: NSPersistentContainer + let repositoryContext: NSManagedObjectContext + let repository: CoreDataRepository + + let values: [[String: Any]] = [ + ManagedIdUrlModel_UuidId.seeded(1).asDict, + ManagedIdUrlModel_UuidId.seeded(2).asDict, + ManagedIdUrlModel_UuidId.seeded(3).asDict, + ManagedIdUrlModel_UuidId.seeded(4).asDict, + ManagedIdUrlModel_UuidId.seeded(5).asDict, + ] + let failureInsertMovies: [[String: Any]] = [ + ["id": "A", "title": 1, "releaseDate": "A"], + ["id": "B", "title": 2, "releaseDate": "B"], + ["id": "C", "title": 3, "releaseDate": "C"], + ["id": "D", "title": 4, "releaseDate": "D"], + ["id": "E", "title": 5, "releaseDate": "E"], + ] + let failureCreateMovies: [[String: Any]] = [ + ["id": UUID(uniform: "A"), "title": "A", "releaseDate": Date()], + ["id": UUID(uniform: "A"), "title": "B", "releaseDate": Date()], + ["id": UUID(uniform: "A"), "title": "C", "releaseDate": Date()], + ["id": UUID(uniform: "A"), "title": "D", "releaseDate": Date()], + ["id": UUID(uniform: "A"), "title": "E", "releaseDate": Date()], + ] + + func mapDictToManagedModel(_ dict: [String: Any]) throws -> ManagedModel_UuidId { + try mapDictToUnmanagedModel(dict).asManagedModel(in: repositoryContext) } - try await repositoryContext().perform { - let data = try self.repositoryContext().fetch(fetchRequest) - XCTAssertEqual( - data.map(\.string).sorted(), - ["1", "2", "3", "4", "5"], - "Inserted titles should match expectation" + func mapDictToUnmanagedModel(_ dict: [String: Any]) throws -> ManagedIdUrlModel_UuidId { + let id = try #require(dict["id"] as? UUID) + let bool = try #require(dict["bool"] as? Bool) + let date = try #require(dict["date"] as? Date) + let decimal = try #require(dict["decimal"] as? Decimal) + let double = try #require(dict["double"] as? Double) + let float = try #require(dict["float"] as? Float) + let int = try #require(dict["int"] as? Int) + let string = try #require(dict["string"] as? String) + let uuid = try #require(dict["uuid"] as? UUID) + + return .init( + bool: bool, + date: date, + decimal: decimal, + double: double, + float: float, + id: id, + int: int, + managedIdUrl: nil, + string: string, + uuid: uuid ) } - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } - - func testInsertFailure() async throws { - let fetchRequest = ManagedIdUrlModel_UuidId.managedFetchRequest() - try await repositoryContext().perform { - let count = try self.repositoryContext().count(for: fetchRequest) - XCTAssertEqual(count, 0, "Count of objects in CoreData should be zero at the start of each test.") - } + @Test + func insertSuccess() async throws { + let fetchRequest = ManagedIdUrlModel_UuidId.managedFetchRequest() + try await repositoryContext.perform { + let count = try repositoryContext.count(for: fetchRequest) + expectNoDifference(count, 0, "Count of objects in CoreData should be zero at the start of each test.") + } - let request = try NSBatchInsertRequest( - entityName: XCTUnwrap(ManagedModel_UuidId.entity().name), - objects: failureInsertMovies - ) - let result: Result = try await repository().insert(request) - - switch result { - case .success: - XCTFail("Not expecting a success result") - case .failure: - XCTAssert(true) - } + let historyTimeStamp = Date() + let transactionAuthor: String = #function - try await repositoryContext().perform { - let data = try self.repositoryContext().fetch(fetchRequest) - XCTAssertEqual(data.map(\.string).sorted(), [], "There should be no inserted values.") - } - } - - func testUpdateSuccess() async throws { - let fetchRequest = ManagedIdUrlModel_UuidId.managedFetchRequest() - try await repositoryContext().perform { - let count = try self.repositoryContext().count(for: fetchRequest) - XCTAssertEqual(count, 0, "Count of objects in CoreData should be zero at the start of each test.") - - let _ = try self.values - .map(self.mapDictToManagedModel(_:)) - try self.repositoryContext().save() + let request = try NSBatchInsertRequest( + entityName: #require(ManagedModel_UuidId.entity().name), + objects: values + ) + let result: Result = await repository + .insert(request, transactionAuthor: transactionAuthor) + + switch result { + case .success: + break + case .failure: + Issue.record("Not expecting a failure result") + } + + try await repositoryContext.perform { + let data = try repositoryContext.fetch(fetchRequest) + expectNoDifference( + data.map(\.string).sorted(), + ["1", "2", "3", "4", "5"], + "Inserted titles should match expectation" + ) + } + + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) } - let predicate = NSPredicate(value: true) - let request = try NSBatchUpdateRequest(entityName: XCTUnwrap(ManagedModel_UuidId.entity().name)) - request.predicate = predicate - request.propertiesToUpdate = ["string": "Updated!", "int": 1] - - let historyTimeStamp = Date() - let transactionAuthor: String = #function - - let _: Result = try await repository() - .update(request, transactionAuthor: transactionAuthor) - - try await repositoryContext().perform { - let data = try self.repositoryContext().fetch(fetchRequest) - XCTAssertEqual( - data.map(\.string).sorted(), - ["Updated!", "Updated!", "Updated!", "Updated!", "Updated!"], - "Updated titles should match request" + @Test + func insertFailure() async throws { + let fetchRequest = ManagedIdUrlModel_UuidId.managedFetchRequest() + try await repositoryContext.perform { + let count = try repositoryContext.count(for: fetchRequest) + expectNoDifference(count, 0, "Count of objects in CoreData should be zero at the start of each test.") + } + + let request = try NSBatchInsertRequest( + entityName: #require(ManagedModel_UuidId.entity().name), + objects: failureInsertMovies ) + let result: Result = await repository.insert(request) + + switch result { + case .success: + Issue.record("Not expecting a success result") + case .failure: + break + } + + try await repositoryContext.perform { + let data = try repositoryContext.fetch(fetchRequest) + expectNoDifference(data.map(\.string).sorted(), [], "There should be no inserted values.") + } } - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } - - func testDeleteSuccess() async throws { - let fetchRequest = ManagedIdUrlModel_UuidId.managedFetchRequest() - try await repositoryContext().perform { - let count = try self.repositoryContext().count(for: fetchRequest) - XCTAssertEqual(count, 0, "Count of objects in CoreData should be zero at the start of each test.") - let _ = try self.values - .map(self.mapDictToManagedModel(_:)) - try self.repositoryContext().save() + @Test + func updateSuccess() async throws { + let fetchRequest = ManagedIdUrlModel_UuidId.managedFetchRequest() + try await repositoryContext.perform { + let count = try repositoryContext.count(for: fetchRequest) + expectNoDifference(count, 0, "Count of objects in CoreData should be zero at the start of each test.") + + let _ = try values + .map(mapDictToManagedModel(_:)) + try repositoryContext.save() + } + + let predicate = NSPredicate(value: true) + let request = try NSBatchUpdateRequest(entityName: #require(ManagedModel_UuidId.entity().name)) + request.predicate = predicate + request.propertiesToUpdate = ["string": "Updated!", "int": 1] + + let historyTimeStamp = Date() + let transactionAuthor: String = #function + + let _: Result = await repository + .update(request, transactionAuthor: transactionAuthor) + + try await repositoryContext.perform { + let data = try repositoryContext.fetch(fetchRequest) + expectNoDifference( + data.map(\.string).sorted(), + ["Updated!", "Updated!", "Updated!", "Updated!", "Updated!"], + "Updated titles should match request" + ) + } + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) } - let request = - try NSBatchDeleteRequest(fetchRequest: NSFetchRequest(entityName: XCTUnwrap( - ManagedModel_UuidId - .entity().name - ))) - - let historyTimeStamp = Date() - let transactionAuthor: String = #function - - let _: Result = try await repository() - .delete(request, transactionAuthor: transactionAuthor) + @Test + func deleteSuccess() async throws { + let fetchRequest = ManagedIdUrlModel_UuidId.managedFetchRequest() + try await repositoryContext.perform { + let count = try repositoryContext.count(for: fetchRequest) + expectNoDifference(count, 0, "Count of objects in CoreData should be zero at the start of each test.") + + let _ = try values + .map(mapDictToManagedModel(_:)) + try repositoryContext.save() + } + + let request = + try NSBatchDeleteRequest(fetchRequest: NSFetchRequest(entityName: #require( + ManagedModel_UuidId + .entity().name + ))) + + let historyTimeStamp = Date() + let transactionAuthor: String = #function + + let _: Result = await repository + .delete(request, transactionAuthor: transactionAuthor) + + try await repositoryContext.perform { + let data = try repositoryContext.fetch(fetchRequest) + expectNoDifference(data.map(\.string).sorted(), [], "There should be no remaining values.") + } + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) + } - try await repositoryContext().perform { - let data = try self.repositoryContext().fetch(fetchRequest) - XCTAssertEqual(data.map(\.string).sorted(), [], "There should be no remaining values.") + init( + container: NSPersistentContainer, + repositoryContext: NSManagedObjectContext, + repository: CoreDataRepository + ) { + self.container = container + self.repositoryContext = repositoryContext + self.repository = repository } - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) } } diff --git a/Tests/CoreDataRepositoryTests/CoreDataRepositoryTests.swift b/Tests/CoreDataRepositoryTests/CoreDataRepositoryTests.swift new file mode 100644 index 0000000..5540aef --- /dev/null +++ b/Tests/CoreDataRepositoryTests/CoreDataRepositoryTests.swift @@ -0,0 +1,10 @@ +// CoreDataRepositoryTests.swift +// CoreDataRepository +// +// This source code is licensed under the MIT License (MIT) found in the +// LICENSE file in the root directory of this source tree. + +import Testing + +@Suite(.serialized) +struct CoreDataRepositoryTests {} diff --git a/Tests/CoreDataRepositoryTests/CoreDataTestSuite.swift b/Tests/CoreDataRepositoryTests/CoreDataTestSuite.swift new file mode 100644 index 0000000..58795ad --- /dev/null +++ b/Tests/CoreDataRepositoryTests/CoreDataTestSuite.swift @@ -0,0 +1,157 @@ +// CoreDataTestSuite.swift +// CoreDataRepository +// +// This source code is licensed under the MIT License (MIT) found in the +// LICENSE file in the root directory of this source tree. + +import CoreData +import CoreDataRepository +import CustomDump +import Internal +import Testing + +protocol CoreDataTestSuite { + var container: NSPersistentContainer { get } + var repositoryContext: NSManagedObjectContext { get } + var repository: CoreDataRepository { get } + + mutating func extraSetup() async throws + + init() async throws + init(container: NSPersistentContainer, repositoryContext: NSManagedObjectContext, repository: CoreDataRepository) +} + +extension CoreDataTestSuite { + init() async throws { + let stack = CoreDataStack( + storeName: "coredata_repository_tests", + type: .sqliteEphemeral, + container: CoreDataStack.persistentContainer( + storeName: "coredata_repository_tests", + type: .sqliteEphemeral, + model: .model_UuidId + ) + ) + let _container = stack.container + let _repositoryContext = _container.newBackgroundContext() + _repositoryContext.automaticallyMergesChangesFromParent = true + let _repository = CoreDataRepository(context: _repositoryContext) + self.init(container: _container, repositoryContext: _repositoryContext, repository: _repository) + + try await extraSetup() + } + + mutating func extraSetup() async throws { + // empty by default + } + + func mapInContext(_ input: I, transform: (I) throws -> O) throws -> O { + try repositoryContext.performAndWait { + try transform(input) + } + } + + func verify(_ item: T) async throws where T: FetchableUnmanagedModel, T: Equatable { + repositoryContext.performAndWait { + var managed: T.ManagedModel? + do { + managed = try repositoryContext.fetch(T.managedFetchRequest()).first { try T(managed: $0) == item } + } catch { + Issue.record( + "Failed to verify item in store because fetching failed. Error: \(error.localizedDescription)" + ) + return + } + + guard managed != nil else { + Issue.record("Failed to verify item in store because it was not found.") + return + } + } + } + + func verify(_ item: T) async throws where T: ReadableUnmanagedModel, T: Equatable { + try repositoryContext.performAndWait { + var _managed: T.ManagedModel? + do { + _managed = try item.readManaged(from: repositoryContext) + } catch { + Issue.record( + "Failed to verify item in store because reading it failed. Error: \(error.localizedDescription)" + ) + return + } + + guard let managed = _managed else { + Issue.record("Failed to verify item in store because it was not found.") + return + } + try expectNoDifference(item, T(managed: managed)) + } + } + + func verify(transactionAuthor: String?, timeStamp: Date) throws { + let historyRequest = NSPersistentHistoryChangeRequest.fetchHistory(after: timeStamp) + try repositoryContext.performAndWait { + let historyResult = try #require(repositoryContext.execute(historyRequest) as? NSPersistentHistoryResult) + let history = try #require(historyResult.result as? [NSPersistentHistoryTransaction]) + #expect(history.count > 0) + for historyTransaction in history { + #expect(historyTransaction.author == transactionAuthor) + } + } + } + + func verifyDoesNotExist(_ item: T) async throws where T: FetchableUnmanagedModel, T: Equatable { + repositoryContext.performAndWait { + var _managed: T.ManagedModel? + do { + _managed = try repositoryContext.fetch(T.managedFetchRequest()).first { try T(managed: $0) == item } + } catch { + return + } + + if let _managed, !_managed.isDeleted { + Issue.record("Item does exist and is not deleted which is not expected") + } + } + } + + func verifyDoesNotExist(_ item: T) async throws where T: ReadableUnmanagedModel, T: Equatable { + repositoryContext.performAndWait { + var _managed: T.ManagedModel? + do { + _managed = try item.readManaged(from: repositoryContext) + } catch { + return + } + + if let _managed, !_managed.isDeleted { + Issue.record("Item does exist and is not deleted which is not expected") + } + } + } + + func verifyDoesNotExist(transactionAuthor: String?, timeStamp: Date) throws { + let historyRequest = NSPersistentHistoryChangeRequest.fetchHistory(after: timeStamp) + try repositoryContext.performAndWait { + let historyResult = try #require(repositoryContext.execute(historyRequest) as? NSPersistentHistoryResult) + let history = try #require(historyResult.result as? [NSPersistentHistoryTransaction]) + if transactionAuthor == nil { + #expect(history.count == 0) + } else { + for historyTransaction in history { + #expect(historyTransaction.author != transactionAuthor) + } + } + } + } + + func delete(managedId: NSManagedObjectID) throws { + try repositoryContext.performAndWait { + let managed = repositoryContext.object(with: managedId) + repositoryContext.delete(managed) + try repositoryContext.save() + } + } +} diff --git a/Tests/CoreDataRepositoryTests/CoreDataTestSuiteTests.swift b/Tests/CoreDataRepositoryTests/CoreDataTestSuiteTests.swift new file mode 100644 index 0000000..1f22af9 --- /dev/null +++ b/Tests/CoreDataRepositoryTests/CoreDataTestSuiteTests.swift @@ -0,0 +1,270 @@ +// CoreDataTestSuiteTests.swift +// CoreDataRepository +// +// This source code is licensed under the MIT License (MIT) found in the +// LICENSE file in the root directory of this source tree. + +import CoreData +import CoreDataRepository +import Internal +import Testing + +extension CoreDataRepositoryTests { + @Suite + struct CoreDataTestSuiteTests: CoreDataTestSuite { + let container: NSPersistentContainer + let repositoryContext: NSManagedObjectContext + let repository: CoreDataRepository + + @Test + func verify_Fetchable_Success() async throws { + let modelType = FetchableModel_UuidId.self + let _value = modelType.seeded(1) + + let existingValue = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + + try await verify(existingValue) + } + + @Test + func verify_Fetchable_Failure() async throws { + let modelType = FetchableModel_UuidId.self + let _value = modelType.seeded(1) + + await withKnownIssue { + try await verify(_value) + } + } + + @Test + func verify_Readable_Success() async throws { + let modelType = IdentifiableModel_UuidId.self + let _value = modelType.seeded(1) + + let existingValue = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + + try await verify(existingValue) + } + + @Test + func verify_Readable_Failure() async throws { + let modelType = IdentifiableModel_UuidId.self + let _value = modelType.seeded(1) + + await withKnownIssue { + try await verify(_value) + } + } + + @Test + func verifyDoesNotExist_Fetchable_Success() async throws { + let modelType = FetchableModel_UuidId.self + let _value = modelType.seeded(1) + + try await verifyDoesNotExist(_value) + } + + @Test + func verifyDoesNotExist_Fetchable_Failure() async throws { + let modelType = FetchableModel_UuidId.self + let _value = modelType.seeded(1) + + let existingValue = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + + await withKnownIssue { + try await verifyDoesNotExist(existingValue) + } + } + + @Test + func verifyDoesNotExist_Readable_Success() async throws { + let modelType = IdentifiableModel_UuidId.self + let _value = modelType.seeded(1) + + try await verifyDoesNotExist(_value) + } + + @Test + func verifyDoesNotExist_Readable_Failure() async throws { + let modelType = IdentifiableModel_UuidId.self + let _value = modelType.seeded(1) + + let existingValue = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + + await withKnownIssue { + try await verifyDoesNotExist(existingValue) + } + } + + @Test + func verifyHistory_Success() async throws { + let modelType = FetchableModel_UuidId.self + let _value = modelType.seeded(1) + + let author: String = #function + let date = Date.now + + try await repositoryContext.perform(schedule: .immediate) { + repositoryContext.transactionAuthor = author + _ = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + } + + try verify(transactionAuthor: author, timeStamp: date) + } + + @Test + func verifyHistory_Failure_NoHistory() async throws { + let author: String = #function + let date = Date.now + + withKnownIssue { + try verify(transactionAuthor: author, timeStamp: date) + } + } + + @Test + func verifyHistory_Failure_WrongAuthor() async throws { + let modelType = FetchableModel_UuidId.self + let _value = modelType.seeded(1) + + let author: String = #function + let date = Date.now + + try await repositoryContext.perform(schedule: .immediate) { + repositoryContext.transactionAuthor = author + _ = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + } + + withKnownIssue { + try verify(transactionAuthor: "WRONG", timeStamp: date) + } + } + + @Test + func verifyHistory_Failure_WrongDate() async throws { + let modelType = FetchableModel_UuidId.self + let _value = modelType.seeded(1) + + let author: String = #function + + try await repositoryContext.perform(schedule: .immediate) { + repositoryContext.transactionAuthor = author + _ = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + } + + let date = Date.now + + withKnownIssue { + try verify(transactionAuthor: author, timeStamp: date) + } + } + + @Test + func verifyHistoryDoesNotExist_Success_Date() async throws { + let modelType = FetchableModel_UuidId.self + let _value = modelType.seeded(1) + + try await repositoryContext.perform(schedule: .immediate) { + _ = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + } + + let date = Date.now + + try verifyDoesNotExist(transactionAuthor: nil, timeStamp: date) + } + + @Test + func verifyHistoryDoesNotExist_Success_Author() async throws { + let modelType = FetchableModel_UuidId.self + let _value = modelType.seeded(1) + + let author: String = #function + let date = Date.now + + try await repositoryContext.perform(schedule: .immediate) { + repositoryContext.transactionAuthor = author + _ = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + } + + try verifyDoesNotExist(transactionAuthor: "WRONG_AUTHOR", timeStamp: date) + } + + @Test + func verifyHistoryDoesNotExist_Failure_Date() async throws { + let modelType = FetchableModel_UuidId.self + let _value = modelType.seeded(1) + + let date = Date.now + + try await repositoryContext.perform(schedule: .immediate) { + _ = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + } + + withKnownIssue { + try verifyDoesNotExist(transactionAuthor: nil, timeStamp: date) + } + } + + @Test + func verifyHistoryDoesNotExist_Failure_Author() async throws { + let modelType = FetchableModel_UuidId.self + let _value = modelType.seeded(1) + + let author: String = #function + let date = Date.now + + try await repositoryContext.perform(schedule: .immediate) { + repositoryContext.transactionAuthor = author + _ = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + } + + withKnownIssue { + try verifyDoesNotExist(transactionAuthor: author, timeStamp: date) + } + } + + init( + container: NSPersistentContainer, + repositoryContext: NSManagedObjectContext, + repository: CoreDataRepository + ) { + self.container = container + self.repositoryContext = repositoryContext + self.repository = repository + } + } +} diff --git a/Tests/CoreDataRepositoryTests/CoreDataXCTestCase.swift b/Tests/CoreDataRepositoryTests/CoreDataXCTestCase.swift deleted file mode 100644 index 8a0458b..0000000 --- a/Tests/CoreDataRepositoryTests/CoreDataXCTestCase.swift +++ /dev/null @@ -1,157 +0,0 @@ -// CoreDataXCTestCase.swift -// CoreDataRepository -// -// This source code is licensed under the MIT License (MIT) found in the -// LICENSE file in the root directory of this source tree. - -import CoreData -import CoreDataRepository -import CustomDump -import Internal -import XCTest - -class CoreDataXCTestCase: XCTestCase { - var _container: NSPersistentContainer? - var _repositoryContext: NSManagedObjectContext? - var _repository: CoreDataRepository? - let mainQueue = DispatchQueue.main - let backgroundQueue = DispatchQueue(label: "background", qos: .userInitiated) - - func container() throws -> NSPersistentContainer { - try XCTUnwrap(_container) - } - - func repositoryContext() throws -> NSManagedObjectContext { - try XCTUnwrap(_repositoryContext) - } - - func repository() throws -> CoreDataRepository { - try XCTUnwrap(_repository) - } - - override func setUpWithError() throws { - let stack = CoreDataStack( - storeName: "coredata_repository_tests", - type: .sqliteEphemeral, - container: CoreDataStack.persistentContainer( - storeName: "coredata_repository_tests", - type: .sqliteEphemeral, - model: .model_UuidId - ) - ) - let container = stack.container - _container = container - backgroundQueue.sync { - _repositoryContext = container.newBackgroundContext() - _repositoryContext?.automaticallyMergesChangesFromParent = true - } - _repository = try CoreDataRepository(context: repositoryContext()) - try super.setUpWithError() - } - - override func tearDownWithError() throws { - try super.tearDownWithError() - _container = nil - _repositoryContext = nil - _repository = nil - } - - func mapInContext(_ input: I, transform: (I) throws -> O) throws -> O { - try repositoryContext().performAndWait { - try transform(input) - } - } - - func verify(_ item: T) async throws where T: FetchableUnmanagedModel, T: Equatable { - let context = try repositoryContext() - context.performAndWait { - var managed: T.ManagedModel? - do { - managed = try context.fetch(T.managedFetchRequest()).first { try T(managed: $0) == item } - } catch { - XCTFail( - "Failed to verify item in store because fetching failed. Error: \(error.localizedDescription)" - ) - return - } - - guard managed != nil else { - XCTFail("Failed to verify item in store because it was not found.") - return - } - } - } - - func verify(_ item: T) async throws where T: ReadableUnmanagedModel, T: Equatable { - let context = try repositoryContext() - try context.performAndWait { - var _managed: T.ManagedModel? - do { - _managed = try item.readManaged(from: context) - } catch { - XCTFail( - "Failed to verify item in store because reading it failed. Error: \(error.localizedDescription)" - ) - return - } - - guard let managed = _managed else { - XCTFail("Failed to verify item in store because it was not found.") - return - } - try expectNoDifference(item, T(managed: managed)) - } - } - - 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) - for historyTransaction in history { - XCTAssertEqual(historyTransaction.author, transactionAuthor) - } - } - } - - func verifyDoesNotExist(_ item: T) async throws where T: FetchableUnmanagedModel, T: Equatable { - let context = try repositoryContext() - context.performAndWait { - var _managed: T.ManagedModel? - do { - _managed = try context.fetch(T.managedFetchRequest()).first { try T(managed: $0) == item } - } catch { - return - } - - if let _managed, !_managed.isDeleted { - XCTFail("Item does exist and is not deleted which is not expected") - } - } - } - - func verifyDoesNotExist(_ item: T) async throws where T: ReadableUnmanagedModel, T: Equatable { - let context = try repositoryContext() - context.performAndWait { - var _managed: T.ManagedModel? - do { - _managed = try item.readManaged(from: context) - } catch { - return - } - - if let _managed, !_managed.isDeleted { - XCTFail("Item does exist and is not deleted which is not expected") - } - } - } - - func delete(managedId: NSManagedObjectID) throws { - try repositoryContext().performAndWait { - let managed = try repositoryContext().object(with: managedId) - try repositoryContext().delete(managed) - try repositoryContext().save() - } - } -} diff --git a/Tests/CoreDataRepositoryTests/CreateTests.swift b/Tests/CoreDataRepositoryTests/CreateTests.swift index cb7923a..eb60dff 100644 --- a/Tests/CoreDataRepositoryTests/CreateTests.swift +++ b/Tests/CoreDataRepositoryTests/CreateTests.swift @@ -8,162 +8,187 @@ import CoreData import CoreDataRepository import CustomDump import Internal -import XCTest - -final class CreateTests: CoreDataXCTestCase { - func testCreate_Fetchable_Success() async throws { - let modelType = FetchableModel_UuidId.self - let historyTimeStamp = Date() - let transactionAuthor: String = #function - let _value = modelType.seeded(1) - let value = try await repository() - .create(_value, transactionAuthor: transactionAuthor).get() - expectNoDifference(value, _value) - - try await verify(value) - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } - - func testCreate_Fetchable_Failure() async throws { - let modelType = FetchableModel_UuidId.self - let _value = modelType.seeded(1) - let existingValue = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try modelType.init(managed: managed) +import Testing + +extension CoreDataRepositoryTests { + @Suite + struct CreateTests: CoreDataTestSuite { + let container: NSPersistentContainer + let repositoryContext: NSManagedObjectContext + let repository: CoreDataRepository + + @Test + func create_Fetchable_Success() async throws { + let modelType = FetchableModel_UuidId.self + let historyTimeStamp = Date() + let transactionAuthor: String = #function + let _value = modelType.seeded(1) + let value = try await repository + .create(_value, transactionAuthor: transactionAuthor).get() + expectNoDifference(value, _value) + + try await verify(value) + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) } - expectNoDifference(existingValue, _value) - - try await verify(existingValue) - let result = try await repository() - .create(_value) - - switch result { - case .success: - XCTFail("Not expecting success") - case let .failure(.cocoa(cocoaError)): - XCTAssertEqual(cocoaError.code, .managedObjectConstraintMerge) - case let .failure(error): - XCTFail("Unexpected error: \(error)") + @Test + func create_Fetchable_Failure() async throws { + let modelType = FetchableModel_UuidId.self + let _value = modelType.seeded(1) + let existingValue = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + expectNoDifference(existingValue, _value) + + try await verify(existingValue) + + let result = await repository + .create(_value) + + switch result { + case .success: + Issue.record("Not expecting success") + case let .failure(.cocoa(cocoaError)): + expectNoDifference(cocoaError.code, .managedObjectConstraintMerge) + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } } - } - func testCreate_Identifiable_Success() async throws { - let modelType = IdentifiableModel_UuidId.self - let historyTimeStamp = Date() - let transactionAuthor: String = #function - let _value = modelType.seeded(1) - let value = try await repository() - .create(_value, transactionAuthor: transactionAuthor).get() - expectNoDifference(value, _value) - - try await verify(value) - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } - - func testCreate_Identifiable_Failure() async throws { - let modelType = IdentifiableModel_UuidId.self - let _value = modelType.seeded(1) - let existingValue = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try modelType.init(managed: managed) + @Test + func create_Identifiable_Success() async throws { + let modelType = IdentifiableModel_UuidId.self + let historyTimeStamp = Date() + let transactionAuthor: String = #function + let _value = modelType.seeded(1) + let value = try await repository + .create(_value, transactionAuthor: transactionAuthor).get() + expectNoDifference(value, _value) + + try await verify(value) + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) } - expectNoDifference(existingValue, _value) - - try await verify(existingValue) - let result = try await repository() - .create(_value) - - switch result { - case .success: - XCTFail("Not expecting success") - case let .failure(.cocoa(cocoaError)): - XCTAssertEqual(cocoaError.code, .managedObjectConstraintMerge) - case let .failure(error): - XCTFail("Unexpected error: \(error)") + @Test + func create_Identifiable_Failure() async throws { + let modelType = IdentifiableModel_UuidId.self + let _value = modelType.seeded(1) + let existingValue = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + expectNoDifference(existingValue, _value) + + try await verify(existingValue) + + let result = await repository + .create(_value) + + switch result { + case .success: + Issue.record("Not expecting success") + case let .failure(.cocoa(cocoaError)): + expectNoDifference(cocoaError.code, .managedObjectConstraintMerge) + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } } - } - func testCreate_ManagedIdReferencable_Success() async throws { - let modelType = ManagedIdModel_UuidId.self - let historyTimeStamp = Date() - let transactionAuthor: String = #function - let _value = modelType.seeded(1) - let value = try await repository() - .create(_value, transactionAuthor: transactionAuthor).get() - expectNoDifference(value.removingManagedId(), _value) - - try await verify(value) - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } - - func testCreate_ManagedIdReferencable_Failure() async throws { - let modelType = ManagedIdModel_UuidId.self - let _value = modelType.seeded(1) - let existingValue = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try modelType.init(managed: managed) + @Test + func create_ManagedIdReferencable_Success() async throws { + let modelType = ManagedIdModel_UuidId.self + let historyTimeStamp = Date() + let transactionAuthor: String = #function + let _value = modelType.seeded(1) + let value = try await repository + .create(_value, transactionAuthor: transactionAuthor).get() + expectNoDifference(value.removingManagedId(), _value) + + try await verify(value) + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) } - expectNoDifference(existingValue.removingManagedId(), _value) - - try await verify(existingValue) - let result = try await repository() - .create(_value) - - switch result { - case .success: - XCTFail("Not expecting success") - case let .failure(.cocoa(cocoaError)): - XCTAssertEqual(cocoaError.code, .managedObjectConstraintMerge) - case let .failure(error): - XCTFail("Unexpected error: \(error)") + @Test + func create_ManagedIdReferencable_Failure() async throws { + let modelType = ManagedIdModel_UuidId.self + let _value = modelType.seeded(1) + let existingValue = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + expectNoDifference(existingValue.removingManagedId(), _value) + + try await verify(existingValue) + + let result = await repository + .create(_value) + + switch result { + case .success: + Issue.record("Not expecting success") + case let .failure(.cocoa(cocoaError)): + expectNoDifference(cocoaError.code, .managedObjectConstraintMerge) + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } } - } - func testCreate_ManagedIdUrlReferencable_Success() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - let historyTimeStamp = Date() - let transactionAuthor: String = #function - let _value = modelType.seeded(1) - let value = try await repository() - .create(_value, transactionAuthor: transactionAuthor).get() - expectNoDifference(value.removingManagedIdUrl(), _value) - - try await verify(value) - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } - - func testCreate_ManagedIdUrlReferencable_Failure() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - let _value = modelType.seeded(1) - let existingValue = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try modelType.init(managed: managed) + @Test + func create_ManagedIdUrlReferencable_Success() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + let historyTimeStamp = Date() + let transactionAuthor: String = #function + let _value = modelType.seeded(1) + let value = try await repository + .create(_value, transactionAuthor: transactionAuthor).get() + expectNoDifference(value.removingManagedIdUrl(), _value) + + try await verify(value) + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) } - expectNoDifference(existingValue.removingManagedIdUrl(), _value) - try await verify(existingValue) - - let result = try await repository() - .create(_value) + @Test + func create_ManagedIdUrlReferencable_Failure() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + let _value = modelType.seeded(1) + let existingValue = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + expectNoDifference(existingValue.removingManagedIdUrl(), _value) + + try await verify(existingValue) + + let result = await repository + .create(_value) + + switch result { + case .success: + Issue.record("Not expecting success") + case let .failure(.cocoa(cocoaError)): + expectNoDifference(cocoaError.code, .managedObjectConstraintMerge) + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } + } - switch result { - case .success: - XCTFail("Not expecting success") - case let .failure(.cocoa(cocoaError)): - XCTAssertEqual(cocoaError.code, .managedObjectConstraintMerge) - case let .failure(error): - XCTFail("Unexpected error: \(error)") + init( + container: NSPersistentContainer, + repositoryContext: NSManagedObjectContext, + repository: CoreDataRepository + ) { + self.container = container + self.repositoryContext = repositoryContext + self.repository = repository } } } diff --git a/Tests/CoreDataRepositoryTests/Create_BatchTests.swift b/Tests/CoreDataRepositoryTests/Create_BatchTests.swift index 13e7806..210a7bc 100644 --- a/Tests/CoreDataRepositoryTests/Create_BatchTests.swift +++ b/Tests/CoreDataRepositoryTests/Create_BatchTests.swift @@ -8,694 +8,727 @@ import CoreData import CoreDataRepository import CustomDump import Internal -import XCTest +import Testing + +extension CoreDataRepositoryTests { + @Suite + struct Create_BatchTests: CoreDataTestSuite { + let container: NSPersistentContainer + let repositoryContext: NSManagedObjectContext + let repository: CoreDataRepository + + @Test + func create_Fetchable_Success() async throws { + let modelType = FetchableModel_UuidId.self + + let fetchRequest = modelType.managedFetchRequest() + try await repositoryContext.perform { + let count = try repositoryContext.count(for: fetchRequest) + expectNoDifference(count, 0, "Count of objects in CoreData should be zero at the start of each test.") + } + + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] + + let historyTimeStamp = Date() + let transactionAuthor: String = #function + + let (successful, failed) = await repository + .create(_values, transactionAuthor: transactionAuthor) + + expectNoDifference(successful.count, _values.count) + expectNoDifference(failed.count, 0) + + for value in successful { + try await verify(value) + } + + try await repositoryContext.perform { + let data = try repositoryContext.fetch(fetchRequest) + expectNoDifference( + data.map(\.string).sorted(), + ["1", "2", "3", "4", "5"], + "Inserted titles should match expectation" + ) + } + + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) + } + + @Test + func create_Fetchable_Failure() async throws { + let modelType = FetchableModel_UuidId.self + + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] + + let fetchRequest = modelType.managedFetchRequest() + try await repositoryContext.perform { + let count = try repositoryContext.count(for: fetchRequest) + expectNoDifference(count, 0, "Count of objects in CoreData should be zero at the start of each test.") + + _ = try _values[0].asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + } + + let historyTimeStamp = Date() + let transactionAuthor: String = #function + + let (successful, failed) = await repository + .create(_values, transactionAuthor: transactionAuthor) + + expectNoDifference(successful.count, _values.count - 1) + expectNoDifference(failed.count, 1) + + for value in successful { + try await verify(value) + } + + try await repositoryContext.perform { + let data = try repositoryContext.fetch(fetchRequest) + expectNoDifference( + data.map(\.string).sorted(), + ["1", "2", "3", "4", "5"], + "Inserted titles should match expectation" + ) + } + + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) + } + + @Test + func create_Identifiable_Success() async throws { + let modelType = IdentifiableModel_UuidId.self + + let fetchRequest = modelType.managedFetchRequest() + try await repositoryContext.perform { + let count = try repositoryContext.count(for: fetchRequest) + expectNoDifference(count, 0, "Count of objects in CoreData should be zero at the start of each test.") + } + + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] + + let historyTimeStamp = Date() + let transactionAuthor: String = #function + + let (successful, failed) = await repository + .create(_values, transactionAuthor: transactionAuthor) + + expectNoDifference(successful.count, _values.count) + expectNoDifference(failed.count, 0) + + for value in successful { + try await verify(value) + } + + try await repositoryContext.perform { + let data = try repositoryContext.fetch(fetchRequest) + expectNoDifference( + data.map(\.string).sorted(), + ["1", "2", "3", "4", "5"], + "Inserted titles should match expectation" + ) + } + + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) + } + + @Test + func create_Identifiable_Failure() async throws { + let modelType = IdentifiableModel_UuidId.self + + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] + + let fetchRequest = modelType.managedFetchRequest() + try await repositoryContext.perform { + let count = try repositoryContext.count(for: fetchRequest) + expectNoDifference(count, 0, "Count of objects in CoreData should be zero at the start of each test.") + + _ = try _values[0].asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + } + + let historyTimeStamp = Date() + let transactionAuthor: String = #function + + let (successful, failed) = await repository + .create(_values, transactionAuthor: transactionAuthor) + + expectNoDifference(successful.count, _values.count - 1) + expectNoDifference(failed.count, 1) + + for value in successful { + try await verify(value) + } + + try await repositoryContext.perform { + let data = try repositoryContext.fetch(fetchRequest) + expectNoDifference( + data.map(\.string).sorted(), + ["1", "2", "3", "4", "5"], + "Inserted titles should match expectation" + ) + } + + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) + } + + @Test + func create_ManagedIdUrlReferencable_Success() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + + let fetchRequest = modelType.managedFetchRequest() + try await repositoryContext.perform { + let count = try repositoryContext.count(for: fetchRequest) + expectNoDifference(count, 0, "Count of objects in CoreData should be zero at the start of each test.") + } + + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] + + let historyTimeStamp = Date() + let transactionAuthor: String = #function + + let (successful, failed) = await repository + .create(_values, transactionAuthor: transactionAuthor) + + expectNoDifference(successful.count, _values.count) + expectNoDifference(failed.count, 0) + + for value in successful { + try await verify(value) + } + + expectNoDifference(successful.map { $0.removingManagedIdUrl() }.sorted(), _values) + + try await repositoryContext.perform { + let data = try repositoryContext.fetch(fetchRequest) + expectNoDifference( + data.map(\.string).sorted(), + ["1", "2", "3", "4", "5"], + "Inserted titles should match expectation" + ) + } + + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) + } + + @Test + func create_ManagedIdUrlReferencable_Failure() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] + + let fetchRequest = modelType.managedFetchRequest() + try await repositoryContext.perform { + let count = try repositoryContext.count(for: fetchRequest) + expectNoDifference(count, 0, "Count of objects in CoreData should be zero at the start of each test.") + + _ = try _values[0].asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + } + + let historyTimeStamp = Date() + let transactionAuthor: String = #function + + let (successful, failed) = await repository + .create(_values, transactionAuthor: transactionAuthor) + + expectNoDifference(successful.count, _values.count - 1) + expectNoDifference(failed.count, 1) + + for value in successful { + try await verify(value) + } + + try await repositoryContext.perform { + let data = try repositoryContext.fetch(fetchRequest) + expectNoDifference( + data.map(\.string).sorted(), + ["1", "2", "3", "4", "5"], + "Inserted titles should match expectation" + ) + } + + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) + } + + @Test + func create_ManagedIdReferencable_Success() async throws { + let modelType = ManagedIdModel_UuidId.self + + let fetchRequest = modelType.managedFetchRequest() + try await repositoryContext.perform { + let count = try repositoryContext.count(for: fetchRequest) + expectNoDifference(count, 0, "Count of objects in CoreData should be zero at the start of each test.") + } + + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] + + let historyTimeStamp = Date() + let transactionAuthor: String = #function + + let (successful, failed) = await repository + .create(_values, transactionAuthor: transactionAuthor) -final class Create_BatchTests: CoreDataXCTestCase { - func testCreate_Fetchable_Success() async throws { - let modelType = FetchableModel_UuidId.self - - let fetchRequest = modelType.managedFetchRequest() - try await repositoryContext().perform { - let count = try self.repositoryContext().count(for: fetchRequest) - XCTAssertEqual(count, 0, "Count of objects in CoreData should be zero at the start of each test.") - } - - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - - let historyTimeStamp = Date() - let transactionAuthor: String = #function - - let (successful, failed) = try await repository() - .create(_values, transactionAuthor: transactionAuthor) - - XCTAssertEqual(successful.count, _values.count) - XCTAssertEqual(failed.count, 0) - - for value in successful { - try await verify(value) - } - - try await repositoryContext().perform { - let data = try self.repositoryContext().fetch(fetchRequest) - XCTAssertEqual( - data.map(\.string).sorted(), - ["1", "2", "3", "4", "5"], - "Inserted titles should match expectation" - ) - } - - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } - - func testCreate_Fetchable_Failure() async throws { - let modelType = FetchableModel_UuidId.self - - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - - let fetchRequest = modelType.managedFetchRequest() - try await repositoryContext().perform { - let count = try self.repositoryContext().count(for: fetchRequest) - XCTAssertEqual(count, 0, "Count of objects in CoreData should be zero at the start of each test.") - - _ = try _values[0].asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - } - - let historyTimeStamp = Date() - let transactionAuthor: String = #function - - let (successful, failed) = try await repository() - .create(_values, transactionAuthor: transactionAuthor) - - XCTAssertEqual(successful.count, _values.count - 1) - XCTAssertEqual(failed.count, 1) - - for value in successful { - try await verify(value) - } - - try await repositoryContext().perform { - let data = try self.repositoryContext().fetch(fetchRequest) - XCTAssertEqual( - data.map(\.string).sorted(), - ["1", "2", "3", "4", "5"], - "Inserted titles should match expectation" - ) - } - - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } - - func testCreate_Identifiable_Success() async throws { - let modelType = IdentifiableModel_UuidId.self - - let fetchRequest = modelType.managedFetchRequest() - try await repositoryContext().perform { - let count = try self.repositoryContext().count(for: fetchRequest) - XCTAssertEqual(count, 0, "Count of objects in CoreData should be zero at the start of each test.") - } - - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - - let historyTimeStamp = Date() - let transactionAuthor: String = #function - - let (successful, failed) = try await repository() - .create(_values, transactionAuthor: transactionAuthor) - - XCTAssertEqual(successful.count, _values.count) - XCTAssertEqual(failed.count, 0) - - for value in successful { - try await verify(value) - } - - try await repositoryContext().perform { - let data = try self.repositoryContext().fetch(fetchRequest) - XCTAssertEqual( - data.map(\.string).sorted(), - ["1", "2", "3", "4", "5"], - "Inserted titles should match expectation" - ) - } - - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } - - func testCreate_Identifiable_Failure() async throws { - let modelType = IdentifiableModel_UuidId.self - - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - - let fetchRequest = modelType.managedFetchRequest() - try await repositoryContext().perform { - let count = try self.repositoryContext().count(for: fetchRequest) - XCTAssertEqual(count, 0, "Count of objects in CoreData should be zero at the start of each test.") - - _ = try _values[0].asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - } - - let historyTimeStamp = Date() - let transactionAuthor: String = #function - - let (successful, failed) = try await repository() - .create(_values, transactionAuthor: transactionAuthor) - - XCTAssertEqual(successful.count, _values.count - 1) - XCTAssertEqual(failed.count, 1) - - for value in successful { - try await verify(value) - } - - try await repositoryContext().perform { - let data = try self.repositoryContext().fetch(fetchRequest) - XCTAssertEqual( - data.map(\.string).sorted(), - ["1", "2", "3", "4", "5"], - "Inserted titles should match expectation" - ) - } - - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } - - func testCreate_ManagedIdUrlReferencable_Success() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - - let fetchRequest = modelType.managedFetchRequest() - try await repositoryContext().perform { - let count = try self.repositoryContext().count(for: fetchRequest) - XCTAssertEqual(count, 0, "Count of objects in CoreData should be zero at the start of each test.") - } - - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - - let historyTimeStamp = Date() - let transactionAuthor: String = #function - - let (successful, failed) = try await repository() - .create(_values, transactionAuthor: transactionAuthor) - - XCTAssertEqual(successful.count, _values.count) - XCTAssertEqual(failed.count, 0) - - for value in successful { - try await verify(value) - } - - expectNoDifference(successful.map { $0.removingManagedIdUrl() }.sorted(), _values) - - try await repositoryContext().perform { - let data = try self.repositoryContext().fetch(fetchRequest) - XCTAssertEqual( - data.map(\.string).sorted(), - ["1", "2", "3", "4", "5"], - "Inserted titles should match expectation" - ) - } - - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } - - func testCreate_ManagedIdUrlReferencable_Failure() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - - let fetchRequest = modelType.managedFetchRequest() - try await repositoryContext().perform { - let count = try self.repositoryContext().count(for: fetchRequest) - XCTAssertEqual(count, 0, "Count of objects in CoreData should be zero at the start of each test.") - - _ = try _values[0].asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - } - - let historyTimeStamp = Date() - let transactionAuthor: String = #function - - let (successful, failed) = try await repository() - .create(_values, transactionAuthor: transactionAuthor) - - XCTAssertEqual(successful.count, _values.count - 1) - XCTAssertEqual(failed.count, 1) - - for value in successful { - try await verify(value) - } - - try await repositoryContext().perform { - let data = try self.repositoryContext().fetch(fetchRequest) - XCTAssertEqual( - data.map(\.string).sorted(), - ["1", "2", "3", "4", "5"], - "Inserted titles should match expectation" - ) - } - - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } - - func testCreate_ManagedIdReferencable_Success() async throws { - let modelType = ManagedIdModel_UuidId.self - - let fetchRequest = modelType.managedFetchRequest() - try await repositoryContext().perform { - let count = try self.repositoryContext().count(for: fetchRequest) - XCTAssertEqual(count, 0, "Count of objects in CoreData should be zero at the start of each test.") - } - - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - - let historyTimeStamp = Date() - let transactionAuthor: String = #function - - let (successful, failed) = try await repository() - .create(_values, transactionAuthor: transactionAuthor) - - XCTAssertEqual(successful.count, _values.count) - XCTAssertEqual(failed.count, 0) - - for value in successful { - try await verify(value) - } - - expectNoDifference(successful.map { $0.removingManagedId() }.sorted(), _values) - - try await repositoryContext().perform { - let data = try self.repositoryContext().fetch(fetchRequest) - XCTAssertEqual( - data.map(\.string).sorted(), - ["1", "2", "3", "4", "5"], - "Inserted titles should match expectation" - ) - } - - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } - - func testCreate_ManagedIdReferencable_Failure() async throws { - let modelType = ManagedIdModel_UuidId.self - - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - - let fetchRequest = modelType.managedFetchRequest() - try await repositoryContext().perform { - let count = try self.repositoryContext().count(for: fetchRequest) - XCTAssertEqual(count, 0, "Count of objects in CoreData should be zero at the start of each test.") - - _ = try _values[0].asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - } - - let historyTimeStamp = Date() - let transactionAuthor: String = #function - - let (successful, failed) = try await repository() - .create(_values, transactionAuthor: transactionAuthor) - - XCTAssertEqual(successful.count, _values.count - 1) - XCTAssertEqual(failed.count, 1) - - for value in successful { - try await verify(value) - } - - try await repositoryContext().perform { - let data = try self.repositoryContext().fetch(fetchRequest) - XCTAssertEqual( - data.map(\.string).sorted(), - ["1", "2", "3", "4", "5"], - "Inserted titles should match expectation" - ) - } - - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } - - func testCreateAtomically_Fetchable_Success() async throws { - let modelType = FetchableModel_UuidId.self - - let fetchRequest = modelType.managedFetchRequest() - try await repositoryContext().perform { - let count = try self.repositoryContext().count(for: fetchRequest) - XCTAssertEqual(count, 0, "Count of objects in CoreData should be zero at the start of each test.") - } - - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - - let historyTimeStamp = Date() - let transactionAuthor: String = #function - - let createdValues = try await repository() - .createAtomically(_values, transactionAuthor: transactionAuthor).get() - - XCTAssertEqual(createdValues.count, _values.count) - - for value in createdValues { - try await verify(value) - } - - expectNoDifference(createdValues, _values) - - try await repositoryContext().perform { - let data = try self.repositoryContext().fetch(fetchRequest) - XCTAssertEqual( - data.map(\.string).sorted(), - ["1", "2", "3", "4", "5"], - "Inserted titles should match expectation" - ) - } - - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } - - func testCreateAtomically_Fetchable_Failure() async throws { - let modelType = FetchableModel_UuidId.self - - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - - let fetchRequest = modelType.managedFetchRequest() - let existingValue = try await repositoryContext().perform { - let count = try self.repositoryContext().count(for: fetchRequest) - XCTAssertEqual(count, 0, "Count of objects in CoreData should be zero at the start of each test.") - - let value = try _values[0].asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return value - } - try await verify(mapInContext(existingValue, transform: modelType.init(managed:))) - - let result = try await repository() - .createAtomically(_values) - - switch result { - case .success: - XCTFail("Not expecting success") - case let .failure(.cocoa(cocoaError)): - XCTAssertEqual(cocoaError.code, .managedObjectConstraintMerge) - case let .failure(error): - XCTFail("Unexpected error: \(error)") - } - - for value in _values[1 ... 4] { - try await verifyDoesNotExist(value) - } - } - - func testCreateAtomically_Identifiable_Success() async throws { - let modelType = IdentifiableModel_UuidId.self - - let fetchRequest = modelType.managedFetchRequest() - try await repositoryContext().perform { - let count = try self.repositoryContext().count(for: fetchRequest) - XCTAssertEqual(count, 0, "Count of objects in CoreData should be zero at the start of each test.") - } - - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - - let historyTimeStamp = Date() - let transactionAuthor: String = #function - - let createdValues = try await repository() - .createAtomically(_values, transactionAuthor: transactionAuthor).get() - - XCTAssertEqual(createdValues.count, _values.count) - - for value in createdValues { - try await verify(value) - } - - expectNoDifference(createdValues, _values) - - try await repositoryContext().perform { - let data = try self.repositoryContext().fetch(fetchRequest) - XCTAssertEqual( - data.map(\.string).sorted(), - ["1", "2", "3", "4", "5"], - "Inserted titles should match expectation" - ) - } - - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } - - func testCreateAtomically_Identifiable_Failure() async throws { - let modelType = IdentifiableModel_UuidId.self - - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - - let fetchRequest = modelType.managedFetchRequest() - let existingValue = try await repositoryContext().perform { - let count = try self.repositoryContext().count(for: fetchRequest) - XCTAssertEqual(count, 0, "Count of objects in CoreData should be zero at the start of each test.") - - let value = try _values[0].asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return value - } - try await verify(mapInContext(existingValue, transform: modelType.init(managed:))) - - let result = try await repository() - .createAtomically(_values) - - switch result { - case .success: - XCTFail("Not expecting success") - case let .failure(.cocoa(cocoaError)): - XCTAssertEqual(cocoaError.code, .managedObjectConstraintMerge) - case let .failure(error): - XCTFail("Unexpected error: \(error)") - } - - for value in _values[1 ... 4] { - try await verifyDoesNotExist(value) - } - } - - func testCreateAtomically_ManagedIdUrlReferencable_Success() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - - let fetchRequest = modelType.managedFetchRequest() - try await repositoryContext().perform { - let count = try self.repositoryContext().count(for: fetchRequest) - XCTAssertEqual(count, 0, "Count of objects in CoreData should be zero at the start of each test.") - } - - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - - let historyTimeStamp = Date() - let transactionAuthor: String = #function - - let createdValues = try await repository() - .createAtomically(_values, transactionAuthor: transactionAuthor).get() - - XCTAssertEqual(createdValues.count, _values.count) - - for value in createdValues { - try await verify(value) - } - - expectNoDifference(createdValues.map { $0.removingManagedIdUrl() }, _values) - - try await repositoryContext().perform { - let data = try self.repositoryContext().fetch(fetchRequest) - XCTAssertEqual( - data.map(\.string).sorted(), - ["1", "2", "3", "4", "5"], - "Inserted titles should match expectation" - ) - } - - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } - - func testCreateAtomically_ManagedIdUrlReferencable_Failure() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - - let fetchRequest = modelType.managedFetchRequest() - let existingValue = try await repositoryContext().perform { - let count = try self.repositoryContext().count(for: fetchRequest) - XCTAssertEqual(count, 0, "Count of objects in CoreData should be zero at the start of each test.") - - let value = try _values[0].asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return value - } - try await verify(mapInContext(existingValue, transform: modelType.init(managed:))) - - let result = try await repository() - .createAtomically(_values) - - switch result { - case .success: - XCTFail("Not expecting success") - case let .failure(.cocoa(cocoaError)): - XCTAssertEqual(cocoaError.code, .managedObjectConstraintMerge) - case let .failure(error): - XCTFail("Unexpected error: \(error)") - } - - for value in _values[1 ... 4] { - try await verifyDoesNotExist(value) - } - } - - func testCreateAtomically_ManagedIdReferencable_Success() async throws { - let modelType = ManagedIdModel_UuidId.self - - let fetchRequest = modelType.managedFetchRequest() - try await repositoryContext().perform { - let count = try self.repositoryContext().count(for: fetchRequest) - XCTAssertEqual(count, 0, "Count of objects in CoreData should be zero at the start of each test.") - } - - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - - let historyTimeStamp = Date() - let transactionAuthor: String = #function - - let createdValues = try await repository() - .createAtomically(_values, transactionAuthor: transactionAuthor).get() - - XCTAssertEqual(createdValues.count, _values.count) - - for value in createdValues { - try await verify(value) - } - - expectNoDifference(createdValues.map { $0.removingManagedId() }, _values) - - try await repositoryContext().perform { - let data = try self.repositoryContext().fetch(fetchRequest) - XCTAssertEqual( - data.map(\.string).sorted(), - ["1", "2", "3", "4", "5"], - "Inserted titles should match expectation" - ) - } - - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } - - func testCreateAtomically_ManagedIdReferencable_Failure() async throws { - let modelType = ManagedIdModel_UuidId.self - - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - - let fetchRequest = modelType.managedFetchRequest() - let existingValue = try await repositoryContext().perform { - let count = try self.repositoryContext().count(for: fetchRequest) - XCTAssertEqual(count, 0, "Count of objects in CoreData should be zero at the start of each test.") - - let value = try _values[0].asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return value - } - try await verify(mapInContext(existingValue, transform: modelType.init(managed:))) - - let result = try await repository() - .createAtomically(_values) - - switch result { - case .success: - XCTFail("Not expecting success") - case let .failure(.cocoa(cocoaError)): - XCTAssertEqual(cocoaError.code, .managedObjectConstraintMerge) - case let .failure(error): - XCTFail("Unexpected error: \(error)") - } - - for value in _values[1 ... 4] { - try await verifyDoesNotExist(value) + expectNoDifference(successful.count, _values.count) + expectNoDifference(failed.count, 0) + + for value in successful { + try await verify(value) + } + + expectNoDifference(successful.map { $0.removingManagedId() }.sorted(), _values) + + try await repositoryContext.perform { + let data = try repositoryContext.fetch(fetchRequest) + expectNoDifference( + data.map(\.string).sorted(), + ["1", "2", "3", "4", "5"], + "Inserted titles should match expectation" + ) + } + + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) + } + + @Test + func create_ManagedIdReferencable_Failure() async throws { + let modelType = ManagedIdModel_UuidId.self + + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] + + let fetchRequest = modelType.managedFetchRequest() + try await repositoryContext.perform { + let count = try repositoryContext.count(for: fetchRequest) + expectNoDifference(count, 0, "Count of objects in CoreData should be zero at the start of each test.") + + _ = try _values[0].asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + } + + let historyTimeStamp = Date() + let transactionAuthor: String = #function + + let (successful, failed) = await repository + .create(_values, transactionAuthor: transactionAuthor) + + expectNoDifference(successful.count, _values.count - 1) + expectNoDifference(failed.count, 1) + + for value in successful { + try await verify(value) + } + + try await repositoryContext.perform { + let data = try repositoryContext.fetch(fetchRequest) + expectNoDifference( + data.map(\.string).sorted(), + ["1", "2", "3", "4", "5"], + "Inserted titles should match expectation" + ) + } + + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) + } + + @Test + func createAtomically_Fetchable_Success() async throws { + let modelType = FetchableModel_UuidId.self + + let fetchRequest = modelType.managedFetchRequest() + try await repositoryContext.perform { + let count = try repositoryContext.count(for: fetchRequest) + expectNoDifference(count, 0, "Count of objects in CoreData should be zero at the start of each test.") + } + + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] + + let historyTimeStamp = Date() + let transactionAuthor: String = #function + + let createdValues = try await repository + .createAtomically(_values, transactionAuthor: transactionAuthor).get() + + expectNoDifference(createdValues.count, _values.count) + + for value in createdValues { + try await verify(value) + } + + expectNoDifference(createdValues, _values) + + try await repositoryContext.perform { + let data = try repositoryContext.fetch(fetchRequest) + expectNoDifference( + data.map(\.string).sorted(), + ["1", "2", "3", "4", "5"], + "Inserted titles should match expectation" + ) + } + + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) + } + + @Test + func createAtomically_Fetchable_Failure() async throws { + let modelType = FetchableModel_UuidId.self + + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] + + let fetchRequest = modelType.managedFetchRequest() + let existingValue = try await repositoryContext.perform { + let count = try repositoryContext.count(for: fetchRequest) + expectNoDifference(count, 0, "Count of objects in CoreData should be zero at the start of each test.") + + let value = try _values[0].asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return value + } + try await verify(mapInContext(existingValue, transform: modelType.init(managed:))) + + let result = await repository + .createAtomically(_values) + + switch result { + case .success: + Issue.record("Not expecting success") + case let .failure(.cocoa(cocoaError)): + expectNoDifference(cocoaError.code, .managedObjectConstraintMerge) + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } + + for value in _values[1 ... 4] { + try await verifyDoesNotExist(value) + } + } + + @Test + func createAtomically_Identifiable_Success() async throws { + let modelType = IdentifiableModel_UuidId.self + + let fetchRequest = modelType.managedFetchRequest() + try await repositoryContext.perform { + let count = try repositoryContext.count(for: fetchRequest) + expectNoDifference(count, 0, "Count of objects in CoreData should be zero at the start of each test.") + } + + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] + + let historyTimeStamp = Date() + let transactionAuthor: String = #function + + let createdValues = try await repository + .createAtomically(_values, transactionAuthor: transactionAuthor).get() + + expectNoDifference(createdValues.count, _values.count) + + for value in createdValues { + try await verify(value) + } + + expectNoDifference(createdValues, _values) + + try await repositoryContext.perform { + let data = try repositoryContext.fetch(fetchRequest) + expectNoDifference( + data.map(\.string).sorted(), + ["1", "2", "3", "4", "5"], + "Inserted titles should match expectation" + ) + } + + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) + } + + @Test + func createAtomically_Identifiable_Failure() async throws { + let modelType = IdentifiableModel_UuidId.self + + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] + + let fetchRequest = modelType.managedFetchRequest() + let existingValue = try await repositoryContext.perform { + let count = try repositoryContext.count(for: fetchRequest) + expectNoDifference(count, 0, "Count of objects in CoreData should be zero at the start of each test.") + + let value = try _values[0].asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return value + } + try await verify(mapInContext(existingValue, transform: modelType.init(managed:))) + + let result = await repository + .createAtomically(_values) + + switch result { + case .success: + Issue.record("Not expecting success") + case let .failure(.cocoa(cocoaError)): + expectNoDifference(cocoaError.code, .managedObjectConstraintMerge) + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } + + for value in _values[1 ... 4] { + try await verifyDoesNotExist(value) + } + } + + @Test + func createAtomically_ManagedIdUrlReferencable_Success() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + + let fetchRequest = modelType.managedFetchRequest() + try await repositoryContext.perform { + let count = try repositoryContext.count(for: fetchRequest) + expectNoDifference(count, 0, "Count of objects in CoreData should be zero at the start of each test.") + } + + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] + + let historyTimeStamp = Date() + let transactionAuthor: String = #function + + let createdValues = try await repository + .createAtomically(_values, transactionAuthor: transactionAuthor).get() + + expectNoDifference(createdValues.count, _values.count) + + for value in createdValues { + try await verify(value) + } + + expectNoDifference(createdValues.map { $0.removingManagedIdUrl() }, _values) + + try await repositoryContext.perform { + let data = try repositoryContext.fetch(fetchRequest) + expectNoDifference( + data.map(\.string).sorted(), + ["1", "2", "3", "4", "5"], + "Inserted titles should match expectation" + ) + } + + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) + } + + @Test + func createAtomically_ManagedIdUrlReferencable_Failure() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] + + let fetchRequest = modelType.managedFetchRequest() + let existingValue = try await repositoryContext.perform { + let count = try repositoryContext.count(for: fetchRequest) + expectNoDifference(count, 0, "Count of objects in CoreData should be zero at the start of each test.") + + let value = try _values[0].asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return value + } + try await verify(mapInContext(existingValue, transform: modelType.init(managed:))) + + let result = await repository + .createAtomically(_values) + + switch result { + case .success: + Issue.record("Not expecting success") + case let .failure(.cocoa(cocoaError)): + expectNoDifference(cocoaError.code, .managedObjectConstraintMerge) + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } + + for value in _values[1 ... 4] { + try await verifyDoesNotExist(value) + } + } + + @Test + func createAtomically_ManagedIdReferencable_Success() async throws { + let modelType = ManagedIdModel_UuidId.self + + let fetchRequest = modelType.managedFetchRequest() + try await repositoryContext.perform { + let count = try repositoryContext.count(for: fetchRequest) + expectNoDifference(count, 0, "Count of objects in CoreData should be zero at the start of each test.") + } + + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] + + let historyTimeStamp = Date() + let transactionAuthor: String = #function + + let createdValues = try await repository + .createAtomically(_values, transactionAuthor: transactionAuthor).get() + + expectNoDifference(createdValues.count, _values.count) + + for value in createdValues { + try await verify(value) + } + + expectNoDifference(createdValues.map { $0.removingManagedId() }, _values) + + try await repositoryContext.perform { + let data = try repositoryContext.fetch(fetchRequest) + expectNoDifference( + data.map(\.string).sorted(), + ["1", "2", "3", "4", "5"], + "Inserted titles should match expectation" + ) + } + + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) + } + + @Test + func createAtomically_ManagedIdReferencable_Failure() async throws { + let modelType = ManagedIdModel_UuidId.self + + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] + + let fetchRequest = modelType.managedFetchRequest() + let existingValue = try await repositoryContext.perform { + let count = try repositoryContext.count(for: fetchRequest) + expectNoDifference(count, 0, "Count of objects in CoreData should be zero at the start of each test.") + + let value = try _values[0].asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return value + } + try await verify(mapInContext(existingValue, transform: modelType.init(managed:))) + + let result = await repository + .createAtomically(_values) + + switch result { + case .success: + Issue.record("Not expecting success") + case let .failure(.cocoa(cocoaError)): + expectNoDifference(cocoaError.code, .managedObjectConstraintMerge) + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } + + for value in _values[1 ... 4] { + try await verifyDoesNotExist(value) + } + } + + init( + container: NSPersistentContainer, + repositoryContext: NSManagedObjectContext, + repository: CoreDataRepository + ) { + self.container = container + self.repositoryContext = repositoryContext + self.repository = repository } } } diff --git a/Tests/CoreDataRepositoryTests/DeleteTests.swift b/Tests/CoreDataRepositoryTests/DeleteTests.swift index a334daa..f670f22 100644 --- a/Tests/CoreDataRepositoryTests/DeleteTests.swift +++ b/Tests/CoreDataRepositoryTests/DeleteTests.swift @@ -8,272 +8,301 @@ import CoreData import CoreDataRepository import CustomDump import Internal -import XCTest - -final class DeleteTests: CoreDataXCTestCase { - func testDelete_Identifiable_Success() async throws { - let modelType = IdentifiableModel_UuidId.self - let transactionAuthor: String = #function - let _value = modelType.seeded(1) - let existingValue = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try modelType.init(managed: managed) +import Testing + +extension CoreDataRepositoryTests { + @Suite + struct DeleteTests: CoreDataTestSuite { + let container: NSPersistentContainer + let repositoryContext: NSManagedObjectContext + let repository: CoreDataRepository + + @Test + func delete_Identifiable_Success() async throws { + let modelType = IdentifiableModel_UuidId.self + let transactionAuthor: String = #function + let _value = modelType.seeded(1) + let existingValue = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + expectNoDifference(existingValue, _value) + + try await verify(existingValue) + + _ = try await repository + .delete(_value, transactionAuthor: transactionAuthor).get() } - expectNoDifference(existingValue, _value) - try await verify(existingValue) - - _ = try await repository() - .delete(_value, transactionAuthor: transactionAuthor).get() - } - - func testDelete_Identifiable_Failure() async throws { - let modelType = IdentifiableModel_UuidId.self - let _value = modelType.seeded(1) - let result = try await repository() - .delete(_value) - - switch result { - case .success: - XCTFail("Not expecting success") - case .failure(.noMatchFoundWhenReadingItem): - return - case let .failure(error): - XCTFail("Unexpected error: \(error)") + @Test + func delete_Identifiable_Failure() async throws { + let modelType = IdentifiableModel_UuidId.self + let _value = modelType.seeded(1) + let result = await repository + .delete(_value) + + switch result { + case .success: + Issue.record("Not expecting success") + case .failure(.noMatchFoundWhenReadingItem): + return + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } } - } - func testDelete_ManagedIdReferencable_Success() async throws { - let modelType = ManagedIdModel_UuidId.self - let transactionAuthor: String = #function - let _value = modelType.seeded(1) - let existingValue = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try modelType.init(managed: managed) + @Test + func delete_ManagedIdReferencable_Success() async throws { + let modelType = ManagedIdModel_UuidId.self + let transactionAuthor: String = #function + let _value = modelType.seeded(1) + let existingValue = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + expectNoDifference(existingValue.removingManagedId(), _value) + + try await verify(existingValue) + + _ = await repository + .delete(existingValue, transactionAuthor: transactionAuthor) } - expectNoDifference(existingValue.removingManagedId(), _value) - - try await verify(existingValue) - - _ = try await repository() - .delete(existingValue, transactionAuthor: transactionAuthor) - } - func testDelete_ManagedIdReferencable_Failure() async throws { - let modelType = ManagedIdModel_UuidId.self + @Test + func delete_ManagedIdReferencable_Failure() async throws { + let modelType = ManagedIdModel_UuidId.self - let _value = try await repositoryContext().perform(schedule: .immediate) { - let managed = try modelType.seeded(1).asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() + let _value = try await repositoryContext.perform(schedule: .immediate) { + let managed = try modelType.seeded(1).asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() - try self.repositoryContext().obtainPermanentIDs(for: [managed]) - let value = try modelType.init(managed: managed) + try repositoryContext.obtainPermanentIDs(for: [managed]) + let value = try modelType.init(managed: managed) - try self.repositoryContext().delete(managed) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() + repositoryContext.delete(managed) + try repositoryContext.save() + try repositoryContext.parent?.save() - return value - } + return value + } - let result = try await repository() - .delete(_value) + let result = await repository + .delete(_value) - switch result { - case .success: - XCTFail("Not expecting success") - case let .failure(.cocoa(cocoaError)): - XCTAssertEqual(cocoaError.code, .managedObjectReferentialIntegrity) - case let .failure(error): - XCTFail("Unexpected error: \(error)") + switch result { + case .success: + Issue.record("Not expecting success") + case let .failure(.cocoa(cocoaError)): + expectNoDifference(cocoaError.code, .managedObjectReferentialIntegrity) + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } } - } - func testDelete_ManagedIdReferencable_NoManagedId_Failure() async throws { - let modelType = ManagedIdModel_UuidId.self + @Test + func delete_ManagedIdReferencable_NoManagedId_Failure() async throws { + let modelType = ManagedIdModel_UuidId.self - let _value = modelType.seeded(1) + let _value = modelType.seeded(1) - let result = try await repository() - .delete(_value) + let result = await repository + .delete(_value) - switch result { - case .success: - XCTFail("Not expecting success") - case .failure(.noObjectIdOnItem): - return - case let .failure(error): - XCTFail("Unexpected error: \(error)") + switch result { + case .success: + Issue.record("Not expecting success") + case .failure(.noObjectIdOnItem): + return + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } } - } - func testDelete_ManagedId_Success() async throws { - let modelType = ManagedIdModel_UuidId.self - let transactionAuthor: String = #function - let _value = modelType.seeded(1) - let existingValue = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try modelType.init(managed: managed) + @Test + func delete_ManagedId_Success() async throws { + let modelType = ManagedIdModel_UuidId.self + let transactionAuthor: String = #function + let _value = modelType.seeded(1) + let existingValue = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + expectNoDifference(existingValue.removingManagedId(), _value) + + try await verify(existingValue) + + _ = try await repository + .delete(#require(existingValue.managedId), transactionAuthor: transactionAuthor) } - expectNoDifference(existingValue.removingManagedId(), _value) - try await verify(existingValue) + @Test + func delete_ManagedId_Failure() async throws { + let modelType = ManagedIdModel_UuidId.self - _ = try await repository() - .delete(XCTUnwrap(existingValue.managedId), transactionAuthor: transactionAuthor) - } + let _value = try await repositoryContext.perform(schedule: .immediate) { + let managed = try modelType.seeded(1).asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() - func testDelete_ManagedId_Failure() async throws { - let modelType = ManagedIdModel_UuidId.self + try repositoryContext.obtainPermanentIDs(for: [managed]) + let value = try modelType.init(managed: managed) - let _value = try await repositoryContext().perform(schedule: .immediate) { - let managed = try modelType.seeded(1).asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() + repositoryContext.delete(managed) + try repositoryContext.save() + try repositoryContext.parent?.save() - try self.repositoryContext().obtainPermanentIDs(for: [managed]) - let value = try modelType.init(managed: managed) + return value + } - try self.repositoryContext().delete(managed) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() + let result = try await repository + .delete(#require(_value.managedId)) - return value + switch result { + case .success: + Issue.record("Not expecting success") + case let .failure(.cocoa(cocoaError)): + expectNoDifference(cocoaError.code, .managedObjectReferentialIntegrity) + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } } - let result = try await repository() - .delete(XCTUnwrap(_value.managedId)) - - switch result { - case .success: - XCTFail("Not expecting success") - case let .failure(.cocoa(cocoaError)): - XCTAssertEqual(cocoaError.code, .managedObjectReferentialIntegrity) - case let .failure(error): - XCTFail("Unexpected error: \(error)") + @Test + func delete_ManagedIdUrlReferencable_Success() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + let transactionAuthor: String = #function + let _value = modelType.seeded(1) + let existingValue = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + expectNoDifference(existingValue.removingManagedIdUrl(), _value) + + try await verify(existingValue) + + _ = await repository + .delete(existingValue, transactionAuthor: transactionAuthor) } - } - - func testDelete_ManagedIdUrlReferencable_Success() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - let transactionAuthor: String = #function - let _value = modelType.seeded(1) - let existingValue = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try modelType.init(managed: managed) - } - expectNoDifference(existingValue.removingManagedIdUrl(), _value) - try await verify(existingValue) + @Test + func delete_ManagedIdUrlReferencable_Failure() async throws { + let modelType = ManagedIdUrlModel_UuidId.self - _ = try await repository() - .delete(existingValue, transactionAuthor: transactionAuthor) - } + let _value = try await repositoryContext.perform(schedule: .immediate) { + let managed = try modelType.seeded(1).asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() - func testDelete_ManagedIdUrlReferencable_Failure() async throws { - let modelType = ManagedIdUrlModel_UuidId.self + try repositoryContext.obtainPermanentIDs(for: [managed]) + let value = try modelType.init(managed: managed) - let _value = try await repositoryContext().perform(schedule: .immediate) { - let managed = try modelType.seeded(1).asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() + repositoryContext.delete(managed) + try repositoryContext.save() + try repositoryContext.parent?.save() - try self.repositoryContext().obtainPermanentIDs(for: [managed]) - let value = try modelType.init(managed: managed) + return value + } - try self.repositoryContext().delete(managed) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() + let result = await repository + .delete(_value) - return value + switch result { + case .success: + Issue.record("Not expecting success") + case let .failure(.cocoa(cocoaError)): + expectNoDifference(cocoaError.code, .managedObjectReferentialIntegrity) + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } } - let result = try await repository() - .delete(_value) + @Test + func delete_ManagedIdUrlReferencable_NoManagedIdUrl_Failure() async throws { + let modelType = ManagedIdUrlModel_UuidId.self - switch result { - case .success: - XCTFail("Not expecting success") - case let .failure(.cocoa(cocoaError)): - XCTAssertEqual(cocoaError.code, .managedObjectReferentialIntegrity) - case let .failure(error): - XCTFail("Unexpected error: \(error)") - } - } + let _value = modelType.seeded(1) - func testDelete_ManagedIdUrlReferencable_NoManagedIdUrl_Failure() async throws { - let modelType = ManagedIdUrlModel_UuidId.self + let result = await repository + .delete(_value) - let _value = modelType.seeded(1) - - let result = try await repository() - .delete(_value) - - switch result { - case .success: - XCTFail("Not expecting success") - case .failure(.noUrlOnItemToMapToObjectId): - return - case let .failure(error): - XCTFail("Unexpected error: \(error)") + switch result { + case .success: + Issue.record("Not expecting success") + case .failure(.noUrlOnItemToMapToObjectId): + return + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } } - } - func testDelete_ManagedIdUrl_Success() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - let transactionAuthor: String = #function - let _value = modelType.seeded(1) - let existingValue = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try modelType.init(managed: managed) + @Test + func delete_ManagedIdUrl_Success() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + let transactionAuthor: String = #function + let _value = modelType.seeded(1) + let existingValue = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + expectNoDifference(existingValue.removingManagedIdUrl(), _value) + + try await verify(existingValue) + + _ = try await repository + .delete(#require(existingValue.managedIdUrl), transactionAuthor: transactionAuthor) } - expectNoDifference(existingValue.removingManagedIdUrl(), _value) - try await verify(existingValue) + @Test + func delete_ManagedIdUrl_Failure() async throws { + let modelType = ManagedIdUrlModel_UuidId.self - _ = try await repository() - .delete(XCTUnwrap(existingValue.managedIdUrl), transactionAuthor: transactionAuthor) - } + let _value = try await repositoryContext.perform(schedule: .immediate) { + let managed = try modelType.seeded(1).asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() - func testDelete_ManagedIdUrl_Failure() async throws { - let modelType = ManagedIdUrlModel_UuidId.self + try repositoryContext.obtainPermanentIDs(for: [managed]) + let value = try modelType.init(managed: managed) - let _value = try await repositoryContext().perform(schedule: .immediate) { - let managed = try modelType.seeded(1).asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() + repositoryContext.delete(managed) + try repositoryContext.save() + try repositoryContext.parent?.save() - try self.repositoryContext().obtainPermanentIDs(for: [managed]) - let value = try modelType.init(managed: managed) + return value + } - try self.repositoryContext().delete(managed) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() + let result = try await repository + .delete(#require(_value.managedIdUrl)) - return value + switch result { + case .success: + Issue.record("Not expecting success") + case let .failure(.cocoa(cocoaError)): + expectNoDifference(cocoaError.code, .managedObjectReferentialIntegrity) + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } } - let result = try await repository() - .delete(XCTUnwrap(_value.managedIdUrl)) - - switch result { - case .success: - XCTFail("Not expecting success") - case let .failure(.cocoa(cocoaError)): - XCTAssertEqual(cocoaError.code, .managedObjectReferentialIntegrity) - case let .failure(error): - XCTFail("Unexpected error: \(error)") + init( + container: NSPersistentContainer, + repositoryContext: NSManagedObjectContext, + repository: CoreDataRepository + ) { + self.container = container + self.repositoryContext = repositoryContext + self.repository = repository } } } diff --git a/Tests/CoreDataRepositoryTests/Delete_BatchTests.swift b/Tests/CoreDataRepositoryTests/Delete_BatchTests.swift index 7fed4ca..aa96f1d 100644 --- a/Tests/CoreDataRepositoryTests/Delete_BatchTests.swift +++ b/Tests/CoreDataRepositoryTests/Delete_BatchTests.swift @@ -8,783 +8,824 @@ import CoreData import CoreDataRepository import CustomDump import Internal -import XCTest - -final class Delete_BatchTests: CoreDataXCTestCase { - // MARK: Non Atomic - - func testDelete_Identifiable_Success() async throws { - let modelType = IdentifiableModel_UuidId.self - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - let existingValues = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try _values.map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try manageds.map { try modelType.init(managed: $0) } - } - expectNoDifference(existingValues, _values) - - for value in existingValues { - try await verify(value) - } - - let historyTimeStamp = Date() - let transactionAuthor: String = #function - - let (successful, failed) = try await repository() - .delete(existingValues, transactionAuthor: transactionAuthor) - - XCTAssertEqual(successful.count, _values.count) - XCTAssertEqual(failed.count, 0) - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } +import Testing - func testDelete_Identifiable_Failure() async throws { - let modelType = IdentifiableModel_UuidId.self - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - let (successful, failed) = try await repository() - .delete(_values) - - XCTAssertEqual(successful.count, 0) - XCTAssertEqual(failed.count, _values.count) - } - - func testDelete_ManagedIdReferencable_Success() async throws { - let modelType = ManagedIdModel_UuidId.self - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - let existingValues = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try _values.map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try manageds.map { try modelType.init(managed: $0) } - } - expectNoDifference(existingValues.map { $0.removingManagedId() }, _values) +extension CoreDataRepositoryTests { + @Suite + struct Delete_BatchTests: CoreDataTestSuite { + let container: NSPersistentContainer + let repositoryContext: NSManagedObjectContext + let repository: CoreDataRepository - for value in existingValues { - try await verify(value) - } + // MARK: Non Atomic - let historyTimeStamp = Date() - let transactionAuthor: String = #function - - let (successful, failed) = try await repository() - .delete(existingValues, transactionAuthor: transactionAuthor) - - XCTAssertEqual(successful.count, _values.count) - XCTAssertEqual(failed.count, 0) - for value in existingValues { - try await verifyDoesNotExist(value) - } - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } - - func testDelete_ManagedIdReferencable_Failure() async throws { - let modelType = ManagedIdModel_UuidId.self - - let _values = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try [ + @Test + func delete_Identifiable_Success() async throws { + let modelType = IdentifiableModel_UuidId.self + let _values = [ modelType.seeded(1), modelType.seeded(2), modelType.seeded(3), modelType.seeded(4), modelType.seeded(5), - ].map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - - try self.repositoryContext().obtainPermanentIDs(for: manageds) - let values = try manageds.map { try modelType.init(managed: $0) } - - try self.repositoryContext().delete(manageds[0]) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - - return values - } - - let (successful, failed) = try await repository() - .delete(_values) - - XCTAssertEqual(successful.count, _values.count - 1) - XCTAssertEqual(failed.count, 1) - for value in _values[1 ... 4] { - try await verifyDoesNotExist(value) - } - } - - func testDelete_ManagedIdReferencable_NoManagedId_Failure() async throws { - let modelType = ManagedIdModel_UuidId.self - - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - - let (successful, failed) = try await repository() - .delete(_values) - - XCTAssertEqual(successful.count, 0) - XCTAssertEqual(failed.count, _values.count) - } - - func testDelete_ManagedId_Success() async throws { - let modelType = ManagedIdModel_UuidId.self - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - let existingValues = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try _values.map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try manageds.map { try modelType.init(managed: $0) } - } - expectNoDifference(existingValues.map { $0.removingManagedId() }, _values) - - for value in existingValues { - try await verify(value) - } - - let historyTimeStamp = Date() - let transactionAuthor: String = #function - - let (successful, failed) = try await repository() - .delete(existingValues.compactMap(\.managedId), transactionAuthor: transactionAuthor) - - XCTAssertEqual(successful.count, _values.count) - XCTAssertEqual(failed.count, 0) - for value in existingValues { - try await verifyDoesNotExist(value) - } - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } - - func testDelete_ManagedId_Failure() async throws { - let modelType = ManagedIdModel_UuidId.self - - let _values = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try [ + ] + let existingValues = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try _values.map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + return try manageds.map { try modelType.init(managed: $0) } + } + expectNoDifference(existingValues, _values) + + for value in existingValues { + try await verify(value) + } + + let historyTimeStamp = Date() + let transactionAuthor: String = #function + + let (successful, failed) = await repository + .delete(existingValues, transactionAuthor: transactionAuthor) + + expectNoDifference(successful.count, _values.count) + expectNoDifference(failed.count, 0) + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) + } + + @Test + func delete_Identifiable_Failure() async throws { + let modelType = IdentifiableModel_UuidId.self + let _values = [ modelType.seeded(1), modelType.seeded(2), modelType.seeded(3), modelType.seeded(4), modelType.seeded(5), - ].map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - - try self.repositoryContext().obtainPermanentIDs(for: manageds) - let values = try manageds.map { try modelType.init(managed: $0) } - - try self.repositoryContext().delete(manageds[0]) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - - return values - } - - for value in _values[1 ... 4] { - try await verify(value) - } - try await verifyDoesNotExist(_values[0]) - - let (successful, failed) = try await repository() - .delete(_values.map { try XCTUnwrap($0.managedId) }) - - XCTAssertEqual(successful.count, _values.count - 1) - XCTAssertEqual(failed.count, 1) - for value in _values[1 ... 4] { - try await verifyDoesNotExist(value) - } - } - - func testDelete_ManagedIdUrlReferencable_Success() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - let existingValues = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try _values.map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try manageds.map { try modelType.init(managed: $0) } - } - expectNoDifference(existingValues.map { $0.removingManagedIdUrl() }, _values) - - for value in existingValues { - try await verify(value) - } - - let historyTimeStamp = Date() - let transactionAuthor: String = #function + ] + let (successful, failed) = await repository + .delete(_values) - let (successful, failed) = try await repository() - .delete(existingValues, transactionAuthor: transactionAuthor) - - XCTAssertEqual(successful.count, _values.count) - XCTAssertEqual(failed.count, 0) - for value in existingValues { - try await verifyDoesNotExist(value) + expectNoDifference(successful.count, 0) + expectNoDifference(failed.count, _values.count) } - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } - func testDelete_ManagedIdUrlReferencable_Failure() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - - let _values = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try [ + @Test + func delete_ManagedIdReferencable_Success() async throws { + let modelType = ManagedIdModel_UuidId.self + let _values = [ modelType.seeded(1), modelType.seeded(2), modelType.seeded(3), modelType.seeded(4), modelType.seeded(5), - ].map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - - try self.repositoryContext().obtainPermanentIDs(for: manageds) - let values = try manageds.map { try modelType.init(managed: $0) } - - try self.repositoryContext().delete(manageds[0]) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - - return values - } - - let (successful, failed) = try await repository() - .delete(_values) - - XCTAssertEqual(successful.count, _values.count - 1) - XCTAssertEqual(failed.count, 1) - for value in _values { - try await verifyDoesNotExist(value) - } - } - - func testDelete_ManagedIdUrlReferencable_NoManagedIdUrl_Failure() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - - let (successful, failed) = try await repository() - .delete(_values) - - XCTAssertEqual(successful.count, 0) - XCTAssertEqual(failed.count, _values.count) - } - - func testDelete_ManagedIdUrl_Success() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - let existingValues = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try _values.map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try manageds.map { try modelType.init(managed: $0) } - } - expectNoDifference(existingValues.map { $0.removingManagedIdUrl() }, _values) - - for value in existingValues { - try await verify(value) - } - - let historyTimeStamp = Date() - let transactionAuthor: String = #function - - let (successful, failed) = try await repository() - .delete(existingValues.compactMap(\.managedIdUrl), transactionAuthor: transactionAuthor) - - XCTAssertEqual(successful.count, _values.count) - XCTAssertEqual(failed.count, 0) - for value in existingValues { - try await verifyDoesNotExist(value) - } - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } - - func testDelete_ManagedIdUrl_Failure() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - - let _values = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try [ + ] + let existingValues = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try _values.map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + return try manageds.map { try modelType.init(managed: $0) } + } + expectNoDifference(existingValues.map { $0.removingManagedId() }, _values) + + for value in existingValues { + try await verify(value) + } + + let historyTimeStamp = Date() + let transactionAuthor: String = #function + + let (successful, failed) = await repository + .delete(existingValues, transactionAuthor: transactionAuthor) + + expectNoDifference(successful.count, _values.count) + expectNoDifference(failed.count, 0) + for value in existingValues { + try await verifyDoesNotExist(value) + } + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) + } + + @Test + func delete_ManagedIdReferencable_Failure() async throws { + let modelType = ManagedIdModel_UuidId.self + + let _values = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ].map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + + try repositoryContext.obtainPermanentIDs(for: manageds) + let values = try manageds.map { try modelType.init(managed: $0) } + + repositoryContext.delete(manageds[0]) + try repositoryContext.save() + try repositoryContext.parent?.save() + + return values + } + + let (successful, failed) = await repository + .delete(_values) + + expectNoDifference(successful.count, _values.count - 1) + expectNoDifference(failed.count, 1) + for value in _values[1 ... 4] { + try await verifyDoesNotExist(value) + } + } + + @Test + func delete_ManagedIdReferencable_NoManagedId_Failure() async throws { + let modelType = ManagedIdModel_UuidId.self + + let _values = [ modelType.seeded(1), modelType.seeded(2), modelType.seeded(3), modelType.seeded(4), modelType.seeded(5), - ].map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - - try self.repositoryContext().obtainPermanentIDs(for: manageds) - let values = try manageds.map { try modelType.init(managed: $0) } - - try self.repositoryContext().delete(manageds[0]) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - - return values - } - - let (successful, failed) = try await repository() - .delete(_values.map { try XCTUnwrap($0.managedIdUrl) }) + ] - XCTAssertEqual(successful.count, _values.count - 1) - XCTAssertEqual(failed.count, 1) - for value in _values[1 ... 4] { - try await verifyDoesNotExist(value) - } - } - - // MARK: Atomic - - func testDeleteAtomically_Identifiable_Success() async throws { - let modelType = IdentifiableModel_UuidId.self - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - let existingValues = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try _values.map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try manageds.map { try modelType.init(managed: $0) } - } - expectNoDifference(existingValues, _values) + let (successful, failed) = await repository + .delete(_values) - for value in existingValues { - try await verify(value) + expectNoDifference(successful.count, 0) + expectNoDifference(failed.count, _values.count) } - let historyTimeStamp = Date() - let transactionAuthor: String = #function - - try await repository() - .deleteAtomically(existingValues, transactionAuthor: transactionAuthor).get() - - for value in existingValues { - try await verifyDoesNotExist(value) - } - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } - - func testDeleteAtomically_Identifiable_Failure() async throws { - let modelType = IdentifiableModel_UuidId.self - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - let result = try await repository() - .deleteAtomically(_values) - - switch result { - case .success: - XCTFail("Not expecting success") - case .failure(.noMatchFoundWhenReadingItem): - break - case let .failure(error): - XCTFail("Unexpected error: \(error)") - } - } - - func testDeleteAtomically_ManagedIdReferencable_Success() async throws { - let modelType = ManagedIdModel_UuidId.self - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - let existingValues = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try _values.map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try manageds.map { try modelType.init(managed: $0) } - } - expectNoDifference(existingValues.map { $0.removingManagedId() }, _values) - - for value in existingValues { - try await verify(value) - } - - let historyTimeStamp = Date() - let transactionAuthor: String = #function - - try await repository() - .deleteAtomically(existingValues, transactionAuthor: transactionAuthor).get() - - for value in existingValues { - try await verifyDoesNotExist(value) - } - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } - - func testDeleteAtomically_ManagedIdReferencable_Failure() async throws { - let modelType = ManagedIdModel_UuidId.self - - let _values = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try [ + @Test + func delete_ManagedId_Success() async throws { + let modelType = ManagedIdModel_UuidId.self + let _values = [ modelType.seeded(1), modelType.seeded(2), modelType.seeded(3), modelType.seeded(4), modelType.seeded(5), - ].map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - - try self.repositoryContext().obtainPermanentIDs(for: manageds) - let values = try manageds.map { try modelType.init(managed: $0) } - - try self.repositoryContext().delete(manageds[0]) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - - return values - } - - let result = try await repository() - .deleteAtomically(_values) - - switch result { - case .success: - XCTFail("Not expecting success") - case let .failure(.cocoa(cocoaError)): - XCTAssertEqual(cocoaError.code, .managedObjectReferentialIntegrity) - case let .failure(error): - XCTFail("Unexpected error: \(error)") - } - - for value in _values[1 ... 4] { - try await verify(value) - } - try await verifyDoesNotExist(_values[0]) - } - - func testDeleteAtomically_ManagedIdReferencable_NoManagedId_Failure() async throws { - let modelType = ManagedIdModel_UuidId.self - - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - - let result = try await repository() - .deleteAtomically(_values) - - switch result { - case .success: - XCTFail("Not expecting success") - case .failure(.noObjectIdOnItem): - break - case let .failure(error): - XCTFail("Unexpected error: \(error)") - } - } - - func testDeleteAtomically_ManagedId_Success() async throws { - let modelType = ManagedIdModel_UuidId.self - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - let existingValues = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try _values.map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try manageds.map { try modelType.init(managed: $0) } - } - expectNoDifference(existingValues.map { $0.removingManagedId() }, _values) - - for value in existingValues { - try await verify(value) - } - - let historyTimeStamp = Date() - let transactionAuthor: String = #function - - try await repository() - .deleteAtomically(existingValues.compactMap(\.managedId), transactionAuthor: transactionAuthor).get() - - for value in existingValues { - try await verifyDoesNotExist(value) - } - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } - - func testDeleteAtomically_ManagedId_Failure() async throws { - let modelType = ManagedIdModel_UuidId.self - - let _values = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try [ + ] + let existingValues = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try _values.map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + return try manageds.map { try modelType.init(managed: $0) } + } + expectNoDifference(existingValues.map { $0.removingManagedId() }, _values) + + for value in existingValues { + try await verify(value) + } + + let historyTimeStamp = Date() + let transactionAuthor: String = #function + + let (successful, failed) = await repository + .delete(existingValues.compactMap(\.managedId), transactionAuthor: transactionAuthor) + + expectNoDifference(successful.count, _values.count) + expectNoDifference(failed.count, 0) + for value in existingValues { + try await verifyDoesNotExist(value) + } + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) + } + + @Test + func delete_ManagedId_Failure() async throws { + let modelType = ManagedIdModel_UuidId.self + + let _values = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ].map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + + try repositoryContext.obtainPermanentIDs(for: manageds) + let values = try manageds.map { try modelType.init(managed: $0) } + + repositoryContext.delete(manageds[0]) + try repositoryContext.save() + try repositoryContext.parent?.save() + + return values + } + + for value in _values[1 ... 4] { + try await verify(value) + } + try await verifyDoesNotExist(_values[0]) + + let (successful, failed) = try await repository + .delete(_values.map { try #require($0.managedId) }) + + expectNoDifference(successful.count, _values.count - 1) + expectNoDifference(failed.count, 1) + for value in _values[1 ... 4] { + try await verifyDoesNotExist(value) + } + } + + @Test + func delete_ManagedIdUrlReferencable_Success() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + let _values = [ modelType.seeded(1), modelType.seeded(2), modelType.seeded(3), modelType.seeded(4), modelType.seeded(5), - ].map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - - try self.repositoryContext().obtainPermanentIDs(for: manageds) - let values = try manageds.map { try modelType.init(managed: $0) } - - try self.repositoryContext().delete(manageds[0]) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - - return values - } - - for value in _values[1 ... 4] { - try await verify(value) - } - try await verifyDoesNotExist(_values[0]) - - let result = try await repository() - .deleteAtomically(_values.map { try XCTUnwrap($0.managedId) }) - - switch result { - case .success: - XCTFail("Not expecting success") - case let .failure(.cocoa(cocoaError)): - XCTAssertEqual(cocoaError.code, .managedObjectReferentialIntegrity) - case let .failure(error): - XCTFail("Unexpected error: \(error)") - } - - for value in _values[1 ... 4] { - try await verify(value) - } - try await verifyDoesNotExist(_values[0]) - } - - func testDeleteAtomically_ManagedIdUrlReferencable_Success() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - let existingValues = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try _values.map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try manageds.map { try modelType.init(managed: $0) } - } - expectNoDifference(existingValues.map { $0.removingManagedIdUrl() }, _values) - - for value in existingValues { - try await verify(value) - } - - let historyTimeStamp = Date() - let transactionAuthor: String = #function - - try await repository() - .deleteAtomically(existingValues, transactionAuthor: transactionAuthor).get() - - for value in existingValues { - try await verifyDoesNotExist(value) - } - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } - - func testDeleteAtomically_ManagedIdUrlReferencable_Failure() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - - let _values = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try [ + ] + let existingValues = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try _values.map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + return try manageds.map { try modelType.init(managed: $0) } + } + expectNoDifference(existingValues.map { $0.removingManagedIdUrl() }, _values) + + for value in existingValues { + try await verify(value) + } + + let historyTimeStamp = Date() + let transactionAuthor: String = #function + + let (successful, failed) = await repository + .delete(existingValues, transactionAuthor: transactionAuthor) + + expectNoDifference(successful.count, _values.count) + expectNoDifference(failed.count, 0) + for value in existingValues { + try await verifyDoesNotExist(value) + } + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) + } + + @Test + func delete_ManagedIdUrlReferencable_Failure() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + + let _values = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ].map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + + try repositoryContext.obtainPermanentIDs(for: manageds) + let values = try manageds.map { try modelType.init(managed: $0) } + + repositoryContext.delete(manageds[0]) + try repositoryContext.save() + try repositoryContext.parent?.save() + + return values + } + + let (successful, failed) = await repository + .delete(_values) + + expectNoDifference(successful.count, _values.count - 1) + expectNoDifference(failed.count, 1) + for value in _values { + try await verifyDoesNotExist(value) + } + } + + @Test + func delete_ManagedIdUrlReferencable_NoManagedIdUrl_Failure() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + + let _values = [ modelType.seeded(1), modelType.seeded(2), modelType.seeded(3), modelType.seeded(4), modelType.seeded(5), - ].map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - - try self.repositoryContext().obtainPermanentIDs(for: manageds) - let values = try manageds.map { try modelType.init(managed: $0) } + ] - try self.repositoryContext().delete(manageds[0]) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() + let (successful, failed) = await repository + .delete(_values) - return values + expectNoDifference(successful.count, 0) + expectNoDifference(failed.count, _values.count) } - let result = try await repository() - .deleteAtomically(_values) - - switch result { - case .success: - XCTFail("Not expecting success") - case let .failure(.cocoa(cocoaError)): - XCTAssertEqual(cocoaError.code, .managedObjectReferentialIntegrity) - case let .failure(error): - XCTFail("Unexpected error: \(error)") - } - - for value in _values[1 ... 4] { - try await verify(value) - } - try await verifyDoesNotExist(_values[0]) - } - - func testDeleteAtomically_ManagedIdUrlReferencable_NoManagedIdUrl_Failure() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - - let result = try await repository() - .deleteAtomically(_values) - - switch result { - case .success: - XCTFail("Not expecting success") - case .failure(.noUrlOnItemToMapToObjectId): - break - case let .failure(error): - XCTFail("Unexpected error: \(error)") - } - } - - func testDeleteAtomically_ManagedIdUrl_Success() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - let existingValues = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try _values.map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try manageds.map { try modelType.init(managed: $0) } - } - expectNoDifference(existingValues.map { $0.removingManagedIdUrl() }, _values) - - for value in existingValues { - try await verify(value) - } - - let historyTimeStamp = Date() - let transactionAuthor: String = #function + @Test + func delete_ManagedIdUrl_Success() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] + let existingValues = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try _values.map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + return try manageds.map { try modelType.init(managed: $0) } + } + expectNoDifference(existingValues.map { $0.removingManagedIdUrl() }, _values) + + for value in existingValues { + try await verify(value) + } + + let historyTimeStamp = Date() + let transactionAuthor: String = #function + + let (successful, failed) = await repository + .delete(existingValues.compactMap(\.managedIdUrl), transactionAuthor: transactionAuthor) + + expectNoDifference(successful.count, _values.count) + expectNoDifference(failed.count, 0) + for value in existingValues { + try await verifyDoesNotExist(value) + } + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) + } + + @Test + func delete_ManagedIdUrl_Failure() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + + let _values = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ].map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + + try repositoryContext.obtainPermanentIDs(for: manageds) + let values = try manageds.map { try modelType.init(managed: $0) } + + repositoryContext.delete(manageds[0]) + try repositoryContext.save() + try repositoryContext.parent?.save() + + return values + } + + let (successful, failed) = try await repository + .delete(_values.map { try #require($0.managedIdUrl) }) + + expectNoDifference(successful.count, _values.count - 1) + expectNoDifference(failed.count, 1) + for value in _values[1 ... 4] { + try await verifyDoesNotExist(value) + } + } + + // MARK: Atomic + + @Test + func deleteAtomically_Identifiable_Success() async throws { + let modelType = IdentifiableModel_UuidId.self + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] + let existingValues = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try _values.map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + return try manageds.map { try modelType.init(managed: $0) } + } + expectNoDifference(existingValues, _values) + + for value in existingValues { + try await verify(value) + } + + let historyTimeStamp = Date() + let transactionAuthor: String = #function + + try await repository + .deleteAtomically(existingValues, transactionAuthor: transactionAuthor).get() + + for value in existingValues { + try await verifyDoesNotExist(value) + } + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) + } + + @Test + func deleteAtomically_Identifiable_Failure() async throws { + let modelType = IdentifiableModel_UuidId.self + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] + let result = await repository + .deleteAtomically(_values) + + switch result { + case .success: + Issue.record("Not expecting success") + case .failure(.noMatchFoundWhenReadingItem): + break + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } + } + + @Test + func deleteAtomically_ManagedIdReferencable_Success() async throws { + let modelType = ManagedIdModel_UuidId.self + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] + let existingValues = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try _values.map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + return try manageds.map { try modelType.init(managed: $0) } + } + expectNoDifference(existingValues.map { $0.removingManagedId() }, _values) + + for value in existingValues { + try await verify(value) + } + + let historyTimeStamp = Date() + let transactionAuthor: String = #function + + try await repository + .deleteAtomically(existingValues, transactionAuthor: transactionAuthor).get() + + for value in existingValues { + try await verifyDoesNotExist(value) + } + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) + } + + @Test + func deleteAtomically_ManagedIdReferencable_Failure() async throws { + let modelType = ManagedIdModel_UuidId.self + + let _values = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ].map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + + try repositoryContext.obtainPermanentIDs(for: manageds) + let values = try manageds.map { try modelType.init(managed: $0) } + + repositoryContext.delete(manageds[0]) + try repositoryContext.save() + try repositoryContext.parent?.save() + + return values + } + + let result = await repository + .deleteAtomically(_values) + + switch result { + case .success: + Issue.record("Not expecting success") + case let .failure(.cocoa(cocoaError)): + expectNoDifference(cocoaError.code, .managedObjectReferentialIntegrity) + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } + + for value in _values[1 ... 4] { + try await verify(value) + } + try await verifyDoesNotExist(_values[0]) + } + + @Test + func deleteAtomically_ManagedIdReferencable_NoManagedId_Failure() async throws { + let modelType = ManagedIdModel_UuidId.self + + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] - try await repository() - .deleteAtomically(existingValues.compactMap(\.managedIdUrl), transactionAuthor: transactionAuthor).get() + let result = await repository + .deleteAtomically(_values) - for value in existingValues { - try await verifyDoesNotExist(value) + switch result { + case .success: + Issue.record("Not expecting success") + case .failure(.noObjectIdOnItem): + break + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } } - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } - - func testDeleteAtomically_ManagedIdUrl_Failure() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - let _values = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try [ + @Test + func deleteAtomically_ManagedId_Success() async throws { + let modelType = ManagedIdModel_UuidId.self + let _values = [ modelType.seeded(1), modelType.seeded(2), modelType.seeded(3), modelType.seeded(4), modelType.seeded(5), - ].map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - - try self.repositoryContext().obtainPermanentIDs(for: manageds) - let values = try manageds.map { try modelType.init(managed: $0) } - - try self.repositoryContext().delete(manageds[0]) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - - return values - } + ] + let existingValues = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try _values.map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + return try manageds.map { try modelType.init(managed: $0) } + } + expectNoDifference(existingValues.map { $0.removingManagedId() }, _values) + + for value in existingValues { + try await verify(value) + } + + let historyTimeStamp = Date() + let transactionAuthor: String = #function + + try await repository + .deleteAtomically(existingValues.compactMap(\.managedId), transactionAuthor: transactionAuthor).get() + + for value in existingValues { + try await verifyDoesNotExist(value) + } + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) + } + + @Test + func deleteAtomically_ManagedId_Failure() async throws { + let modelType = ManagedIdModel_UuidId.self + + let _values = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ].map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + + try repositoryContext.obtainPermanentIDs(for: manageds) + let values = try manageds.map { try modelType.init(managed: $0) } + + repositoryContext.delete(manageds[0]) + try repositoryContext.save() + try repositoryContext.parent?.save() + + return values + } + + for value in _values[1 ... 4] { + try await verify(value) + } + try await verifyDoesNotExist(_values[0]) + + let result = try await repository + .deleteAtomically(_values.map { try #require($0.managedId) }) + + switch result { + case .success: + Issue.record("Not expecting success") + case let .failure(.cocoa(cocoaError)): + expectNoDifference(cocoaError.code, .managedObjectReferentialIntegrity) + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } + + for value in _values[1 ... 4] { + try await verify(value) + } + try await verifyDoesNotExist(_values[0]) + } + + @Test + func deleteAtomically_ManagedIdUrlReferencable_Success() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] + let existingValues = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try _values.map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + return try manageds.map { try modelType.init(managed: $0) } + } + expectNoDifference(existingValues.map { $0.removingManagedIdUrl() }, _values) + + for value in existingValues { + try await verify(value) + } + + let historyTimeStamp = Date() + let transactionAuthor: String = #function + + try await repository + .deleteAtomically(existingValues, transactionAuthor: transactionAuthor).get() + + for value in existingValues { + try await verifyDoesNotExist(value) + } + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) + } + + @Test + func deleteAtomically_ManagedIdUrlReferencable_Failure() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + + let _values = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ].map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + + try repositoryContext.obtainPermanentIDs(for: manageds) + let values = try manageds.map { try modelType.init(managed: $0) } + + repositoryContext.delete(manageds[0]) + try repositoryContext.save() + try repositoryContext.parent?.save() + + return values + } + + let result = await repository + .deleteAtomically(_values) + + switch result { + case .success: + Issue.record("Not expecting success") + case let .failure(.cocoa(cocoaError)): + expectNoDifference(cocoaError.code, .managedObjectReferentialIntegrity) + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } + + for value in _values[1 ... 4] { + try await verify(value) + } + try await verifyDoesNotExist(_values[0]) + } + + @Test + func deleteAtomically_ManagedIdUrlReferencable_NoManagedIdUrl_Failure() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] - let result = try await repository() - .deleteAtomically(_values.map { try XCTUnwrap($0.managedIdUrl) }) + let result = await repository + .deleteAtomically(_values) - switch result { - case .success: - XCTFail("Not expecting success") - case let .failure(.cocoa(cocoaError)): - XCTAssertEqual(cocoaError.code, .managedObjectReferentialIntegrity) - case let .failure(error): - XCTFail("Unexpected error: \(error)") + switch result { + case .success: + Issue.record("Not expecting success") + case .failure(.noUrlOnItemToMapToObjectId): + break + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } } - for value in _values[1 ... 4] { - try await verify(value) + @Test + func deleteAtomically_ManagedIdUrl_Success() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] + let existingValues = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try _values.map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + return try manageds.map { try modelType.init(managed: $0) } + } + expectNoDifference(existingValues.map { $0.removingManagedIdUrl() }, _values) + + for value in existingValues { + try await verify(value) + } + + let historyTimeStamp = Date() + let transactionAuthor: String = #function + + try await repository + .deleteAtomically(existingValues.compactMap(\.managedIdUrl), transactionAuthor: transactionAuthor).get() + + for value in existingValues { + try await verifyDoesNotExist(value) + } + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) + } + + @Test + func deleteAtomically_ManagedIdUrl_Failure() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + + let _values = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ].map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + + try repositoryContext.obtainPermanentIDs(for: manageds) + let values = try manageds.map { try modelType.init(managed: $0) } + + repositoryContext.delete(manageds[0]) + try repositoryContext.save() + try repositoryContext.parent?.save() + + return values + } + + let result = try await repository + .deleteAtomically(_values.map { try #require($0.managedIdUrl) }) + + switch result { + case .success: + Issue.record("Not expecting success") + case let .failure(.cocoa(cocoaError)): + expectNoDifference(cocoaError.code, .managedObjectReferentialIntegrity) + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } + + for value in _values[1 ... 4] { + try await verify(value) + } + try await verifyDoesNotExist(_values[0]) + } + + init( + container: NSPersistentContainer, + repositoryContext: NSManagedObjectContext, + repository: CoreDataRepository + ) { + self.container = container + self.repositoryContext = repositoryContext + self.repository = repository } - try await verifyDoesNotExist(_values[0]) } } diff --git a/Tests/CoreDataRepositoryTests/FetchTests.swift b/Tests/CoreDataRepositoryTests/FetchTests.swift index ae2e32c..4c17db1 100644 --- a/Tests/CoreDataRepositoryTests/FetchTests.swift +++ b/Tests/CoreDataRepositoryTests/FetchTests.swift @@ -6,116 +6,138 @@ import CoreData import CoreDataRepository +import CustomDump import Internal -import XCTest +import Testing -final class FetchRepositoryTests: CoreDataXCTestCase { - let fetchRequest: NSFetchRequest = { - let request = FetchableModel_UuidId.managedFetchRequest() - request.sortDescriptors = [NSSortDescriptor(keyPath: \ManagedModel_UuidId.int, ascending: true)] - return request - }() +extension CoreDataRepositoryTests { + @Suite + struct FetchRepositoryTests: CoreDataTestSuite, @unchecked Sendable { + let container: NSPersistentContainer + let repositoryContext: NSManagedObjectContext + let repository: CoreDataRepository - let values = [ - FetchableModel_UuidId.seeded(1), - FetchableModel_UuidId.seeded(2), - FetchableModel_UuidId.seeded(3), - FetchableModel_UuidId.seeded(4), - FetchableModel_UuidId.seeded(5), - ] - var expectedValues = [FetchableModel_UuidId]() - var objectIds = [NSManagedObjectID]() + let fetchRequest: NSFetchRequest = { + let request = FetchableModel_UuidId.managedFetchRequest() + request.sortDescriptors = [NSSortDescriptor(keyPath: \ManagedModel_UuidId.int, ascending: true)] + return request + }() - override func setUpWithError() throws { - try super.setUpWithError() - let (_expectedValues, _objectIds) = try repositoryContext().performAndWait { - let managedMovies = try self.values - .map { - try ManagedIdUrlModel_UuidId(fetchable: $0) - .asManagedModel(in: repositoryContext()) - } - try self.repositoryContext().save() - return try ( - self.repositoryContext().fetch(fetchRequest).map(FetchableModel_UuidId.init(managed:)), - managedMovies.map(\.objectID) - ) - } - expectedValues = _expectedValues - objectIds = _objectIds - } + let values = [ + FetchableModel_UuidId.seeded(1), + FetchableModel_UuidId.seeded(2), + FetchableModel_UuidId.seeded(3), + FetchableModel_UuidId.seeded(4), + FetchableModel_UuidId.seeded(5), + ] + var expectedValues = [FetchableModel_UuidId]() + var objectIds = [NSManagedObjectID]() - override func tearDownWithError() throws { - try super.tearDownWithError() - expectedValues = [] - objectIds = [] - } + mutating func extraSetup() async throws { + let (_expectedValues, _objectIds) = try repositoryContext.performAndWait { + let managedMovies = try values + .map { + try ManagedIdUrlModel_UuidId(fetchable: $0) + .asManagedModel(in: repositoryContext) + } + try repositoryContext.save() + return try ( + repositoryContext.fetch(fetchRequest).map(FetchableModel_UuidId.init(managed:)), + managedMovies.map(\.objectID) + ) + } + expectedValues = _expectedValues + objectIds = _objectIds + } - func testFetchSuccess() async throws { - switch try await repository().fetch(fetchRequest, as: FetchableModel_UuidId.self) { - case let .success(values): - XCTAssertEqual(values.count, 5, "Result items count should match expectation") - XCTAssertEqual(values, expectedValues, "Result items should match expectations") - case .failure: - XCTFail("Not expecting failure") + @Test + func fetchSuccess() async throws { + switch await repository.fetch(fetchRequest, as: FetchableModel_UuidId.self) { + case let .success(values): + expectNoDifference(values.count, 5, "Result items count should match expectation") + expectNoDifference(values, expectedValues, "Result items should match expectations") + case .failure: + Issue.record("Not expecting failure") + } } - } - func testFetchSubscriptionSuccess() async throws { - let task = Task { - var resultCount = 0 - let stream = try repository() - .fetchSubscription(fetchRequest, of: FetchableModel_UuidId.self) - for await _items in stream { - let items = try _items.get() - resultCount += 1 - switch resultCount { - case 1: - XCTAssertEqual(items.count, 5, "Result items count should match expectation") - XCTAssertEqual(items, self.expectedValues, "Result items should match expectations") - try delete(managedId: XCTUnwrap(objectIds.last)) - await Task.yield() - case 2: - XCTAssertEqual(items.count, 4, "Result items count should match expectation") - XCTAssertEqual(items, Array(self.expectedValues[0 ... 3]), "Result items should match expectations") - return resultCount - default: - XCTFail("Not expecting any values past the first two.") - return resultCount + @Test + func fetchSubscriptionSuccess() async throws { + let task = Task { + var resultCount = 0 + let stream = repository + .fetchSubscription(fetchRequest, of: FetchableModel_UuidId.self) + for await _items in stream { + let items = try _items.get() + resultCount += 1 + switch resultCount { + case 1: + expectNoDifference(items.count, 5, "Result items count should match expectation") + expectNoDifference(items, expectedValues, "Result items should match expectations") + try delete(managedId: #require(objectIds.last)) + await Task.yield() + case 2: + expectNoDifference(items.count, 4, "Result items count should match expectation") + expectNoDifference( + items, + Array(expectedValues[0 ... 3]), + "Result items should match expectations" + ) + return resultCount + default: + Issue.record("Not expecting any values past the first two.") + return resultCount + } } + return resultCount } - return resultCount + let finalCount = try await task.value + expectNoDifference(finalCount, 2) } - let finalCount = try await task.value - XCTAssertEqual(finalCount, 2) - } - func testFetchThrowingSubscriptionSuccess() async throws { - let task = Task { - var resultCount = 0 - let stream = try repository().fetchThrowingSubscription( - self.fetchRequest, - of: FetchableModel_UuidId.self - ) - for try await items in stream { - resultCount += 1 - switch resultCount { - case 1: - XCTAssertEqual(items.count, 5, "Result items count should match expectation") - XCTAssertEqual(items, self.expectedValues, "Result items should match expectations") - try delete(managedId: XCTUnwrap(objectIds.last)) - await Task.yield() - case 2: - XCTAssertEqual(items.count, 4, "Result items count should match expectation") - XCTAssertEqual(items, Array(self.expectedValues[0 ... 3]), "Result items should match expectations") - return resultCount - default: - XCTFail("Not expecting any values past the first two.") - return resultCount + @Test + func fetchThrowingSubscriptionSuccess() async throws { + let task = Task { + var resultCount = 0 + let stream = repository.fetchThrowingSubscription( + fetchRequest, + of: FetchableModel_UuidId.self + ) + for try await items in stream { + resultCount += 1 + switch resultCount { + case 1: + expectNoDifference(items.count, 5, "Result items count should match expectation") + expectNoDifference(items, expectedValues, "Result items should match expectations") + try delete(managedId: #require(objectIds.last)) + await Task.yield() + case 2: + expectNoDifference(items.count, 4, "Result items count should match expectation") + expectNoDifference( + items, + Array(expectedValues[0 ... 3]), + "Result items should match expectations" + ) + return resultCount + default: + Issue.record("Not expecting any values past the first two.") + return resultCount + } } + return resultCount } - return resultCount + let finalCount = try await task.value + expectNoDifference(finalCount, 2) + } + + init( + container: NSPersistentContainer, + repositoryContext: NSManagedObjectContext, + repository: CoreDataRepository + ) { + self.container = container + self.repositoryContext = repositoryContext + self.repository = repository } - let finalCount = try await task.value - XCTAssertEqual(finalCount, 2) } } diff --git a/Tests/CoreDataRepositoryTests/ReadTests.swift b/Tests/CoreDataRepositoryTests/ReadTests.swift index 51a064d..b30be60 100644 --- a/Tests/CoreDataRepositoryTests/ReadTests.swift +++ b/Tests/CoreDataRepositoryTests/ReadTests.swift @@ -8,702 +8,745 @@ import CoreData import CoreDataRepository import CustomDump import Internal -import XCTest - -final class ReadTests: CoreDataXCTestCase { - func testRead_Identifiable_Success() async throws { - let modelType = IdentifiableModel_UuidId.self - let _value = modelType.seeded(1) - let existingValue = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try modelType.init(managed: managed) +import Testing + +extension CoreDataRepositoryTests { + @Suite + struct ReadTests: CoreDataTestSuite { + let container: NSPersistentContainer + let repositoryContext: NSManagedObjectContext + let repository: CoreDataRepository + + @Test + func read_Identifiable_Success() async throws { + let modelType = IdentifiableModel_UuidId.self + let _value = modelType.seeded(1) + let existingValue = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + expectNoDifference(existingValue, _value) + + try await verify(existingValue) + + let value = try await repository + .read(existingValue).get() + + expectNoDifference(value, existingValue) + } + + @Test + func read_Identifiable_Failure() async throws { + let modelType = IdentifiableModel_UuidId.self + let _value = modelType.seeded(1) + let result = await repository + .read(_value) + + switch result { + case .success: + Issue.record("Not expecting success") + case .failure(.noMatchFoundWhenReadingItem): + return + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } + } + + @Test + func read_Identifiable_ById_Success() async throws { + let modelType = IdentifiableModel_UuidId.self + let _value = modelType.seeded(1) + let existingValue = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + expectNoDifference(existingValue, _value) + + try await verify(existingValue) + + _ = try await repository + .read(existingValue.id, of: modelType).get() + } + + @Test + func read_Identifiable_ById_Failure() async throws { + let modelType = IdentifiableModel_UuidId.self + let _value = modelType.seeded(1) + let result = await repository + .read(_value.id, of: modelType) + + switch result { + case .success: + Issue.record("Not expecting success") + case .failure(.noMatchFoundWhenReadingItem): + return + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } + } + + @Test + func readSubscription_Identifiable_Success() async throws { + let modelType = IdentifiableModel_UuidId.self + let _value = modelType.seeded(1) + var (existingValue, managed) = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try (modelType.init(managed: managed), managed) + } + expectNoDifference(existingValue, _value) + + try await verify(existingValue) + + let stream = repository + .readSubscription(_value) + var iterator = stream.makeAsyncIterator() + var _latestValue = try await iterator.next()?.get() + var latestValue = try #require(_latestValue) + + expectNoDifference(latestValue, existingValue) + + existingValue = try await repositoryContext.perform(schedule: .immediate) { + managed.int += 1 + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + + _latestValue = try await iterator.next()?.get() + latestValue = try #require(_latestValue) + + expectNoDifference(latestValue, existingValue) + } + + @Test + func readThrowingSubscription_Identifiable_Success() async throws { + let modelType = IdentifiableModel_UuidId.self + let _value = modelType.seeded(1) + var (existingValue, managed) = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try (modelType.init(managed: managed), managed) + } + expectNoDifference(existingValue, _value) + + try await verify(existingValue) + + let stream = repository + .readThrowingSubscription(_value) + var iterator = stream.makeAsyncIterator() + var _latestValue = try await iterator.next() + var latestValue = try #require(_latestValue) + + expectNoDifference(latestValue, existingValue) + + existingValue = try await repositoryContext.perform(schedule: .immediate) { + managed.int += 1 + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + + _latestValue = try await iterator.next() + latestValue = try #require(_latestValue) + + expectNoDifference(latestValue, existingValue) + } + + @Test + func readSubscription_Identifiable_ById_Success() async throws { + let modelType = IdentifiableModel_UuidId.self + let _value = modelType.seeded(1) + var (existingValue, managed) = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try (modelType.init(managed: managed), managed) + } + expectNoDifference(existingValue, _value) + + try await verify(existingValue) + + let stream = repository + .readSubscription(_value.id, of: modelType) + var iterator = stream.makeAsyncIterator() + var _latestValue = try await iterator.next()?.get() + var latestValue = try #require(_latestValue) + + expectNoDifference(latestValue, existingValue) + + existingValue = try await repositoryContext.perform(schedule: .immediate) { + managed.int += 1 + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + + _latestValue = try await iterator.next()?.get() + latestValue = try #require(_latestValue) + + expectNoDifference(latestValue, existingValue) + } + + @Test + func readThrowingSubscription_Identifiable_ById_Success() async throws { + let modelType = IdentifiableModel_UuidId.self + let _value = modelType.seeded(1) + var (existingValue, managed) = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try (modelType.init(managed: managed), managed) + } + expectNoDifference(existingValue, _value) + + try await verify(existingValue) + + let stream = repository + .readThrowingSubscription(_value.id, of: modelType) + var iterator = stream.makeAsyncIterator() + var _latestValue = try await iterator.next() + var latestValue = try #require(_latestValue) + + expectNoDifference(latestValue, existingValue) + + existingValue = try await repositoryContext.perform(schedule: .immediate) { + managed.int += 1 + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + + _latestValue = try await iterator.next() + latestValue = try #require(_latestValue) + + expectNoDifference(latestValue, existingValue) + } + + @Test + func read_ManagedId_Success() async throws { + let modelType = ManagedIdModel_UuidId.self + let _value = modelType.seeded(1) + let existingValue = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + expectNoDifference(existingValue.removingManagedId(), _value) + + try await verify(existingValue) + + _ = try await repository + .read(#require(existingValue.managedId), of: modelType).get() + } + + @Test + func read_ManagedId_Failure() async throws { + let modelType = ManagedIdModel_UuidId.self + let _value = modelType.seeded(1) + let existingValue = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + try repositoryContext.obtainPermanentIDs(for: [managed]) + + let value = try modelType.init(managed: managed) + + repositoryContext.delete(managed) + try repositoryContext.save() + try repositoryContext.parent?.save() + + return value + } + let result = try await repository + .read(#require(existingValue.managedId), of: modelType) + + switch result { + case .success: + Issue.record("Not expecting success") + case let .failure(.cocoa(cocoaError)): + expectNoDifference(cocoaError.code, .managedObjectReferentialIntegrity) + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } + } + + @Test + func readSubscription_ManagedId_Success() async throws { + let modelType = ManagedIdModel_UuidId.self + let _value = modelType.seeded(1) + var (existingValue, managed) = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try (modelType.init(managed: managed), managed) + } + expectNoDifference(existingValue.removingManagedId(), _value) + + try await verify(existingValue) + + let stream = try repository + .readSubscription(#require(existingValue.managedId), of: modelType) + var iterator = stream.makeAsyncIterator() + var _latestValue = try await iterator.next()?.get() + var latestValue = try #require(_latestValue) + + expectNoDifference(latestValue, existingValue) + + existingValue = try await repositoryContext.perform(schedule: .immediate) { + managed.int += 1 + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + + _latestValue = try await iterator.next()?.get() + latestValue = try #require(_latestValue) + + expectNoDifference(latestValue, existingValue) + } + + @Test + func readThrowingSubscription_ManagedId_Success() async throws { + let modelType = ManagedIdModel_UuidId.self + let _value = modelType.seeded(1) + var (existingValue, managed) = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try (modelType.init(managed: managed), managed) + } + expectNoDifference(existingValue.removingManagedId(), _value) + + try await verify(existingValue) + + let stream = try repository + .readThrowingSubscription(#require(existingValue.managedId), of: modelType) + var iterator = stream.makeAsyncIterator() + var _latestValue = try await iterator.next() + var latestValue = try #require(_latestValue) + + expectNoDifference(latestValue, existingValue) + + existingValue = try await repositoryContext.perform(schedule: .immediate) { + managed.int += 1 + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + + _latestValue = try await iterator.next() + latestValue = try #require(_latestValue) + + expectNoDifference(latestValue, existingValue) + } + + @Test + func read_ManagedIdReferencable_Success() async throws { + let modelType = ManagedIdModel_UuidId.self + let _value = modelType.seeded(1) + let existingValue = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + expectNoDifference(existingValue.removingManagedId(), _value) + + try await verify(existingValue) + + _ = try await repository + .read(existingValue).get() + } + + @Test + func read_ManagedIdReferencable_Failure() async throws { + let modelType = ManagedIdModel_UuidId.self + let _value = modelType.seeded(1) + let existingValue = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + try repositoryContext.obtainPermanentIDs(for: [managed]) + + let value = try modelType.init(managed: managed) + + repositoryContext.delete(managed) + try repositoryContext.save() + try repositoryContext.parent?.save() + + return value + } + let result = await repository + .read(existingValue) + + switch result { + case .success: + Issue.record("Not expecting success") + case let .failure(.cocoa(cocoaError)): + expectNoDifference(cocoaError.code, .managedObjectReferentialIntegrity) + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } + } + + @Test + func read_ManagedIdReferencable_NoManagedId_Failure() async throws { + let modelType = ManagedIdModel_UuidId.self + let _value = modelType.seeded(1) + let result = await repository + .read(_value) + + switch result { + case .success: + Issue.record("Not expecting success") + case .failure(.noObjectIdOnItem): + return + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } + } + + @Test + func readSubscription_ManagedIdReferencable_Success() async throws { + let modelType = ManagedIdModel_UuidId.self + let _value = modelType.seeded(1) + var (existingValue, managed) = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try (modelType.init(managed: managed), managed) + } + expectNoDifference(existingValue.removingManagedId(), _value) + + try await verify(existingValue) + + let stream = repository + .readSubscription(existingValue) + var iterator = stream.makeAsyncIterator() + var _latestValue = try await iterator.next()?.get() + var latestValue = try #require(_latestValue) + + expectNoDifference(latestValue, existingValue) + + existingValue = try await repositoryContext.perform(schedule: .immediate) { + managed.int += 1 + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + + _latestValue = try await iterator.next()?.get() + latestValue = try #require(_latestValue) + + expectNoDifference(latestValue, existingValue) + } + + @Test + func readThrowingSubscription_ManagedIdReferencable_Success() async throws { + let modelType = ManagedIdModel_UuidId.self + let _value = modelType.seeded(1) + var (existingValue, managed) = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try (modelType.init(managed: managed), managed) + } + expectNoDifference(existingValue.removingManagedId(), _value) + + try await verify(existingValue) + + let stream = repository + .readThrowingSubscription(existingValue) + var iterator = stream.makeAsyncIterator() + var _latestValue = try await iterator.next() + var latestValue = try #require(_latestValue) + + expectNoDifference(latestValue, existingValue) + + existingValue = try await repositoryContext.perform(schedule: .immediate) { + managed.int += 1 + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + + _latestValue = try await iterator.next() + latestValue = try #require(_latestValue) + + expectNoDifference(latestValue, existingValue) + } + + @Test + func read_ManagedIdUrl_Success() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + let _value = modelType.seeded(1) + let existingValue = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + expectNoDifference(existingValue.removingManagedIdUrl(), _value) + + try await verify(existingValue) + + _ = try await repository + .read(#require(existingValue.managedIdUrl), of: modelType).get() + } + + @Test + func read_ManagedIdUrl_Failure() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + let _value = modelType.seeded(1) + let existingValue = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + try repositoryContext.obtainPermanentIDs(for: [managed]) + + let value = try modelType.init(managed: managed) + + repositoryContext.delete(managed) + try repositoryContext.save() + try repositoryContext.parent?.save() + + return value + } + let result = try await repository + .read(#require(existingValue.managedIdUrl), of: modelType) + + switch result { + case .success: + Issue.record("Not expecting success") + case let .failure(.cocoa(cocoaError)): + expectNoDifference(cocoaError.code, .managedObjectReferentialIntegrity) + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } + } + + @Test + func readSubscription_ManagedIdUrl_Success() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + let _value = modelType.seeded(1) + var (existingValue, managed) = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try (modelType.init(managed: managed), managed) + } + expectNoDifference(existingValue.removingManagedIdUrl(), _value) + + try await verify(existingValue) + + let stream = try repository + .readSubscription(#require(existingValue.managedIdUrl), of: modelType) + var iterator = stream.makeAsyncIterator() + var _latestValue = try await iterator.next()?.get() + var latestValue = try #require(_latestValue) + + expectNoDifference(latestValue, existingValue) + + existingValue = try await repositoryContext.perform(schedule: .immediate) { + managed.int += 1 + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + + _latestValue = try await iterator.next()?.get() + latestValue = try #require(_latestValue) + + expectNoDifference(latestValue, existingValue) + } + + @Test + func readThrowingSubscription_ManagedIdUrl_Success() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + let _value = modelType.seeded(1) + var (existingValue, managed) = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try (modelType.init(managed: managed), managed) + } + expectNoDifference(existingValue.removingManagedIdUrl(), _value) + + try await verify(existingValue) + + let stream = repository + .readThrowingSubscription(existingValue) + var iterator = stream.makeAsyncIterator() + var _latestValue = try await iterator.next() + var latestValue = try #require(_latestValue) + + expectNoDifference(latestValue, existingValue) + + existingValue = try await repositoryContext.perform(schedule: .immediate) { + managed.int += 1 + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + + _latestValue = try await iterator.next() + latestValue = try #require(_latestValue) + + expectNoDifference(latestValue, existingValue) + } + + @Test + func read_ManagedIdUrlReferencable_Success() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + let _value = modelType.seeded(1) + let existingValue = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + expectNoDifference(existingValue.removingManagedIdUrl(), _value) + + try await verify(existingValue) + + _ = try await repository + .read(existingValue).get() + } + + @Test + func read_ManagedIdUrlReferencable_Failure() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + let _value = modelType.seeded(1) + let existingValue = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + try repositoryContext.obtainPermanentIDs(for: [managed]) + + let value = try modelType.init(managed: managed) + + repositoryContext.delete(managed) + try repositoryContext.save() + try repositoryContext.parent?.save() + + return value + } + let result = await repository + .read(existingValue) + + switch result { + case .success: + Issue.record("Not expecting success") + case let .failure(.cocoa(cocoaError)): + expectNoDifference(cocoaError.code, .managedObjectReferentialIntegrity) + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } + } + + @Test + func read_ManagedIdUrlReferencable_NoManagedId_Failure() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + let _value = modelType.seeded(1) + let result = await repository + .read(_value) + + switch result { + case .success: + Issue.record("Not expecting success") + case .failure(.noUrlOnItemToMapToObjectId): + return + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } + } + + @Test + func readSubscription_ManagedIdUrlReferencable_Success() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + let _value = modelType.seeded(1) + var (existingValue, managed) = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try (modelType.init(managed: managed), managed) + } + expectNoDifference(existingValue.removingManagedIdUrl(), _value) + + try await verify(existingValue) + + let stream = repository + .readSubscription(existingValue) + var iterator = stream.makeAsyncIterator() + var _latestValue = try await iterator.next()?.get() + var latestValue = try #require(_latestValue) + + expectNoDifference(latestValue, existingValue) + + existingValue = try await repositoryContext.perform(schedule: .immediate) { + managed.int += 1 + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + + _latestValue = try await iterator.next()?.get() + latestValue = try #require(_latestValue) + + expectNoDifference(latestValue, existingValue) + } + + @Test + func readThrowingSubscription_ManagedIdUrlReferencable_Success() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + let _value = modelType.seeded(1) + var (existingValue, managed) = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try (modelType.init(managed: managed), managed) + } + expectNoDifference(existingValue.removingManagedIdUrl(), _value) + + try await verify(existingValue) + + let stream = repository + .readThrowingSubscription(existingValue) + var iterator = stream.makeAsyncIterator() + var _latestValue = try await iterator.next() + var latestValue = try #require(_latestValue) + + expectNoDifference(latestValue, existingValue) + + existingValue = try await repositoryContext.perform(schedule: .immediate) { + managed.int += 1 + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + + _latestValue = try await iterator.next() + latestValue = try #require(_latestValue) + + expectNoDifference(latestValue, existingValue) + } + + init( + container: NSPersistentContainer, + repositoryContext: NSManagedObjectContext, + repository: CoreDataRepository + ) { + self.container = container + self.repositoryContext = repositoryContext + self.repository = repository } - expectNoDifference(existingValue, _value) - - try await verify(existingValue) - - let value = try await repository() - .read(existingValue).get() - - expectNoDifference(value, existingValue) - } - - func testRead_Identifiable_Failure() async throws { - let modelType = IdentifiableModel_UuidId.self - let _value = modelType.seeded(1) - let result = try await repository() - .read(_value) - - switch result { - case .success: - XCTFail("Not expecting success") - case .failure(.noMatchFoundWhenReadingItem): - return - case let .failure(error): - XCTFail("Unexpected error: \(error)") - } - } - - func testRead_Identifiable_ById_Success() async throws { - let modelType = IdentifiableModel_UuidId.self - let _value = modelType.seeded(1) - let existingValue = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try modelType.init(managed: managed) - } - expectNoDifference(existingValue, _value) - - try await verify(existingValue) - - _ = try await repository() - .read(existingValue.id, of: modelType).get() - } - - func testRead_Identifiable_ById_Failure() async throws { - let modelType = IdentifiableModel_UuidId.self - let _value = modelType.seeded(1) - let result = try await repository() - .read(_value.id, of: modelType) - - switch result { - case .success: - XCTFail("Not expecting success") - case .failure(.noMatchFoundWhenReadingItem): - return - case let .failure(error): - XCTFail("Unexpected error: \(error)") - } - } - - func testReadSubscription_Identifiable_Success() async throws { - let modelType = IdentifiableModel_UuidId.self - let _value = modelType.seeded(1) - var (existingValue, managed) = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try (modelType.init(managed: managed), managed) - } - expectNoDifference(existingValue, _value) - - try await verify(existingValue) - - let stream = try repository() - .readSubscription(_value) - var iterator = stream.makeAsyncIterator() - var _latestValue = try await iterator.next()?.get() - var latestValue = try XCTUnwrap(_latestValue) - - expectNoDifference(latestValue, existingValue) - - existingValue = try await repositoryContext().perform(schedule: .immediate) { - managed.int += 1 - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try modelType.init(managed: managed) - } - - _latestValue = try await iterator.next()?.get() - latestValue = try XCTUnwrap(_latestValue) - - expectNoDifference(latestValue, existingValue) - } - - func testReadThrowingSubscription_Identifiable_Success() async throws { - let modelType = IdentifiableModel_UuidId.self - let _value = modelType.seeded(1) - var (existingValue, managed) = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try (modelType.init(managed: managed), managed) - } - expectNoDifference(existingValue, _value) - - try await verify(existingValue) - - let stream = try repository() - .readThrowingSubscription(_value) - var iterator = stream.makeAsyncIterator() - var _latestValue = try await iterator.next() - var latestValue = try XCTUnwrap(_latestValue) - - expectNoDifference(latestValue, existingValue) - - existingValue = try await repositoryContext().perform(schedule: .immediate) { - managed.int += 1 - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try modelType.init(managed: managed) - } - - _latestValue = try await iterator.next() - latestValue = try XCTUnwrap(_latestValue) - - expectNoDifference(latestValue, existingValue) - } - - func testReadSubscription_Identifiable_ById_Success() async throws { - let modelType = IdentifiableModel_UuidId.self - let _value = modelType.seeded(1) - var (existingValue, managed) = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try (modelType.init(managed: managed), managed) - } - expectNoDifference(existingValue, _value) - - try await verify(existingValue) - - let stream = try repository() - .readSubscription(_value.id, of: modelType) - var iterator = stream.makeAsyncIterator() - var _latestValue = try await iterator.next()?.get() - var latestValue = try XCTUnwrap(_latestValue) - - expectNoDifference(latestValue, existingValue) - - existingValue = try await repositoryContext().perform(schedule: .immediate) { - managed.int += 1 - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try modelType.init(managed: managed) - } - - _latestValue = try await iterator.next()?.get() - latestValue = try XCTUnwrap(_latestValue) - - expectNoDifference(latestValue, existingValue) - } - - func testReadThrowingSubscription_Identifiable_ById_Success() async throws { - let modelType = IdentifiableModel_UuidId.self - let _value = modelType.seeded(1) - var (existingValue, managed) = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try (modelType.init(managed: managed), managed) - } - expectNoDifference(existingValue, _value) - - try await verify(existingValue) - - let stream = try repository() - .readThrowingSubscription(_value.id, of: modelType) - var iterator = stream.makeAsyncIterator() - var _latestValue = try await iterator.next() - var latestValue = try XCTUnwrap(_latestValue) - - expectNoDifference(latestValue, existingValue) - - existingValue = try await repositoryContext().perform(schedule: .immediate) { - managed.int += 1 - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try modelType.init(managed: managed) - } - - _latestValue = try await iterator.next() - latestValue = try XCTUnwrap(_latestValue) - - expectNoDifference(latestValue, existingValue) - } - - func testRead_ManagedId_Success() async throws { - let modelType = ManagedIdModel_UuidId.self - let _value = modelType.seeded(1) - let existingValue = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try modelType.init(managed: managed) - } - expectNoDifference(existingValue.removingManagedId(), _value) - - try await verify(existingValue) - - _ = try await repository() - .read(XCTUnwrap(existingValue.managedId), of: modelType).get() - } - - func testRead_ManagedId_Failure() async throws { - let modelType = ManagedIdModel_UuidId.self - let _value = modelType.seeded(1) - let existingValue = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - try self.repositoryContext().obtainPermanentIDs(for: [managed]) - - let value = try modelType.init(managed: managed) - - try self.repositoryContext().delete(managed) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - - return value - } - let result = try await repository() - .read(XCTUnwrap(existingValue.managedId), of: modelType) - - switch result { - case .success: - XCTFail("Not expecting success") - case let .failure(.cocoa(cocoaError)): - XCTAssertEqual(cocoaError.code, .managedObjectReferentialIntegrity) - case let .failure(error): - XCTFail("Unexpected error: \(error)") - } - } - - func testReadSubscription_ManagedId_Success() async throws { - let modelType = ManagedIdModel_UuidId.self - let _value = modelType.seeded(1) - var (existingValue, managed) = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try (modelType.init(managed: managed), managed) - } - expectNoDifference(existingValue.removingManagedId(), _value) - - try await verify(existingValue) - - let stream = try repository() - .readSubscription(XCTUnwrap(existingValue.managedId), of: modelType) - var iterator = stream.makeAsyncIterator() - var _latestValue = try await iterator.next()?.get() - var latestValue = try XCTUnwrap(_latestValue) - - expectNoDifference(latestValue, existingValue) - - existingValue = try await repositoryContext().perform(schedule: .immediate) { - managed.int += 1 - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try modelType.init(managed: managed) - } - - _latestValue = try await iterator.next()?.get() - latestValue = try XCTUnwrap(_latestValue) - - expectNoDifference(latestValue, existingValue) - } - - func testReadThrowingSubscription_ManagedId_Success() async throws { - let modelType = ManagedIdModel_UuidId.self - let _value = modelType.seeded(1) - var (existingValue, managed) = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try (modelType.init(managed: managed), managed) - } - expectNoDifference(existingValue.removingManagedId(), _value) - - try await verify(existingValue) - - let stream = try repository() - .readThrowingSubscription(XCTUnwrap(existingValue.managedId), of: modelType) - var iterator = stream.makeAsyncIterator() - var _latestValue = try await iterator.next() - var latestValue = try XCTUnwrap(_latestValue) - - expectNoDifference(latestValue, existingValue) - - existingValue = try await repositoryContext().perform(schedule: .immediate) { - managed.int += 1 - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try modelType.init(managed: managed) - } - - _latestValue = try await iterator.next() - latestValue = try XCTUnwrap(_latestValue) - - expectNoDifference(latestValue, existingValue) - } - - func testRead_ManagedIdReferencable_Success() async throws { - let modelType = ManagedIdModel_UuidId.self - let _value = modelType.seeded(1) - let existingValue = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try modelType.init(managed: managed) - } - expectNoDifference(existingValue.removingManagedId(), _value) - - try await verify(existingValue) - - _ = try await repository() - .read(existingValue).get() - } - - func testRead_ManagedIdReferencable_Failure() async throws { - let modelType = ManagedIdModel_UuidId.self - let _value = modelType.seeded(1) - let existingValue = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - try self.repositoryContext().obtainPermanentIDs(for: [managed]) - - let value = try modelType.init(managed: managed) - - try self.repositoryContext().delete(managed) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - - return value - } - let result = try await repository() - .read(existingValue) - - switch result { - case .success: - XCTFail("Not expecting success") - case let .failure(.cocoa(cocoaError)): - XCTAssertEqual(cocoaError.code, .managedObjectReferentialIntegrity) - case let .failure(error): - XCTFail("Unexpected error: \(error)") - } - } - - func testRead_ManagedIdReferencable_NoManagedId_Failure() async throws { - let modelType = ManagedIdModel_UuidId.self - let _value = modelType.seeded(1) - let result = try await repository() - .read(_value) - - switch result { - case .success: - XCTFail("Not expecting success") - case .failure(.noObjectIdOnItem): - return - case let .failure(error): - XCTFail("Unexpected error: \(error)") - } - } - - func testReadSubscription_ManagedIdReferencable_Success() async throws { - let modelType = ManagedIdModel_UuidId.self - let _value = modelType.seeded(1) - var (existingValue, managed) = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try (modelType.init(managed: managed), managed) - } - expectNoDifference(existingValue.removingManagedId(), _value) - - try await verify(existingValue) - - let stream = try repository() - .readSubscription(existingValue) - var iterator = stream.makeAsyncIterator() - var _latestValue = try await iterator.next()?.get() - var latestValue = try XCTUnwrap(_latestValue) - - expectNoDifference(latestValue, existingValue) - - existingValue = try await repositoryContext().perform(schedule: .immediate) { - managed.int += 1 - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try modelType.init(managed: managed) - } - - _latestValue = try await iterator.next()?.get() - latestValue = try XCTUnwrap(_latestValue) - - expectNoDifference(latestValue, existingValue) - } - - func testReadThrowingSubscription_ManagedIdReferencable_Success() async throws { - let modelType = ManagedIdModel_UuidId.self - let _value = modelType.seeded(1) - var (existingValue, managed) = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try (modelType.init(managed: managed), managed) - } - expectNoDifference(existingValue.removingManagedId(), _value) - - try await verify(existingValue) - - let stream = try repository() - .readThrowingSubscription(existingValue) - var iterator = stream.makeAsyncIterator() - var _latestValue = try await iterator.next() - var latestValue = try XCTUnwrap(_latestValue) - - expectNoDifference(latestValue, existingValue) - - existingValue = try await repositoryContext().perform(schedule: .immediate) { - managed.int += 1 - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try modelType.init(managed: managed) - } - - _latestValue = try await iterator.next() - latestValue = try XCTUnwrap(_latestValue) - - expectNoDifference(latestValue, existingValue) - } - - func testRead_ManagedIdUrl_Success() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - let _value = modelType.seeded(1) - let existingValue = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try modelType.init(managed: managed) - } - expectNoDifference(existingValue.removingManagedIdUrl(), _value) - - try await verify(existingValue) - - _ = try await repository() - .read(XCTUnwrap(existingValue.managedIdUrl), of: modelType).get() - } - - func testRead_ManagedIdUrl_Failure() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - let _value = modelType.seeded(1) - let existingValue = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - try self.repositoryContext().obtainPermanentIDs(for: [managed]) - - let value = try modelType.init(managed: managed) - - try self.repositoryContext().delete(managed) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - - return value - } - let result = try await repository() - .read(XCTUnwrap(existingValue.managedIdUrl), of: modelType) - - switch result { - case .success: - XCTFail("Not expecting success") - case let .failure(.cocoa(cocoaError)): - XCTAssertEqual(cocoaError.code, .managedObjectReferentialIntegrity) - case let .failure(error): - XCTFail("Unexpected error: \(error)") - } - } - - func testReadSubscription_ManagedIdUrl_Success() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - let _value = modelType.seeded(1) - var (existingValue, managed) = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try (modelType.init(managed: managed), managed) - } - expectNoDifference(existingValue.removingManagedIdUrl(), _value) - - try await verify(existingValue) - - let stream = try repository() - .readSubscription(XCTUnwrap(existingValue.managedIdUrl), of: modelType) - var iterator = stream.makeAsyncIterator() - var _latestValue = try await iterator.next()?.get() - var latestValue = try XCTUnwrap(_latestValue) - - expectNoDifference(latestValue, existingValue) - - existingValue = try await repositoryContext().perform(schedule: .immediate) { - managed.int += 1 - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try modelType.init(managed: managed) - } - - _latestValue = try await iterator.next()?.get() - latestValue = try XCTUnwrap(_latestValue) - - expectNoDifference(latestValue, existingValue) - } - - func testReadThrowingSubscription_ManagedIdUrl_Success() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - let _value = modelType.seeded(1) - var (existingValue, managed) = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try (modelType.init(managed: managed), managed) - } - expectNoDifference(existingValue.removingManagedIdUrl(), _value) - - try await verify(existingValue) - - let stream = try repository() - .readThrowingSubscription(existingValue) - var iterator = stream.makeAsyncIterator() - var _latestValue = try await iterator.next() - var latestValue = try XCTUnwrap(_latestValue) - - expectNoDifference(latestValue, existingValue) - - existingValue = try await repositoryContext().perform(schedule: .immediate) { - managed.int += 1 - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try modelType.init(managed: managed) - } - - _latestValue = try await iterator.next() - latestValue = try XCTUnwrap(_latestValue) - - expectNoDifference(latestValue, existingValue) - } - - func testRead_ManagedIdUrlReferencable_Success() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - let _value = modelType.seeded(1) - let existingValue = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try modelType.init(managed: managed) - } - expectNoDifference(existingValue.removingManagedIdUrl(), _value) - - try await verify(existingValue) - - _ = try await repository() - .read(existingValue).get() - } - - func testRead_ManagedIdUrlReferencable_Failure() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - let _value = modelType.seeded(1) - let existingValue = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - try self.repositoryContext().obtainPermanentIDs(for: [managed]) - - let value = try modelType.init(managed: managed) - - try self.repositoryContext().delete(managed) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - - return value - } - let result = try await repository() - .read(existingValue) - - switch result { - case .success: - XCTFail("Not expecting success") - case let .failure(.cocoa(cocoaError)): - XCTAssertEqual(cocoaError.code, .managedObjectReferentialIntegrity) - case let .failure(error): - XCTFail("Unexpected error: \(error)") - } - } - - func testRead_ManagedIdUrlReferencable_NoManagedId_Failure() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - let _value = modelType.seeded(1) - let result = try await repository() - .read(_value) - - switch result { - case .success: - XCTFail("Not expecting success") - case .failure(.noUrlOnItemToMapToObjectId): - return - case let .failure(error): - XCTFail("Unexpected error: \(error)") - } - } - - func testReadSubscription_ManagedIdUrlReferencable_Success() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - let _value = modelType.seeded(1) - var (existingValue, managed) = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try (modelType.init(managed: managed), managed) - } - expectNoDifference(existingValue.removingManagedIdUrl(), _value) - - try await verify(existingValue) - - let stream = try repository() - .readSubscription(existingValue) - var iterator = stream.makeAsyncIterator() - var _latestValue = try await iterator.next()?.get() - var latestValue = try XCTUnwrap(_latestValue) - - expectNoDifference(latestValue, existingValue) - - existingValue = try await repositoryContext().perform(schedule: .immediate) { - managed.int += 1 - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try modelType.init(managed: managed) - } - - _latestValue = try await iterator.next()?.get() - latestValue = try XCTUnwrap(_latestValue) - - expectNoDifference(latestValue, existingValue) - } - - func testReadThrowingSubscription_ManagedIdUrlReferencable_Success() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - let _value = modelType.seeded(1) - var (existingValue, managed) = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try (modelType.init(managed: managed), managed) - } - expectNoDifference(existingValue.removingManagedIdUrl(), _value) - - try await verify(existingValue) - - let stream = try repository() - .readThrowingSubscription(existingValue) - var iterator = stream.makeAsyncIterator() - var _latestValue = try await iterator.next() - var latestValue = try XCTUnwrap(_latestValue) - - expectNoDifference(latestValue, existingValue) - - existingValue = try await repositoryContext().perform(schedule: .immediate) { - managed.int += 1 - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try modelType.init(managed: managed) - } - - _latestValue = try await iterator.next() - latestValue = try XCTUnwrap(_latestValue) - - expectNoDifference(latestValue, existingValue) } } diff --git a/Tests/CoreDataRepositoryTests/Read_BatchTests.swift b/Tests/CoreDataRepositoryTests/Read_BatchTests.swift index 35cadf5..50ddf27 100644 --- a/Tests/CoreDataRepositoryTests/Read_BatchTests.swift +++ b/Tests/CoreDataRepositoryTests/Read_BatchTests.swift @@ -8,689 +8,730 @@ import CoreData import CoreDataRepository import CustomDump import Internal -import XCTest - -final class Read_BatchTests: CoreDataXCTestCase { - // MARK: Non Atomic - - func testRead_Identifiable_Success() async throws { - let modelType = IdentifiableModel_UuidId.self - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - let existingValues = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try _values.map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try manageds.map { try modelType.init(managed: $0) } - } - expectNoDifference(existingValues, _values) +import Testing - for value in existingValues { - try await verify(value) - } +extension CoreDataRepositoryTests { + @Suite + struct Read_BatchTests: CoreDataTestSuite { + let container: NSPersistentContainer + let repositoryContext: NSManagedObjectContext + let repository: CoreDataRepository - let (successful, failed) = try await repository() - .read(existingValues) + // MARK: Non Atomic - XCTAssertEqual(successful.count, _values.count) - XCTAssertEqual(failed.count, 0) - } - - func testRead_Identifiable_Failure() async throws { - let modelType = IdentifiableModel_UuidId.self - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - let (successful, failed) = try await repository() - .read(_values) - - XCTAssertEqual(successful.count, 0) - XCTAssertEqual(failed.count, _values.count) - } - - func testRead_ManagedIdReferencable_Success() async throws { - let modelType = ManagedIdModel_UuidId.self - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - let existingValues = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try _values.map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try manageds.map { try modelType.init(managed: $0) } - } - expectNoDifference(existingValues.map { $0.removingManagedId() }, _values) + @Test + func read_Identifiable_Success() async throws { + let modelType = IdentifiableModel_UuidId.self + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] + let existingValues = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try _values.map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + return try manageds.map { try modelType.init(managed: $0) } + } + expectNoDifference(existingValues, _values) + + for value in existingValues { + try await verify(value) + } + + let (successful, failed) = await repository + .read(existingValues) + + expectNoDifference(successful.count, _values.count) + expectNoDifference(failed.count, 0) + } + + @Test + func read_Identifiable_Failure() async throws { + let modelType = IdentifiableModel_UuidId.self + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] + let (successful, failed) = await repository + .read(_values) - for value in existingValues { - try await verify(value) + expectNoDifference(successful.count, 0) + expectNoDifference(failed.count, _values.count) } - let (successful, failed) = try await repository() - .read(existingValues) - - XCTAssertEqual(successful.count, _values.count) - XCTAssertEqual(failed.count, 0) - } - - func testRead_ManagedIdReferencable_Failure() async throws { - let modelType = ManagedIdModel_UuidId.self - - let _values = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try [ + @Test + func read_ManagedIdReferencable_Success() async throws { + let modelType = ManagedIdModel_UuidId.self + let _values = [ modelType.seeded(1), modelType.seeded(2), modelType.seeded(3), modelType.seeded(4), modelType.seeded(5), - ].map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() + ] + let existingValues = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try _values.map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + return try manageds.map { try modelType.init(managed: $0) } + } + expectNoDifference(existingValues.map { $0.removingManagedId() }, _values) - try self.repositoryContext().obtainPermanentIDs(for: manageds) - let values = try manageds.map { try modelType.init(managed: $0) } + for value in existingValues { + try await verify(value) + } - try self.repositoryContext().delete(manageds[0]) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() + let (successful, failed) = await repository + .read(existingValues) - return values + expectNoDifference(successful.count, _values.count) + expectNoDifference(failed.count, 0) } - let (successful, failed) = try await repository() - .read(_values) + @Test + func read_ManagedIdReferencable_Failure() async throws { + let modelType = ManagedIdModel_UuidId.self - XCTAssertEqual(successful.count, _values.count - 1) - XCTAssertEqual(failed.count, 1) - } + let _values = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ].map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() - func testRead_ManagedIdReferencable_NoManagedId_Failure() async throws { - let modelType = ManagedIdModel_UuidId.self + try repositoryContext.obtainPermanentIDs(for: manageds) + let values = try manageds.map { try modelType.init(managed: $0) } - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] + repositoryContext.delete(manageds[0]) + try repositoryContext.save() + try repositoryContext.parent?.save() - let (successful, failed) = try await repository() - .read(_values) + return values + } - XCTAssertEqual(successful.count, 0) - XCTAssertEqual(failed.count, _values.count) - } + let (successful, failed) = await repository + .read(_values) - func testRead_ManagedId_Success() async throws { - let modelType = ManagedIdModel_UuidId.self - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - let existingValues = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try _values.map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try manageds.map { try modelType.init(managed: $0) } + expectNoDifference(successful.count, _values.count - 1) + expectNoDifference(failed.count, 1) } - expectNoDifference(existingValues.map { $0.removingManagedId() }, _values) - for value in existingValues { - try await verify(value) - } - - let (successful, failed) = try await repository() - .read(existingValues.compactMap(\.managedId), as: modelType) - - XCTAssertEqual(successful.count, _values.count) - XCTAssertEqual(failed.count, 0) - } + @Test + func read_ManagedIdReferencable_NoManagedId_Failure() async throws { + let modelType = ManagedIdModel_UuidId.self - func testRead_ManagedId_Failure() async throws { - let modelType = ManagedIdModel_UuidId.self - - let _values = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try [ + let _values = [ modelType.seeded(1), modelType.seeded(2), modelType.seeded(3), modelType.seeded(4), modelType.seeded(5), - ].map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - - try self.repositoryContext().obtainPermanentIDs(for: manageds) - let values = try manageds.map { try modelType.init(managed: $0) } - - try self.repositoryContext().delete(manageds[0]) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - - return values - } - - for value in _values[1 ... 4] { - try await verify(value) - } - try await verifyDoesNotExist(_values[0]) - - let (successful, failed) = try await repository() - .read(_values.map { try XCTUnwrap($0.managedId) }, as: modelType) + ] - XCTAssertEqual(successful.count, _values.count - 1) - XCTAssertEqual(failed.count, 1) - } - - func testRead_ManagedIdUrlReferencable_Success() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - let existingValues = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try _values.map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try manageds.map { try modelType.init(managed: $0) } - } - expectNoDifference(existingValues.map { $0.removingManagedIdUrl() }, _values) + let (successful, failed) = await repository + .read(_values) - for value in existingValues { - try await verify(value) + expectNoDifference(successful.count, 0) + expectNoDifference(failed.count, _values.count) } - let (successful, failed) = try await repository() - .read(existingValues) - - XCTAssertEqual(successful.count, _values.count) - XCTAssertEqual(failed.count, 0) - } - - func testRead_ManagedIdUrlReferencable_Failure() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - - let _values = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try [ + @Test + func read_ManagedId_Success() async throws { + let modelType = ManagedIdModel_UuidId.self + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] + let existingValues = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try _values.map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + return try manageds.map { try modelType.init(managed: $0) } + } + expectNoDifference(existingValues.map { $0.removingManagedId() }, _values) + + for value in existingValues { + try await verify(value) + } + + let (successful, failed) = await repository + .read(existingValues.compactMap(\.managedId), as: modelType) + + expectNoDifference(successful.count, _values.count) + expectNoDifference(failed.count, 0) + } + + @Test + func read_ManagedId_Failure() async throws { + let modelType = ManagedIdModel_UuidId.self + + let _values = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ].map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + + try repositoryContext.obtainPermanentIDs(for: manageds) + let values = try manageds.map { try modelType.init(managed: $0) } + + repositoryContext.delete(manageds[0]) + try repositoryContext.save() + try repositoryContext.parent?.save() + + return values + } + + for value in _values[1 ... 4] { + try await verify(value) + } + try await verifyDoesNotExist(_values[0]) + + let (successful, failed) = try await repository + .read(_values.map { try #require($0.managedId) }, as: modelType) + + expectNoDifference(successful.count, _values.count - 1) + expectNoDifference(failed.count, 1) + } + + @Test + func read_ManagedIdUrlReferencable_Success() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + let _values = [ modelType.seeded(1), modelType.seeded(2), modelType.seeded(3), modelType.seeded(4), modelType.seeded(5), - ].map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() + ] + let existingValues = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try _values.map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + return try manageds.map { try modelType.init(managed: $0) } + } + expectNoDifference(existingValues.map { $0.removingManagedIdUrl() }, _values) - try self.repositoryContext().obtainPermanentIDs(for: manageds) - let values = try manageds.map { try modelType.init(managed: $0) } + for value in existingValues { + try await verify(value) + } - try self.repositoryContext().delete(manageds[0]) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() + let (successful, failed) = await repository + .read(existingValues) - return values + expectNoDifference(successful.count, _values.count) + expectNoDifference(failed.count, 0) } - let (successful, failed) = try await repository() - .read(_values) + @Test + func read_ManagedIdUrlReferencable_Failure() async throws { + let modelType = ManagedIdUrlModel_UuidId.self - XCTAssertEqual(successful.count, _values.count - 1) - XCTAssertEqual(failed.count, 1) - } + let _values = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ].map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() - func testRead_ManagedIdUrlReferencable_NoManagedIdUrl_Failure() async throws { - let modelType = ManagedIdUrlModel_UuidId.self + try repositoryContext.obtainPermanentIDs(for: manageds) + let values = try manageds.map { try modelType.init(managed: $0) } - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] + repositoryContext.delete(manageds[0]) + try repositoryContext.save() + try repositoryContext.parent?.save() - let (successful, failed) = try await repository() - .read(_values) + return values + } - XCTAssertEqual(successful.count, 0) - XCTAssertEqual(failed.count, _values.count) - } + let (successful, failed) = await repository + .read(_values) - func testRead_ManagedIdUrl_Success() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - let existingValues = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try _values.map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try manageds.map { try modelType.init(managed: $0) } + expectNoDifference(successful.count, _values.count - 1) + expectNoDifference(failed.count, 1) } - expectNoDifference(existingValues.map { $0.removingManagedIdUrl() }, _values) - for value in existingValues { - try await verify(value) - } - - let (successful, failed) = try await repository() - .read(existingValues.compactMap(\.managedIdUrl), as: modelType) - - XCTAssertEqual(successful.count, _values.count) - XCTAssertEqual(failed.count, 0) - } + @Test + func read_ManagedIdUrlReferencable_NoManagedIdUrl_Failure() async throws { + let modelType = ManagedIdUrlModel_UuidId.self - func testRead_ManagedIdUrl_Failure() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - - let _values = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try [ + let _values = [ modelType.seeded(1), modelType.seeded(2), modelType.seeded(3), modelType.seeded(4), modelType.seeded(5), - ].map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - - try self.repositoryContext().obtainPermanentIDs(for: manageds) - let values = try manageds.map { try modelType.init(managed: $0) } - - try self.repositoryContext().delete(manageds[0]) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - - return values - } + ] - let (successful, failed) = try await repository() - .read(_values.map { try XCTUnwrap($0.managedIdUrl) }, as: modelType) - - XCTAssertEqual(successful.count, _values.count - 1) - XCTAssertEqual(failed.count, 1) - } - - // MARK: Atomic - - func testReadAtomically_Identifiable_Success() async throws { - let modelType = IdentifiableModel_UuidId.self - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - let existingValues = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try _values.map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try manageds.map { try modelType.init(managed: $0) } - } - expectNoDifference(existingValues, _values) - - for value in existingValues { - try await verify(value) - } - - let values = try await repository() - .readAtomically(existingValues).get() - - expectNoDifference(values, existingValues) - } - - func testReadAtomically_Identifiable_Failure() async throws { - let modelType = IdentifiableModel_UuidId.self - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - let result = try await repository() - .readAtomically(_values) - - switch result { - case .success: - XCTFail("Not expecting success") - case .failure(.noMatchFoundWhenReadingItem): - break - case let .failure(error): - XCTFail("Unexpected error: \(error)") - } - } + let (successful, failed) = await repository + .read(_values) - func testReadAtomically_ManagedIdReferencable_Success() async throws { - let modelType = ManagedIdModel_UuidId.self - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - let existingValues = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try _values.map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try manageds.map { try modelType.init(managed: $0) } + expectNoDifference(successful.count, 0) + expectNoDifference(failed.count, _values.count) } - expectNoDifference(existingValues.map { $0.removingManagedId() }, _values) - for value in existingValues { - try await verify(value) - } - - let values = try await repository() - .readAtomically(existingValues).get() - - expectNoDifference(values, existingValues) - } - - func testReadAtomically_ManagedIdReferencable_Failure() async throws { - let modelType = ManagedIdModel_UuidId.self - - let _values = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try [ + @Test + func read_ManagedIdUrl_Success() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + let _values = [ modelType.seeded(1), modelType.seeded(2), modelType.seeded(3), modelType.seeded(4), modelType.seeded(5), - ].map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() + ] + let existingValues = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try _values.map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + return try manageds.map { try modelType.init(managed: $0) } + } + expectNoDifference(existingValues.map { $0.removingManagedIdUrl() }, _values) - try self.repositoryContext().obtainPermanentIDs(for: manageds) - let values = try manageds.map { try modelType.init(managed: $0) } + for value in existingValues { + try await verify(value) + } - try self.repositoryContext().delete(manageds[0]) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() + let (successful, failed) = await repository + .read(existingValues.compactMap(\.managedIdUrl), as: modelType) - return values + expectNoDifference(successful.count, _values.count) + expectNoDifference(failed.count, 0) } - let result = try await repository() - .readAtomically(_values) + @Test + func read_ManagedIdUrl_Failure() async throws { + let modelType = ManagedIdUrlModel_UuidId.self - switch result { - case .success: - XCTFail("Not expecting success") - case let .failure(.cocoa(cocoaError)): - XCTAssertEqual(cocoaError.code, .managedObjectReferentialIntegrity) - case let .failure(error): - XCTFail("Unexpected error: \(error)") - } - } + let _values = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ].map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() - func testReadAtomically_ManagedIdReferencable_NoManagedId_Failure() async throws { - let modelType = ManagedIdModel_UuidId.self - - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - - let result = try await repository() - .readAtomically(_values) - - switch result { - case .success: - XCTFail("Not expecting success") - case .failure(.noObjectIdOnItem): - break - case let .failure(error): - XCTFail("Unexpected error: \(error)") - } - } + try repositoryContext.obtainPermanentIDs(for: manageds) + let values = try manageds.map { try modelType.init(managed: $0) } - func testReadAtomically_ManagedId_Success() async throws { - let modelType = ManagedIdModel_UuidId.self - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - let existingValues = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try _values.map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try manageds.map { try modelType.init(managed: $0) } - } - expectNoDifference(existingValues.map { $0.removingManagedId() }, _values) + repositoryContext.delete(manageds[0]) + try repositoryContext.save() + try repositoryContext.parent?.save() - for value in existingValues { - try await verify(value) - } + return values + } - let values = try await repository() - .readAtomically(existingValues.compactMap(\.managedId), as: modelType).get() + let (successful, failed) = try await repository + .read(_values.map { try #require($0.managedIdUrl) }, as: modelType) - expectNoDifference(values, existingValues) - } + expectNoDifference(successful.count, _values.count - 1) + expectNoDifference(failed.count, 1) + } - func testReadAtomically_ManagedId_Failure() async throws { - let modelType = ManagedIdModel_UuidId.self + // MARK: Atomic - let _values = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try [ + @Test + func readAtomically_Identifiable_Success() async throws { + let modelType = IdentifiableModel_UuidId.self + let _values = [ modelType.seeded(1), modelType.seeded(2), modelType.seeded(3), modelType.seeded(4), modelType.seeded(5), - ].map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() + ] + let existingValues = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try _values.map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + return try manageds.map { try modelType.init(managed: $0) } + } + expectNoDifference(existingValues, _values) - try self.repositoryContext().obtainPermanentIDs(for: manageds) - let values = try manageds.map { try modelType.init(managed: $0) } + for value in existingValues { + try await verify(value) + } - try self.repositoryContext().delete(manageds[0]) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() + let values = try await repository + .readAtomically(existingValues).get() - return values + expectNoDifference(values, existingValues) } - for value in _values[1 ... 4] { - try await verify(value) - } - try await verifyDoesNotExist(_values[0]) - - let result = try await repository() - .readAtomically(_values.map { try XCTUnwrap($0.managedId) }, as: modelType) - - switch result { - case .success: - XCTFail("Not expecting success") - case let .failure(.cocoa(cocoaError)): - XCTAssertEqual(cocoaError.code, .managedObjectReferentialIntegrity) - case let .failure(error): - XCTFail("Unexpected error: \(error)") - } - } - - func testReadAtomically_ManagedIdUrlReferencable_Success() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - let existingValues = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try _values.map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try manageds.map { try modelType.init(managed: $0) } - } - expectNoDifference(existingValues.map { $0.removingManagedIdUrl() }, _values) - - for value in existingValues { - try await verify(value) - } - - let values = try await repository() - .readAtomically(existingValues).get() - - expectNoDifference(values, existingValues) - } - - func testReadAtomically_ManagedIdUrlReferencable_Failure() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - - let _values = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try [ + @Test + func readAtomically_Identifiable_Failure() async throws { + let modelType = IdentifiableModel_UuidId.self + let _values = [ modelType.seeded(1), modelType.seeded(2), modelType.seeded(3), modelType.seeded(4), modelType.seeded(5), - ].map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - - try self.repositoryContext().obtainPermanentIDs(for: manageds) - let values = try manageds.map { try modelType.init(managed: $0) } - - try self.repositoryContext().delete(manageds[0]) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - - return values - } - - let result = try await repository() - .readAtomically(_values) - - switch result { - case .success: - XCTFail("Not expecting success") - case let .failure(.cocoa(cocoaError)): - XCTAssertEqual(cocoaError.code, .managedObjectReferentialIntegrity) - case let .failure(error): - XCTFail("Unexpected error: \(error)") - } - } - - func testReadAtomically_ManagedIdUrlReferencable_NoManagedIdUrl_Failure() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - - let result = try await repository() - .readAtomically(_values) - - switch result { - case .success: - XCTFail("Not expecting success") - case .failure(.noUrlOnItemToMapToObjectId): - break - case let .failure(error): - XCTFail("Unexpected error: \(error)") - } - } + ] + let result = await repository + .readAtomically(_values) + + switch result { + case .success: + Issue.record("Not expecting success") + case .failure(.noMatchFoundWhenReadingItem): + break + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } + } + + @Test + func readAtomically_ManagedIdReferencable_Success() async throws { + let modelType = ManagedIdModel_UuidId.self + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] + let existingValues = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try _values.map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + return try manageds.map { try modelType.init(managed: $0) } + } + expectNoDifference(existingValues.map { $0.removingManagedId() }, _values) + + for value in existingValues { + try await verify(value) + } + + let values = try await repository + .readAtomically(existingValues).get() + + expectNoDifference(values, existingValues) + } + + @Test + func readAtomically_ManagedIdReferencable_Failure() async throws { + let modelType = ManagedIdModel_UuidId.self + + let _values = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ].map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + + try repositoryContext.obtainPermanentIDs(for: manageds) + let values = try manageds.map { try modelType.init(managed: $0) } + + repositoryContext.delete(manageds[0]) + try repositoryContext.save() + try repositoryContext.parent?.save() + + return values + } + + let result = await repository + .readAtomically(_values) + + switch result { + case .success: + Issue.record("Not expecting success") + case let .failure(.cocoa(cocoaError)): + expectNoDifference(cocoaError.code, .managedObjectReferentialIntegrity) + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } + } + + @Test + func readAtomically_ManagedIdReferencable_NoManagedId_Failure() async throws { + let modelType = ManagedIdModel_UuidId.self + + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] - func testReadAtomically_ManagedIdUrl_Success() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - let existingValues = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try _values.map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try manageds.map { try modelType.init(managed: $0) } - } - expectNoDifference(existingValues.map { $0.removingManagedIdUrl() }, _values) + let result = await repository + .readAtomically(_values) - for value in existingValues { - try await verify(value) + switch result { + case .success: + Issue.record("Not expecting success") + case .failure(.noObjectIdOnItem): + break + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } } - let values = try await repository() - .readAtomically(existingValues.compactMap(\.managedIdUrl), as: modelType).get() - - expectNoDifference(values, existingValues) - } - - func testReadAtomically_ManagedIdUrl_Failure() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - - let _values = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try [ + @Test + func readAtomically_ManagedId_Success() async throws { + let modelType = ManagedIdModel_UuidId.self + let _values = [ modelType.seeded(1), modelType.seeded(2), modelType.seeded(3), modelType.seeded(4), modelType.seeded(5), - ].map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - - try self.repositoryContext().obtainPermanentIDs(for: manageds) - let values = try manageds.map { try modelType.init(managed: $0) } + ] + let existingValues = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try _values.map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + return try manageds.map { try modelType.init(managed: $0) } + } + expectNoDifference(existingValues.map { $0.removingManagedId() }, _values) + + for value in existingValues { + try await verify(value) + } + + let values = try await repository + .readAtomically(existingValues.compactMap(\.managedId), as: modelType).get() + + expectNoDifference(values, existingValues) + } + + @Test + func readAtomically_ManagedId_Failure() async throws { + let modelType = ManagedIdModel_UuidId.self + + let _values = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ].map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + + try repositoryContext.obtainPermanentIDs(for: manageds) + let values = try manageds.map { try modelType.init(managed: $0) } + + repositoryContext.delete(manageds[0]) + try repositoryContext.save() + try repositoryContext.parent?.save() + + return values + } + + for value in _values[1 ... 4] { + try await verify(value) + } + try await verifyDoesNotExist(_values[0]) + + let result = try await repository + .readAtomically(_values.map { try #require($0.managedId) }, as: modelType) + + switch result { + case .success: + Issue.record("Not expecting success") + case let .failure(.cocoa(cocoaError)): + expectNoDifference(cocoaError.code, .managedObjectReferentialIntegrity) + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } + } + + @Test + func readAtomically_ManagedIdUrlReferencable_Success() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] + let existingValues = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try _values.map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + return try manageds.map { try modelType.init(managed: $0) } + } + expectNoDifference(existingValues.map { $0.removingManagedIdUrl() }, _values) + + for value in existingValues { + try await verify(value) + } + + let values = try await repository + .readAtomically(existingValues).get() + + expectNoDifference(values, existingValues) + } + + @Test + func readAtomically_ManagedIdUrlReferencable_Failure() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + + let _values = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ].map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + + try repositoryContext.obtainPermanentIDs(for: manageds) + let values = try manageds.map { try modelType.init(managed: $0) } + + repositoryContext.delete(manageds[0]) + try repositoryContext.save() + try repositoryContext.parent?.save() + + return values + } + + let result = await repository + .readAtomically(_values) + + switch result { + case .success: + Issue.record("Not expecting success") + case let .failure(.cocoa(cocoaError)): + expectNoDifference(cocoaError.code, .managedObjectReferentialIntegrity) + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } + } + + @Test + func readAtomically_ManagedIdUrlReferencable_NoManagedIdUrl_Failure() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] - try self.repositoryContext().delete(manageds[0]) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() + let result = await repository + .readAtomically(_values) - return values + switch result { + case .success: + Issue.record("Not expecting success") + case .failure(.noUrlOnItemToMapToObjectId): + break + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } } - let result = try await repository() - .readAtomically(_values.map { try XCTUnwrap($0.managedIdUrl) }, as: modelType) - - switch result { - case .success: - XCTFail("Not expecting success") - case let .failure(.cocoa(cocoaError)): - XCTAssertEqual(cocoaError.code, .managedObjectReferentialIntegrity) - case let .failure(error): - XCTFail("Unexpected error: \(error)") + @Test + func readAtomically_ManagedIdUrl_Success() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] + let existingValues = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try _values.map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + return try manageds.map { try modelType.init(managed: $0) } + } + expectNoDifference(existingValues.map { $0.removingManagedIdUrl() }, _values) + + for value in existingValues { + try await verify(value) + } + + let values = try await repository + .readAtomically(existingValues.compactMap(\.managedIdUrl), as: modelType).get() + + expectNoDifference(values, existingValues) + } + + @Test + func readAtomically_ManagedIdUrl_Failure() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + + let _values = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ].map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + + try repositoryContext.obtainPermanentIDs(for: manageds) + let values = try manageds.map { try modelType.init(managed: $0) } + + repositoryContext.delete(manageds[0]) + try repositoryContext.save() + try repositoryContext.parent?.save() + + return values + } + + let result = try await repository + .readAtomically(_values.map { try #require($0.managedIdUrl) }, as: modelType) + + switch result { + case .success: + Issue.record("Not expecting success") + case let .failure(.cocoa(cocoaError)): + expectNoDifference(cocoaError.code, .managedObjectReferentialIntegrity) + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } + } + + init( + container: NSPersistentContainer, + repositoryContext: NSManagedObjectContext, + repository: CoreDataRepository + ) { + self.container = container + self.repositoryContext = repositoryContext + self.repository = repository } } } diff --git a/Tests/CoreDataRepositoryTests/UpdateTests.swift b/Tests/CoreDataRepositoryTests/UpdateTests.swift index b29938b..6f4352a 100644 --- a/Tests/CoreDataRepositoryTests/UpdateTests.swift +++ b/Tests/CoreDataRepositoryTests/UpdateTests.swift @@ -8,261 +8,293 @@ import CoreData import CoreDataRepository import CustomDump import Internal -import XCTest - -final class UpdateTests: CoreDataXCTestCase { - func testUpdate_Identifiable_Success() async throws { - let modelType = IdentifiableModel_UuidId.self - let _value = modelType.seeded(1) - var existingValue = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try modelType.init(managed: managed) - } +import Testing - let historyTimeStamp = Date() - let transactionAuthor: String = #function +extension CoreDataRepositoryTests { + @Suite + struct UpdateTests: CoreDataTestSuite { + let container: NSPersistentContainer + let repositoryContext: NSManagedObjectContext + let repository: CoreDataRepository - expectNoDifference(existingValue, _value) + @Test + func update_Identifiable_Success() async throws { + let modelType = IdentifiableModel_UuidId.self + let _value = modelType.seeded(1) + var existingValue = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } - try await verify(existingValue) + let historyTimeStamp = Date() + let transactionAuthor: String = #function - existingValue.bool.toggle() + expectNoDifference(existingValue, _value) - let updatedValue = try await repository() - .update(with: existingValue, transactionAuthor: transactionAuthor).get() + try await verify(existingValue) - expectNoDifference(updatedValue, existingValue) + existingValue.bool.toggle() - try await verify(existingValue) - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } + let updatedValue = try await repository + .update(with: existingValue, transactionAuthor: transactionAuthor).get() + + expectNoDifference(updatedValue, existingValue) - func testUpdate_Identifiable_Failure() async throws { - let modelType = IdentifiableModel_UuidId.self - let _value = modelType.seeded(1) - let result = try await repository() - .update(with: _value) - - switch result { - case .success: - XCTFail("Not expecting success") - case .failure(.noMatchFoundWhenReadingItem): - return - case let .failure(error): - XCTFail("Unexpected error: \(error)") + try await verify(existingValue) + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) } - } - func testUpdate_ManagedIdReferencable_Success() async throws { - let modelType = ManagedIdModel_UuidId.self - let _value = modelType.seeded(1) - var existingValue = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try modelType.init(managed: managed) + @Test + func update_Identifiable_Failure() async throws { + let modelType = IdentifiableModel_UuidId.self + let _value = modelType.seeded(1) + let result = await repository + .update(with: _value) + + switch result { + case .success: + Issue.record("Not expecting success") + case .failure(.noMatchFoundWhenReadingItem): + return + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } } - expectNoDifference(existingValue.removingManagedId(), _value) - try await verify(existingValue) + @Test + func update_ManagedIdReferencable_Success() async throws { + let modelType = ManagedIdModel_UuidId.self + let _value = modelType.seeded(1) + var existingValue = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + expectNoDifference(existingValue.removingManagedId(), _value) - let historyTimeStamp = Date() - let transactionAuthor: String = #function + try await verify(existingValue) - existingValue.bool.toggle() + let historyTimeStamp = Date() + let transactionAuthor: String = #function - let updatedValue = try await repository() - .update(with: existingValue, transactionAuthor: transactionAuthor).get() + existingValue.bool.toggle() - expectNoDifference(updatedValue, existingValue) + let updatedValue = try await repository + .update(with: existingValue, transactionAuthor: transactionAuthor).get() - try await verify(existingValue) - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } + expectNoDifference(updatedValue, existingValue) - func testUpdate_ManagedIdReferencable_Failure() async throws { - let modelType = ManagedIdModel_UuidId.self - let _value = modelType.seeded(1) - let result = try await repository() - .update(with: _value) - - switch result { - case .success: - XCTFail("Not expecting success") - case .failure(.noObjectIdOnItem): - return - case let .failure(error): - XCTFail("Unexpected error: \(error)") + try await verify(existingValue) + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) } - } - func testUpdate_ManagedId_Success() async throws { - let modelType = ManagedIdModel_UuidId.self - let _value = modelType.seeded(1) - var existingValue = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try modelType.init(managed: managed) + @Test + func update_ManagedIdReferencable_Failure() async throws { + let modelType = ManagedIdModel_UuidId.self + let _value = modelType.seeded(1) + let result = await repository + .update(with: _value) + + switch result { + case .success: + Issue.record("Not expecting success") + case .failure(.noObjectIdOnItem): + return + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } } - let historyTimeStamp = Date() - let transactionAuthor: String = #function + @Test + func update_ManagedId_Success() async throws { + let modelType = ManagedIdModel_UuidId.self + let _value = modelType.seeded(1) + var existingValue = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } - expectNoDifference(existingValue.removingManagedId(), _value) + let historyTimeStamp = Date() + let transactionAuthor: String = #function - try await verify(existingValue) + expectNoDifference(existingValue.removingManagedId(), _value) - existingValue.bool.toggle() + try await verify(existingValue) - let updatedValue = try await repository() - .update(XCTUnwrap(existingValue.managedId), with: existingValue, transactionAuthor: transactionAuthor).get() + existingValue.bool.toggle() - expectNoDifference(updatedValue, existingValue) + let updatedValue = try await repository + .update(#require(existingValue.managedId), with: existingValue, transactionAuthor: transactionAuthor) + .get() - try await verify(existingValue) - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } + expectNoDifference(updatedValue, existingValue) - func testUpdate_ManagedId_Failure() async throws { - let modelType = ManagedIdModel_UuidId.self - let _value = modelType.seeded(1) - var existingValue = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() + try await verify(existingValue) + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) + } - let value = try modelType.init(managed: managed) + @Test + func update_ManagedId_Failure() async throws { + let modelType = ManagedIdModel_UuidId.self + let _value = modelType.seeded(1) + var existingValue = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() - try self.repositoryContext().delete(managed) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() + let value = try modelType.init(managed: managed) - return value - } + repositoryContext.delete(managed) + try repositoryContext.save() + try repositoryContext.parent?.save() - expectNoDifference(existingValue.removingManagedId(), _value) + return value + } - existingValue.bool.toggle() + expectNoDifference(existingValue.removingManagedId(), _value) - let result = try await repository() - .update(XCTUnwrap(existingValue.managedId), with: existingValue) + existingValue.bool.toggle() - switch result { - case .success: - XCTFail("Not expecting success") - case let .failure(.cocoa(cocoaError)): - XCTAssertEqual(cocoaError.code, .managedObjectReferentialIntegrity) - case let .failure(error): - XCTFail("Unexpected error: \(error)") - } - } + let result = try await repository + .update(#require(existingValue.managedId), with: existingValue) - func testUpdate_ManagedIdUrlReferencable_Success() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - let _value = modelType.seeded(1) - var existingValue = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try modelType.init(managed: managed) + switch result { + case .success: + Issue.record("Not expecting success") + case let .failure(.cocoa(cocoaError)): + expectNoDifference(cocoaError.code, .managedObjectReferentialIntegrity) + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } } - expectNoDifference(existingValue.removingManagedIdUrl(), _value) - try await verify(existingValue) + @Test + func update_ManagedIdUrlReferencable_Success() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + let _value = modelType.seeded(1) + var existingValue = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } + expectNoDifference(existingValue.removingManagedIdUrl(), _value) - let historyTimeStamp = Date() - let transactionAuthor: String = #function + try await verify(existingValue) - existingValue.bool.toggle() + let historyTimeStamp = Date() + let transactionAuthor: String = #function - let updatedValue = try await repository() - .update(with: existingValue, transactionAuthor: transactionAuthor).get() + existingValue.bool.toggle() - expectNoDifference(updatedValue, existingValue) + let updatedValue = try await repository + .update(with: existingValue, transactionAuthor: transactionAuthor).get() - try await verify(existingValue) - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } + expectNoDifference(updatedValue, existingValue) - func testUpdate_ManagedIdUrlReferencable_Failure() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - let _value = modelType.seeded(1) - let result = try await repository() - .update(with: _value) - - switch result { - case .success: - XCTFail("Not expecting success") - case .failure(.noUrlOnItemToMapToObjectId): - return - case let .failure(error): - XCTFail("Unexpected error: \(error)") + try await verify(existingValue) + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) } - } - func testUpdate_ManagedIdUrl_Success() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - let _value = modelType.seeded(1) - var existingValue = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try modelType.init(managed: managed) + @Test + func update_ManagedIdUrlReferencable_Failure() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + let _value = modelType.seeded(1) + let result = await repository + .update(with: _value) + + switch result { + case .success: + Issue.record("Not expecting success") + case .failure(.noUrlOnItemToMapToObjectId): + return + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } } - let historyTimeStamp = Date() - let transactionAuthor: String = #function + @Test + func update_ManagedIdUrl_Success() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + let _value = modelType.seeded(1) + var existingValue = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() + return try modelType.init(managed: managed) + } - expectNoDifference(existingValue.removingManagedIdUrl(), _value) + let historyTimeStamp = Date() + let transactionAuthor: String = #function - try await verify(existingValue) + expectNoDifference(existingValue.removingManagedIdUrl(), _value) - existingValue.bool.toggle() + try await verify(existingValue) - let updatedValue = try await repository() - .update(XCTUnwrap(existingValue.managedIdUrl), with: existingValue, transactionAuthor: transactionAuthor) - .get() + existingValue.bool.toggle() - expectNoDifference(updatedValue, existingValue) + let updatedValue = try await repository + .update( + #require(existingValue.managedIdUrl), + with: existingValue, + transactionAuthor: transactionAuthor + ) + .get() - try await verify(existingValue) - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } + expectNoDifference(updatedValue, existingValue) - func testUpdate_ManagedIdUrl_Failure() async throws { - let modelType = ManagedIdUrlModel_UuidId.self - let _value = modelType.seeded(1) - var existingValue = try await repositoryContext().perform(schedule: .immediate) { - let managed = try _value.asManagedModel(in: self.repositoryContext()) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() + try await verify(existingValue) + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) + } - let value = try modelType.init(managed: managed) + @Test + func update_ManagedIdUrl_Failure() async throws { + let modelType = ManagedIdUrlModel_UuidId.self + let _value = modelType.seeded(1) + var existingValue = try await repositoryContext.perform(schedule: .immediate) { + let managed = try _value.asManagedModel(in: repositoryContext) + try repositoryContext.save() + try repositoryContext.parent?.save() - try self.repositoryContext().delete(managed) - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() + let value = try modelType.init(managed: managed) - return value - } + repositoryContext.delete(managed) + try repositoryContext.save() + try repositoryContext.parent?.save() + + return value + } - expectNoDifference(existingValue.removingManagedIdUrl(), _value) + expectNoDifference(existingValue.removingManagedIdUrl(), _value) - existingValue.bool.toggle() + existingValue.bool.toggle() - let result = try await repository() - .update(XCTUnwrap(existingValue.managedIdUrl), with: existingValue) + let result = try await repository + .update(#require(existingValue.managedIdUrl), with: existingValue) + + switch result { + case .success: + Issue.record("Not expecting success") + case let .failure(.cocoa(cocoaError)): + expectNoDifference(cocoaError.code, .managedObjectReferentialIntegrity) + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } + } - switch result { - case .success: - XCTFail("Not expecting success") - case let .failure(.cocoa(cocoaError)): - XCTAssertEqual(cocoaError.code, .managedObjectReferentialIntegrity) - case let .failure(error): - XCTFail("Unexpected error: \(error)") + init( + container: NSPersistentContainer, + repositoryContext: NSManagedObjectContext, + repository: CoreDataRepository + ) { + self.container = container + self.repositoryContext = repositoryContext + self.repository = repository } } } diff --git a/Tests/CoreDataRepositoryTests/Update_BatchTests.swift b/Tests/CoreDataRepositoryTests/Update_BatchTests.swift index 4daf0c0..ce3d307 100644 --- a/Tests/CoreDataRepositoryTests/Update_BatchTests.swift +++ b/Tests/CoreDataRepositoryTests/Update_BatchTests.swift @@ -8,118 +8,139 @@ import CoreData import CoreDataRepository import CustomDump import Internal -import XCTest - -final class Update_BatchTests: CoreDataXCTestCase { - // MARK: Non Atomic - - func testUpdate_Identifiable_Success() async throws { - let modelType = IdentifiableModel_UuidId.self - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - var existingValues = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try _values.map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try manageds.map { try modelType.init(managed: $0) } +import Testing + +extension CoreDataRepositoryTests { + @Suite + struct Update_BatchTests: CoreDataTestSuite { + let container: NSPersistentContainer + let repositoryContext: NSManagedObjectContext + let repository: CoreDataRepository + + // MARK: Non Atomic + + @Test + func update_Identifiable_Success() async throws { + let modelType = IdentifiableModel_UuidId.self + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] + var existingValues = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try _values.map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + return try manageds.map { try modelType.init(managed: $0) } + } + expectNoDifference(existingValues, _values) + + for value in existingValues { + try await verify(value) + } + + let historyTimeStamp = Date() + let transactionAuthor: String = #function + + existingValues = existingValues.map { value in + var value = value + value.int += 1 + return value + } + + let (successful, failed) = await repository + .update(existingValues, transactionAuthor: transactionAuthor) + + expectNoDifference(successful.count, _values.count) + expectNoDifference(failed.count, 0) + for value in successful { + try await verify(value) + } + try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) } - expectNoDifference(existingValues, _values) - for value in existingValues { - try await verify(value) + @Test + func update_Identifiable_Failure() async throws { + let modelType = IdentifiableModel_UuidId.self + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] + let (successful, failed) = await repository + .update(_values) + + expectNoDifference(successful.count, 0) + expectNoDifference(failed.count, _values.count) } - let historyTimeStamp = Date() - let transactionAuthor: String = #function - - existingValues = existingValues.map { value in - var value = value - value.int += 1 - return value - } - - let (successful, failed) = try await repository() - .update(existingValues, transactionAuthor: transactionAuthor) - - XCTAssertEqual(successful.count, _values.count) - XCTAssertEqual(failed.count, 0) - for value in successful { - try await verify(value) + // MARK: Atomic + + @Test + func updateAtomically_Identifiable_Success() async throws { + let modelType = IdentifiableModel_UuidId.self + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] + let existingValues = try await repositoryContext.perform(schedule: .immediate) { + let manageds = try _values.map { try $0.asManagedModel(in: repositoryContext) } + try repositoryContext.save() + try repositoryContext.parent?.save() + return try manageds.map { try modelType.init(managed: $0) } + } + expectNoDifference(existingValues, _values) + + for value in existingValues { + try await verify(value) + } + + let updatedValues = try await repository + .updateAtomically(existingValues).get() + + for value in updatedValues { + try await verify(value) + } } - try verify(transactionAuthor: transactionAuthor, timeStamp: historyTimeStamp) - } - - func testUpdate_Identifiable_Failure() async throws { - let modelType = IdentifiableModel_UuidId.self - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - let (successful, failed) = try await repository() - .update(_values) - - XCTAssertEqual(successful.count, 0) - XCTAssertEqual(failed.count, _values.count) - } - // MARK: Atomic - - func testUpdateAtomically_Identifiable_Success() async throws { - let modelType = IdentifiableModel_UuidId.self - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - let existingValues = try await repositoryContext().perform(schedule: .immediate) { - let manageds = try _values.map { try $0.asManagedModel(in: self.repositoryContext()) } - try self.repositoryContext().save() - try self.repositoryContext().parent?.save() - return try manageds.map { try modelType.init(managed: $0) } + @Test + func updateAtomically_Identifiable_Failure() async throws { + let modelType = IdentifiableModel_UuidId.self + let _values = [ + modelType.seeded(1), + modelType.seeded(2), + modelType.seeded(3), + modelType.seeded(4), + modelType.seeded(5), + ] + let result = await repository + .updateAtomically(_values) + + switch result { + case .success: + Issue.record("Not expecting success") + case .failure(.noMatchFoundWhenReadingItem): + break + case let .failure(error): + Issue.record("Unexpected error: \(error)") + } } - expectNoDifference(existingValues, _values) - - for value in existingValues { - try await verify(value) - } - - let updatedValues = try await repository() - .updateAtomically(existingValues).get() - - for value in updatedValues { - try await verify(value) - } - } - func testUpdateAtomically_Identifiable_Failure() async throws { - let modelType = IdentifiableModel_UuidId.self - let _values = [ - modelType.seeded(1), - modelType.seeded(2), - modelType.seeded(3), - modelType.seeded(4), - modelType.seeded(5), - ] - let result = try await repository() - .updateAtomically(_values) - - switch result { - case .success: - XCTFail("Not expecting success") - case .failure(.noMatchFoundWhenReadingItem): - break - case let .failure(error): - XCTFail("Unexpected error: \(error)") + init( + container: NSPersistentContainer, + repositoryContext: NSManagedObjectContext, + repository: CoreDataRepository + ) { + self.container = container + self.repositoryContext = repositoryContext + self.repository = repository } } }