Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OfferingsManager: don't consider timeouts as configuration errors #2493

Merged
merged 1 commit into from
May 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 19 additions & 7 deletions Sources/Purchasing/OfferingsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,7 @@ private extension OfferingsManager {
let products = result.value ?? []

guard products.isEmpty == false else {
self.handleOfferingsUpdateError(
.configurationError(Strings.offering.configuration_error_products_not_found.description,
underlyingError: result.error as NSError?),
completion: completion
)
self.handleOfferingsUpdateError(Self.createErrorForEmptyResult(result.error), completion: completion)
return
}

Expand Down Expand Up @@ -168,6 +164,16 @@ private extension OfferingsManager {
}
}

private static func createErrorForEmptyResult(_ error: PurchasesError?) -> OfferingsManager.Error {
if let purchasesError = error,
case ErrorCode.productRequestTimedOut = purchasesError.error {
return .timeout(purchasesError)
} else {
return .configurationError(Strings.offering.configuration_error_products_not_found.description,
underlyingError: error?.asPublicError)
}
}

func handleOfferingsUpdateError(
_ error: Error,
completion: (@MainActor @Sendable (Result<Offerings, Error>) -> Void)?
Expand Down Expand Up @@ -213,10 +219,11 @@ extension OfferingsManager: @unchecked Sendable {}

extension OfferingsManager {

enum Error: Swift.Error, Equatable {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This didn't need to be Equatable. Small but useful code size decrease in the SDK 💪🏻

enum Error: Swift.Error {

case backendError(BackendError)
case configurationError(String, NSError?, ErrorSource)
case configurationError(String, PublicError?, ErrorSource)
case timeout(PurchasesError)
case noOfferingsFound(ErrorSource)
case missingProducts(identifiers: Set<String>, ErrorSource)

Expand All @@ -231,6 +238,9 @@ extension OfferingsManager.Error: PurchasesErrorConvertible {
case let .backendError(backendError):
return backendError.asPurchasesError

case let .timeout(underlyingError):
return underlyingError

case let .configurationError(errorMessage, underlyingError, source):
return ErrorUtils.configurationError(message: errorMessage,
underlyingError: underlyingError,
Expand Down Expand Up @@ -293,6 +303,7 @@ extension OfferingsManager.Error: CustomNSError {
var errorDescription: String? {
switch self {
case .backendError: return nil
case let .timeout(underlyingError): return underlyingError.error.localizedDescription
case let .configurationError(message, _, _): return message
case .noOfferingsFound: return nil
case .missingProducts: return nil
Expand All @@ -303,6 +314,7 @@ extension OfferingsManager.Error: CustomNSError {
switch self {
case let .backendError(.networkError(error)): return error
case let .backendError(error): return error
case let .timeout(underlyingError): return underlyingError
case let .configurationError(_, error, _): return error
case .noOfferingsFound: return nil
case .missingProducts: return nil
Expand Down
48 changes: 45 additions & 3 deletions Tests/UnitTests/Purchasing/OfferingsManagerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,13 @@ extension OfferingsManagerTests {

// then
expect(result).to(beFailure())
expect(result?.error) == .backendError(.unexpectedBackendResponse(.customerInfoNil))

switch result?.error {
case .backendError(.unexpectedBackendResponse(.customerInfoNil, _, _)):
break
default:
fail("Unexpected result")
}
}

func testOfferingsForAppUserIDReturnsConfigurationErrorIfBackendReturnsEmpty() throws {
Expand Down Expand Up @@ -180,6 +186,30 @@ extension OfferingsManagerTests {
}
}

func testOfferingsReturnsTimeoutErrorIfProductRequestTimesOut() throws {
// given
let timeoutError = ErrorUtils.productRequestTimedOutError()

self.mockOfferings.stubbedGetOfferingsCompletionResult = .success(
MockData.anyBackendOfferingsResponse
)
self.mockProductsManager.stubbedProductsCompletionResult = .failure(timeoutError)

// when
let result = waitUntilValue { completed in
self.offeringsManager.offerings(appUserID: MockData.anyAppUserID) {
completed($0)
}
}

// then
expect(result).to(beFailure())
expect(result?.error).to(matchError(OfferingsManager.Error.timeout(timeoutError)))

let underlyingError = try XCTUnwrap(result?.error?.errorUserInfo[NSUnderlyingErrorKey] as? NSError)
expect(underlyingError).to(matchError(timeoutError))
}

func testOfferingsLogsErrorInformationIfBackendReturnsEmpty() throws {
let logger = TestLogHandler()

Expand Down Expand Up @@ -274,7 +304,13 @@ extension OfferingsManagerTests {

// then
expect(result).to(beFailure())
expect(result?.error) == .noOfferingsFound()

switch result?.error {
case .noOfferingsFound:
break
default:
fail("Unexpected result")
}
}

func testOfferingsForAppUserIDReturnsUnexpectedBackendErrorIfBadBackendRequest() throws {
Expand All @@ -291,7 +327,13 @@ extension OfferingsManagerTests {

// then
expect(result).to(beFailure())
expect(result?.error) == .backendError(MockData.unexpectedBackendResponseError)

switch result?.error {
case .backendError(MockData.unexpectedBackendResponseError):
break
default:
fail("Unexpected result")
}
}

func testFailBackendDeviceCacheClearsOfferingsCache() {
Expand Down