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

ErrorUtils.purchasesError(withSKError:): handle URLErrors #3346

Merged
merged 1 commit into from
Oct 26, 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
9 changes: 9 additions & 0 deletions Sources/Error Handling/ErrorUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,15 @@ enum ErrorUtils {
return skError.asPurchasesError
case let purchasesError as PurchasesError:
return purchasesError
case is URLError:
// Some StoreKit APIs can return `URLError`s.
// See https://github.com/RevenueCat/purchases-ios/issues/3343
return NetworkError.networkError(
error as NSError,
.init(file: fileName,
function: functionName,
line: line)
).asPurchasesError
default:
return ErrorUtils.unknownError(
error: error,
Expand Down
9 changes: 1 addition & 8 deletions Sources/Networking/HTTPClient/HTTPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -527,14 +527,7 @@ private extension NetworkError {
Logger.error(blockedError.description)
self = blockedError
} else {
let nsError = error as NSError

switch (nsError.domain, nsError.code) {
case (NSURLErrorDomain, NSURLErrorNotConnectedToInternet):
self = .offlineConnection()
default:
self = .networkError(error)
}
self = .networkError(error as NSError)
}
}

Expand Down
16 changes: 5 additions & 11 deletions Sources/Networking/HTTPClient/NetworkError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import Foundation
enum NetworkError: Swift.Error, Equatable {

case decoding(NSError, Source)
case offlineConnection(Source)
case networkError(NSError, Source)
case dnsError(failedURL: URL, resolvedHost: String?, Source)
case unableToCreateRequest(path: String, Source)
Expand All @@ -45,12 +44,6 @@ extension NetworkError {
return .decoding(error as NSError, .init(file: file, function: function, line: line))
}

static func offlineConnection(
file: String = #fileID, function: String = #function, line: UInt = #line
) -> Self {
return .offlineConnection(.init(file: file, function: function, line: line))
}

static func networkError(
_ error: Error,
file: String = #fileID, function: String = #function, line: UInt = #line
Expand Down Expand Up @@ -113,7 +106,8 @@ extension NetworkError: PurchasesErrorConvertible {
line: source.line
)

case let .offlineConnection(source):
case let .networkError(error, source)
where error.domain == NSURLErrorDomain && error.code == NSURLErrorNotConnectedToInternet:
return ErrorUtils.offlineConnectionError(
fileName: source.file,
functionName: source.function,
Expand Down Expand Up @@ -183,7 +177,8 @@ extension NetworkError: DescribableError {
case let .decoding(error, _):
return error.localizedDescription

case .offlineConnection:
case let .networkError(error, _)
where error.domain == NSURLErrorDomain && error.code == NSURLErrorNotConnectedToInternet:
return ErrorCode.offlineConnectionError.description

case let .networkError(error, _):
Expand Down Expand Up @@ -240,8 +235,7 @@ extension NetworkError {
case let .errorResponse(_, statusCode, _):
return statusCode

case .offlineConnection,
.decoding,
case .decoding,
.networkError,
.dnsError,
.unableToCreateRequest,
Expand Down
6 changes: 4 additions & 2 deletions Tests/UnitTests/Networking/HTTPClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1282,6 +1282,7 @@ final class HTTPClientTests: BaseHTTPClientTests<MockETagManager> {
let path: HTTPRequest.Path = .mockPath

let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorNotConnectedToInternet)
let expectedError: NetworkError = .networkError(error)

stub(condition: isPath(path)) { _ in
let response = HTTPStubsResponse.emptySuccessResponse()
Expand All @@ -1295,8 +1296,9 @@ final class HTTPClientTests: BaseHTTPClientTests<MockETagManager> {
}
}

expect(obtainedError).toNot(beNil())
expect(obtainedError) == .offlineConnection()
// Can't compare the errors directly because `obtainedError` has additional userInfo.
expect(obtainedError?.asPurchasesError)
.to(matchError(expectedError.asPurchasesError))
}

func testErrorIsLoggedAndReturnsDNSErrorWhenGETRequestFailedWithDNSError() {
Expand Down
11 changes: 11 additions & 0 deletions Tests/UnitTests/Networking/NetworkErrorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -381,4 +381,15 @@ extension NetworkError {
)
}

static func offlineConnection(
file: String = #fileID, function: String = #function, line: UInt = #line
) -> Self {
return .networkError(
NSError(domain: NSURLErrorDomain, code: NSURLErrorNotConnectedToInternet),
file: file,
function: function,
line: line
)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,13 @@ class PaywallEventsManagerTests: TestCase {
func testFlushWithUnsuccessfulPostError() async throws {
let event = await self.storeRandomEvent()
let storedEvent: PaywallStoredEvent = .init(event: event, userID: Self.userID)
let expectedError: NetworkError = .offlineConnection()

self.api.stubbedPostPaywallEventsCompletionResult = .networkError(.offlineConnection())
self.api.stubbedPostPaywallEventsCompletionResult = .networkError(expectedError)
do {
_ = try await self.manager.flushEvents(count: 1)
fail("Expected error")
} catch BackendError.networkError(.offlineConnection) {
} catch BackendError.networkError(expectedError) {
// Expected
} catch {
throw error
Expand Down
8 changes: 8 additions & 0 deletions Tests/UnitTests/Purchasing/ErrorUtilsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,14 @@ class ErrorUtilsTests: TestCase {
.to(matchError(error))
}

func testPurchasesErrorWithNetworkErrorAsSKError() {
let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorNotConnectedToInternet)
let expectedError = ErrorUtils.offlineConnectionError()

expect(ErrorUtils.purchasesError(withSKError: error))
.to(matchError(expectedError))
}

func testPurchaseErrorsAreLoggedAsApppleErrors() {
let underlyingError = NSError(domain: SKErrorDomain, code: SKError.Code.paymentInvalid.rawValue)
let error = ErrorUtils.purchaseNotAllowedError(error: underlyingError)
Expand Down