Skip to content

Commit

Permalink
[Shipping labels] Upsert create packages response after saving packag…
Browse files Browse the repository at this point in the history
…es remotely (#14710)
  • Loading branch information
rachelmcr authored Dec 18, 2024
2 parents 0a2b752 + 0a8fb41 commit 0daaa02
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 10 deletions.
5 changes: 5 additions & 0 deletions Yosemite/Yosemite/Model/Model.swift
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,11 @@ public typealias FeatureAnnouncementCampaignSettings = Storage.FeatureAnnounceme
public typealias AnalyticsCard = Storage.AnalyticsCard
public typealias DashboardCard = Storage.DashboardCard
public typealias StorageWooShippingPackagesResponse = Storage.WooShippingPackagesResponse
public typealias StorageWooShippingCarrierPredefinedOptions = Storage.WooShippingCarrierPredefinedOptions
public typealias StorageWooShippingPredefinedOption = Storage.WooShippingPredefinedOption
public typealias StorageWooShippingPredefinedPackage = Storage.WooShippingPredefinedPackage
public typealias StorageWooShippingCustomPackage = Storage.WooShippingCustomPackage
public typealias StorageWooShippingSavedPredefinedPackage = Storage.WooShippingSavedPredefinedPackage

// MARK: - Internal ReadOnly Models

Expand Down
96 changes: 86 additions & 10 deletions Yosemite/Yosemite/Stores/WooShippingStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,12 @@ private extension WooShippingStore {
customPackage: WooShippingCustomPackage? = nil,
predefinedOption: WooShippingPredefinedSavedOption? = nil,
completion: @escaping (Result<WooShippingCreatePackageResponse, PackageCreationError>) -> Void) {
remote.createPackage(siteID: siteID, customPackage: customPackage, predefinedOption: predefinedOption) { result in
remote.createPackage(siteID: siteID, customPackage: customPackage, predefinedOption: predefinedOption) { [weak self] result in
switch result {
case .success(let packages):
completion(.success(packages))
self?.upsertCreatePackagesResponseInBackground(readOnlyPackages: packages, siteID: siteID, onCompletion: {
completion(.success(packages))
})
case .failure(let error):
completion(.failure(PackageCreationError(error: error)))
}
Expand Down Expand Up @@ -237,7 +239,6 @@ private extension WooShippingStore {
// MARK: - Storage
private extension WooShippingStore {
/// Updates (OR Inserts) the specified ReadOnly WooShippingPackagesResponse Entities *in a background thread*.
/// Also deletes existing packages if requested.
/// `onCompletion` will be called on the main thread!
///
func upsertPackagesResponseInBackground(readOnlyPackages: Networking.WooShippingPackagesResponse,
Expand All @@ -249,6 +250,18 @@ private extension WooShippingStore {
}, completion: onCompletion, on: .main)
}

/// Updates (OR Inserts) the specified ReadOnly WooShippingCreatePackageResponse Entities *in a background thread*.
/// `onCompletion` will be called on the main thread!
///
func upsertCreatePackagesResponseInBackground(readOnlyPackages: Networking.WooShippingCreatePackageResponse,
siteID: Int64,
onCompletion: @escaping () -> Void) {
storageManager.performAndSave({ [weak self] storage in
guard let self else { return }
upsertCreatePackageResponse(readOnlyPackages: readOnlyPackages, siteID: siteID, in: storage)
}, completion: onCompletion, on: .main)
}

/// Updates (OR Inserts) the specified ReadOnly `WooShippingPackagesResponse` Entities into the Storage Layer.
///
/// - Parameters:
Expand All @@ -261,8 +274,23 @@ private extension WooShippingStore {

storagePackages.update(with: readOnlyPackages)
handleAllPredefinedOptions(readOnlyPackages, storagePackages, storage)
handleCustomPackages(readOnlyPackages, storagePackages, storage)
handleSavedPredefinedPackages(readOnlyPackages, storagePackages, storage)
handleCustomPackages(readOnlyPackages.customPackages, storagePackages, storage)
handleSavedPredefinedPackages(readOnlyPackages.savedPredefinedPackages, storagePackages, storage)
}

/// Updates (OR Inserts) the specified ReadOnly `WooShippingCreatePackageResponse` Entities into the Storage Layer.
///
/// - Parameters:
/// - readOnlyPackages: Remote `WooShippingCreatePackageResponse` to be persisted.
/// - siteID: Site ID to be associated with the packages.
/// - storage: Where we should save all the things!
///
func upsertCreatePackageResponse(readOnlyPackages: Networking.WooShippingCreatePackageResponse, siteID: Int64, in storage: StorageType) {
let storagePackages = storage.loadPackages(siteID: siteID) ?? storage.insertNewObject(ofType: Storage.WooShippingPackagesResponse.self)
storagePackages.siteID = siteID

handleCustomPackages(readOnlyPackages.customPackages, storagePackages, storage)
handleSavedPredefinedOptions(readOnlyPackages.predefinedOptions, storagePackages, storage)
}

/// Updates, inserts, or prunes the provided Storage.WooShippingPackagesResponse's allPredefinedOptions
Expand Down Expand Up @@ -326,9 +354,9 @@ private extension WooShippingStore {
}

/// Updates, inserts, or prunes the provided Storage.WooShippingPackagesResponse's customPackages
/// using the provided read-only WooShippingPackagesResponse's customPackages
/// using the provided read-only WooShippingCustomPackages
///
func handleCustomPackages(_ readOnlyPackages: Networking.WooShippingPackagesResponse,
func handleCustomPackages(_ readOnlyCustomPackages: [Networking.WooShippingCustomPackage],
_ storagePackages: Storage.WooShippingPackagesResponse,
_ storage: StorageType) {
// Remove all previous custom packages, they will be deleted as they have the `cascade` delete rule
Expand All @@ -337,7 +365,7 @@ private extension WooShippingStore {
}

// Creates and adds `storageCustomPackages` from `readOnlyPackages.customPackages`
let storageCustomPackages = readOnlyPackages.customPackages.map { readOnlyPackage -> Storage.WooShippingCustomPackage in
let storageCustomPackages = readOnlyCustomPackages.map { readOnlyPackage -> Storage.WooShippingCustomPackage in
let storagePackage = storage.insertNewObject(ofType: Storage.WooShippingCustomPackage.self)
storagePackage.update(with: readOnlyPackage)
return storagePackage
Expand All @@ -348,7 +376,7 @@ private extension WooShippingStore {
/// Updates, inserts, or prunes the provided Storage.WooShippingPackagesResponse's savedPredefinedPackages
/// using the provided read-only WooShippingPackagesResponse's savedPredefinedPackages
///
func handleSavedPredefinedPackages(_ readOnlyPackages: Networking.WooShippingPackagesResponse,
func handleSavedPredefinedPackages(_ readOnlySavedPackages: [Networking.WooShippingSavedPredefinedPackage],
_ storagePackages: Storage.WooShippingPackagesResponse,
_ storage: StorageType) {
// Remove all previous saved predefined packages, they will be deleted as they have the `cascade` delete rule
Expand All @@ -357,7 +385,7 @@ private extension WooShippingStore {
}

// Creates and adds `storageSavedPredefinedPackages` from `readOnlyPackages.savedPredefinedPackages`
let storageSavedPredefinedPackages = readOnlyPackages.savedPredefinedPackages.map { readOnlyPackage -> Storage.WooShippingSavedPredefinedPackage in
let storageSavedPredefinedPackages = readOnlySavedPackages.map { readOnlyPackage -> Storage.WooShippingSavedPredefinedPackage in
let storagePackage = storage.insertNewObject(ofType: Storage.WooShippingSavedPredefinedPackage.self)
storagePackage.update(with: readOnlyPackage)
handlePredefinedPackage(readOnlyPackage, storagePackage, storage)
Expand All @@ -366,6 +394,54 @@ private extension WooShippingStore {
storagePackages.addToSavedPredefinedPackages(NSSet(array: storageSavedPredefinedPackages))
}

/// Updates, inserts, or prunes the provided Storage.WooShippingPackagesResponse's savedPredefinedPackages
/// using the provided read-only WooShippingPredefinedSavedOptions
///
func handleSavedPredefinedOptions(_ readOnlySavedOptions: [WooShippingPredefinedSavedOption],
_ storagePackages: Storage.WooShippingPackagesResponse,
_ storage: StorageType) {
guard let storagePredefinedOptions: [StorageWooShippingCarrierPredefinedOptions] = storagePackages.allPredefinedOptions?.toArray() else {
return
}
let readOnlyPredefinedOptions = storagePredefinedOptions.map({ $0.toReadOnly() })
let savedPackages = transformSavedPredefinedOptions(readOnlySavedOptions, allPredefinedOptions: readOnlyPredefinedOptions)
handleSavedPredefinedPackages(savedPackages, storagePackages, storage)
}

/// Transforms the provided `WooShippingPredefinedSavedOption`s into `WooShippingSavedPredefinedPackage`s to save in storage.
///
func transformSavedPredefinedOptions(_ options: [WooShippingPredefinedSavedOption],
allPredefinedOptions: [WooShippingCarrierPredefinedOptions]) -> [WooShippingSavedPredefinedPackage] {
// helper function for creating jointIDs for easier checking if package should be used or not
func jointID(carrierID: String, packageID: String) -> String {
return "\(carrierID)-\(packageID)"
}

var jointIDs: [String] = []
for option in options {
for packageID in option.predefinedPackageIDs {
jointIDs.append(jointID(carrierID: option.id, packageID: packageID))
}
}

var allSavedOptions: [WooShippingSavedPredefinedPackage] = []

// use predefined saved packages from list of all packages
// since the response gives us IDs we need to get them manually from the list
for carrier in allPredefinedOptions {
let carrierID = carrier.carrierID
for option in carrier.predefinedOptions {
for package in option.predefinedPackages {
if jointIDs.contains(jointID(carrierID: carrierID, packageID: package.id)) {
allSavedOptions.append(WooShippingSavedPredefinedPackage(groupTitle: option.title, providerID: option.providerID, package: package))
}
}
}
}

return allSavedOptions
}

/// Updates or inserts the provided Storage.WooShippingSavedPredefinedPackage's package
/// using the provided read-only WooShippingSavedPredefinedPackage's package
///
Expand Down
34 changes: 34 additions & 0 deletions Yosemite/YosemiteTests/Mocks/MockStorageManager+Sample.swift
Original file line number Diff line number Diff line change
Expand Up @@ -248,4 +248,38 @@ extension MockStorageManager {

return newNote
}

/// Inserts a new sample Woo Shipping packages response into the specified content.
///
@discardableResult
func insertSamplePackages(readOnlyPackages: WooShippingPackagesResponse) -> StorageWooShippingPackagesResponse {
let newPackages = viewStorage.insertNewObject(ofType: StorageWooShippingPackagesResponse.self)
newPackages.update(with: readOnlyPackages)
readOnlyPackages.allPredefinedOptions.forEach { carrierOption in
let newCarrierOption = viewStorage.insertNewObject(ofType: StorageWooShippingCarrierPredefinedOptions.self)
newCarrierOption.update(with: carrierOption)
newPackages.addToAllPredefinedOptions(newCarrierOption)
carrierOption.predefinedOptions.forEach { predefinedOption in
let newPredefinedOption = viewStorage.insertNewObject(ofType: StorageWooShippingPredefinedOption.self)
newPredefinedOption.update(with: predefinedOption)
newCarrierOption.addToPredefinedOptions(newPredefinedOption)
predefinedOption.predefinedPackages.forEach { package in
let newPackage = viewStorage.insertNewObject(ofType: StorageWooShippingPredefinedPackage.self)
newPackage.update(with: package)
newPredefinedOption.addToPredefinedPackages(newPackage)
}
}
}
readOnlyPackages.customPackages.forEach { customPackage in
let newCustomPackage = viewStorage.insertNewObject(ofType: StorageWooShippingCustomPackage.self)
newCustomPackage.update(with: customPackage)
newPackages.addToCustomPackages(newCustomPackage)
}
readOnlyPackages.savedPredefinedPackages.forEach { savedPackage in
let newSavedPackage = viewStorage.insertNewObject(ofType: StorageWooShippingSavedPredefinedPackage.self)
newSavedPackage.update(with: savedPackage)
newPackages.addToSavedPredefinedPackages(newSavedPackage)
}
return newPackages
}
}
62 changes: 62 additions & 0 deletions Yosemite/YosemiteTests/Stores/WooShippingStoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,40 @@ final class WooShippingStoreTests: XCTestCase {
XCTAssertTrue(result.isFailure)
}

func test_createPackage_when_successful_then_upserts_packages_into_storage() throws {
// Given
let store = WooShippingStore(dispatcher: dispatcher, storageManager: storageManager, network: network)
network.simulateResponse(requestUrlSuffix: "packages", filename: "wooshipping-create-package-success")
storageManager.insertSamplePackages(readOnlyPackages: .init(siteID: sampleSiteID,
customPackages: [],
savedPredefinedPackages: [],
allPredefinedOptions: [sampleCarrierPredefinedOptions()]))

// Confidence check
XCTAssertEqual(storageManager.viewStorage.countObjects(ofType: StorageWooShippingCustomPackage.self), 0)
XCTAssertEqual(storageManager.viewStorage.countObjects(ofType: StorageWooShippingSavedPredefinedPackage.self), 0)

// When
let onSuccess: Bool = waitFor { promise in
let action = WooShippingAction.createPackage(siteID: self.sampleSiteID,
customPackage: .fake(),
predefinedOption: .fake()) { result in
promise(result.isSuccess)
}
store.onAction(action)
}

let storedPackages = try XCTUnwrap(storageManager.viewStorage.firstObject(ofType: StorageWooShippingPackagesResponse.self)).toReadOnly()

// Then
XCTAssertTrue(onSuccess)
XCTAssertEqual(storageManager.viewStorage.countObjects(ofType: StorageWooShippingCustomPackage.self), 5)
XCTAssertEqual(storageManager.viewStorage.countObjects(ofType: StorageWooShippingSavedPredefinedPackage.self), 2)
XCTAssertEqual(storedPackages.siteID, sampleSiteID)
XCTAssertEqual(storedPackages.customPackages.count, 5)
XCTAssertEqual(storedPackages.savedPredefinedPackages.count, 2)
}

// MARK: `loadLabelRates`

func test_loadLabelRates_returns_success_response_with_rates() throws {
Expand Down Expand Up @@ -501,4 +535,32 @@ private extension WooShippingStoreTests {
dimensions: "12 x 12 x 12",
boxWeight: 0.01)
}

func sampleCarrierPredefinedOptions() -> WooShippingCarrierPredefinedOptions {
WooShippingCarrierPredefinedOptions(carrierID: "usps",
predefinedOptions: [.init(title: "pri_flat_boxes",
providerID: "usps",
predefinedPackages: [.init(id: "small_flat_box",
name: "",
isLetter: false,
dimensions: "",
boxWeight: "",
groupId: "")]),
.init(title: "pri_flat_envelopes",
providerID: "usps",
predefinedPackages: [.init(id: "flat_envelope",
name: "",
isLetter: true,
dimensions: "",
boxWeight: "",
groupId: "")]),
.init(title: "pri_flat_boxes",
providerID: "usps",
predefinedPackages: [.init(id: "medium_flat_box_top",
name: "",
isLetter: false,
dimensions: "",
boxWeight: "",
groupId: "")])])
}
}

0 comments on commit 0daaa02

Please sign in to comment.