diff --git a/Sources/FoundationExtensions/JSONDecoder+Extensions.swift b/Sources/FoundationExtensions/JSONDecoder+Extensions.swift index 275191acba..6c24c0a401 100644 --- a/Sources/FoundationExtensions/JSONDecoder+Extensions.swift +++ b/Sources/FoundationExtensions/JSONDecoder+Extensions.swift @@ -129,3 +129,25 @@ extension Encodable { } } + +extension JSONEncoder { + + static let `default`: JSONEncoder = { + let encoder = JSONEncoder() + encoder.keyEncodingStrategy = .convertToSnakeCase + + return encoder + }() + +} + +extension JSONDecoder { + + static let `default`: JSONDecoder = { + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + + return decoder + }() + +} diff --git a/Sources/Networking/ETagManager.swift b/Sources/Networking/ETagManager.swift index 73ba1c4dce..dcc2d94648 100644 --- a/Sources/Networking/ETagManager.swift +++ b/Sources/Networking/ETagManager.swift @@ -39,13 +39,12 @@ class ETagManager { } func httpResultFromCacheOrBackend(with response: HTTPURLResponse, - jsonObject: [String: Any]?, - error: Error?, + jsonObject: [String: Any], request: URLRequest, retried: Bool) -> HTTPResponse? { let statusCode: HTTPStatusCode = .init(rawValue: response.statusCode) let resultFromBackend = HTTPResponse(statusCode: statusCode, jsonObject: jsonObject) - guard error == nil else { return resultFromBackend } + let headersInResponse = response.allHeaderFields let eTagInResponse: String? = headersInResponse[ETagManager.eTagHeaderName] as? String ?? diff --git a/Sources/Networking/HTTPClient.swift b/Sources/Networking/HTTPClient.swift index f7817032e3..82432b2b53 100644 --- a/Sources/Networking/HTTPClient.swift +++ b/Sources/Networking/HTTPClient.swift @@ -16,7 +16,7 @@ import Foundation class HTTPClient { typealias RequestHeaders = [String: String] - typealias Completion = ((_ statusCode: HTTPStatusCode, _ result: Result<[String: Any], Error>) -> Void) + typealias Completion = (Result) -> Void private let session: URLSession internal let systemInfo: SystemInfo @@ -60,6 +60,7 @@ extension HTTPClient { } private extension HTTPClient { + struct State { var queuedRequests: [Request] var currentSerialRequest: Request? @@ -67,6 +68,7 @@ private extension HTTPClient { static let initial: Self = .init(queuedRequests: [], currentSerialRequest: nil) } + } private extension HTTPClient { @@ -160,62 +162,114 @@ private extension HTTPClient { self.start(request: request) } - func handle(urlResponse: URLResponse?, - request: Request, - urlRequest: URLRequest, - data: Data?, - error networkError: Error?) { - var statusCode: HTTPStatusCode = .networkConnectTimeoutError - var jsonObject: [String: Any]? - var httpResponse: HTTPResponse? = HTTPResponse(statusCode: statusCode, jsonObject: jsonObject) - var receivedJSONError: Error? - - if networkError == nil { - if let httpURLResponse = urlResponse as? HTTPURLResponse { - statusCode = .init(rawValue: httpURLResponse.statusCode) - Logger.debug(Strings.network.api_request_completed(request.httpRequest, httpCode: statusCode)) - - if statusCode == .notModified || data == nil { - jsonObject = [:] - } else if let data = data { - do { - jsonObject = try JSONSerialization.jsonObject(with: data) as? [String: Any] - } catch let jsonError { - Logger.error(Strings.network.parsing_json_error(error: jsonError)) - - let dataAsString = String(data: data, encoding: .utf8) ?? "" - Logger.error(Strings.network.json_data_received(dataString: dataAsString)) - - receivedJSONError = jsonError - } + // swiftlint:disable:next function_body_length + func parse(urlResponse: URLResponse?, + request: Request, + urlRequest: URLRequest, + data: Data?, + error networkError: Error? + ) -> (result: Result, shouldRetry: Bool) { + if let networkError = networkError { + return ( + result: .failure(networkError) + .mapError { error in + if self.dnsChecker.isBlockedAPIError(networkError), + let blockedError = self.dnsChecker.errorWithBlockedHostFromError(networkError) { + Logger.error(blockedError.description) + return blockedError + } else { + return error + } + }, + shouldRetry: false + ) + } + + guard let httpURLResponse = urlResponse as? HTTPURLResponse else { + return (.failure(ErrorUtils.unexpectedBackendResponseError()), false) + } + + func extractBody(_ response: HTTPURLResponse, _ statusCode: HTTPStatusCode) throws -> [String: Any] { + if let data = data, statusCode != .notModified { + let result: [String: Any]? + + do { + result = try JSONSerialization.jsonObject(with: data) as? [String: Any] + } catch let jsonError { + Logger.error(Strings.network.parsing_json_error(error: jsonError)) + + let dataAsString = String(data: data, encoding: .utf8) ?? "" + Logger.error(Strings.network.json_data_received(dataString: dataAsString)) + + throw jsonError } - httpResponse = self.eTagManager.httpResultFromCacheOrBackend(with: httpURLResponse, - jsonObject: jsonObject, - error: receivedJSONError, - request: urlRequest, - retried: request.retried) - if httpResponse == nil { - Logger.debug(Strings.network.retrying_request(httpMethod: request.method.httpMethod, - path: request.path)) - self.state.modify { - $0.queuedRequests.insert(request.retriedRequest(), at: 0) - } + guard let result = result else { + throw ErrorUtils.unexpectedBackendResponseError() } + + return result + } else { + // No data if the body was empty, or if status is `.notModified` + // since the body will be fetched from the eTag. + return [:] } } - var networkError = networkError - if dnsChecker.isBlockedAPIError(networkError), - let blockedError = dnsChecker.errorWithBlockedHostFromError(networkError) { - Logger.error(blockedError.description) - networkError = blockedError - } + let statusCode = HTTPStatusCode(rawValue: httpURLResponse.statusCode) + + Logger.debug(Strings.network.api_request_completed(request.httpRequest, + httpCode: statusCode)) + + let result: Result = Result { try extractBody(httpURLResponse, statusCode) } + .map { HTTPResponse(statusCode: statusCode, jsonObject: $0) } + .map { + return self.eTagManager.httpResultFromCacheOrBackend(with: httpURLResponse, + jsonObject: $0.jsonObject, + request: urlRequest, + retried: request.retried) + } + + let shouldRetry: Bool = { + if case .success(.none) = result { + return true + } else { + return false + } + }() + + return ( + result: result + .flatMap { + $0.map(Result.success) + ?? .failure(ErrorUtils.unexpectedBackendResponseError()) + }, + shouldRetry: shouldRetry + ) + } - if let httpResponse = httpResponse, - let completionHandler = request.completionHandler { - let error = receivedJSONError ?? networkError - completionHandler(httpResponse.statusCode, Result(httpResponse.jsonObject, error)) + func handle(urlResponse: URLResponse?, + request: Request, + urlRequest: URLRequest, + data: Data?, + error networkError: Error?) { + let (response, shouldRetry) = self.parse( + urlResponse: urlResponse, + request: request, + urlRequest: urlRequest, + data: data, + error: networkError + ) + + if shouldRetry { + Logger.debug(Strings.network.retrying_request(httpMethod: request.method.httpMethod, + path: request.path)) + + self.state.modify { + $0.queuedRequests.insert(request.retriedRequest(), at: 0) + } + } else { + request.completionHandler?(response) } self.beginNextRequest() @@ -244,7 +298,6 @@ private extension HTTPClient { Logger.error("Could not create request to \(request.path)") request.completionHandler?( - .invalidRequest, .failure(ErrorUtils.networkError(withUnderlyingError: ErrorUtils.unknownError())) ) return @@ -304,14 +357,7 @@ extension HTTPRequest.Path { private extension Encodable { func asData() throws -> Data { - return try jsonEncoder.encode(self) + return try JSONEncoder.default.encode(self) } } - -private let jsonEncoder: JSONEncoder = { - let encoder = JSONEncoder() - encoder.keyEncodingStrategy = .convertToSnakeCase - - return encoder -}() diff --git a/Sources/Networking/HTTPResponse.swift b/Sources/Networking/HTTPResponse.swift index 6773f52f6f..37cc82081d 100644 --- a/Sources/Networking/HTTPResponse.swift +++ b/Sources/Networking/HTTPResponse.swift @@ -19,14 +19,14 @@ struct HTTPResponse { typealias Body = [String: Any] let statusCode: HTTPStatusCode - let jsonObject: Body? + let jsonObject: Body } extension HTTPResponse: CustomStringConvertible { var description: String { - "HTTPResponse(statusCode: \(self.statusCode.rawValue), jsonObject: \(self.jsonObject?.description ?? ""))" + "HTTPResponse(statusCode: \(self.statusCode.rawValue), jsonObject: \(self.jsonObject.description))" } } diff --git a/Sources/Networking/IntroEligibilityResponse.swift b/Sources/Networking/IntroEligibilityResponse.swift index 369edff0d5..ae37760514 100644 --- a/Sources/Networking/IntroEligibilityResponse.swift +++ b/Sources/Networking/IntroEligibilityResponse.swift @@ -16,8 +16,7 @@ import Foundation // All parameters that are required to process the reponse from a GetIntroEligibility API call. struct IntroEligibilityResponse { - let result: Result<[String: Any], Error> - let statusCode: HTTPStatusCode + let result: Result let productIdentifiers: [String] let unknownEligibilityClosure: () -> [String: IntroEligibility] let completion: IntroEligibilityResponseHandler diff --git a/Sources/Networking/Operations/CreateAliasOperation.swift b/Sources/Networking/Operations/CreateAliasOperation.swift index c6a789a804..da7ed8456a 100644 --- a/Sources/Networking/Operations/CreateAliasOperation.swift +++ b/Sources/Networking/Operations/CreateAliasOperation.swift @@ -54,16 +54,14 @@ private extension CreateAliasOperation { let request = HTTPRequest(method: .post(Body(newAppUserID: newAppUserID)), path: .createAlias(appUserID: appUserID)) - httpClient.perform(request, authHeaders: self.authHeaders) { statusCode, result in + httpClient.perform(request, authHeaders: self.authHeaders) { response in self.aliasCallbackCache.performOnAllItemsAndRemoveFromCache(withCacheable: self) { aliasCallback in guard let completion = aliasCallback.completion else { return } - self.createAliasResponseHandler.handle(statusCode: statusCode, - result: result, - completion: completion) + self.createAliasResponseHandler.handle(response, completion: completion) } completion() diff --git a/Sources/Networking/Operations/GetCustomerInfoOperation.swift b/Sources/Networking/Operations/GetCustomerInfoOperation.swift index 1641ff68c9..7fc03e28c5 100644 --- a/Sources/Networking/Operations/GetCustomerInfoOperation.swift +++ b/Sources/Networking/Operations/GetCustomerInfoOperation.swift @@ -60,10 +60,9 @@ private extension GetCustomerInfoOperation { let request = HTTPRequest(method: .get, path: .getCustomerInfo(appUserID: appUserID)) - httpClient.perform(request, authHeaders: self.authHeaders) { statusCode, result in + httpClient.perform(request, authHeaders: self.authHeaders) { response in self.customerInfoCallbackCache.performOnAllItemsAndRemoveFromCache(withCacheable: self) { callback in - self.customerInfoResponseHandler.handle(customerInfoResponse: result, - statusCode: statusCode, + self.customerInfoResponseHandler.handle(customerInfoResponse: response, completion: callback.completion) } diff --git a/Sources/Networking/Operations/GetIntroEligibilityOperation.swift b/Sources/Networking/Operations/GetIntroEligibilityOperation.swift index fa19ce72e7..1c82090759 100644 --- a/Sources/Networking/Operations/GetIntroEligibilityOperation.swift +++ b/Sources/Networking/Operations/GetIntroEligibilityOperation.swift @@ -86,9 +86,8 @@ private extension GetIntroEligibilityOperation { fetchToken: self.receiptData.asFetchToken)), path: .getIntroEligibility(appUserID: appUserID)) - httpClient.perform(request, authHeaders: self.authHeaders) { statusCode, result in - let eligibilityResponse = IntroEligibilityResponse(result: result, - statusCode: statusCode, + httpClient.perform(request, authHeaders: self.authHeaders) { response in + let eligibilityResponse = IntroEligibilityResponse(result: response, productIdentifiers: self.productIdentifiers, unknownEligibilityClosure: unknownEligibilityClosure, completion: self.responseHandler) @@ -99,9 +98,9 @@ private extension GetIntroEligibilityOperation { func handleIntroEligibility(response: IntroEligibilityResponse) { let result: [String: IntroEligibility] = { - var eligibilitiesByProductIdentifier = response.result.value + var eligibilitiesByProductIdentifier = response.result.value?.jsonObject - if !response.statusCode.isSuccessfulResponse || response.result.error != nil { + if response.result.value?.statusCode.isSuccessfulResponse != true { eligibilitiesByProductIdentifier = [:] } diff --git a/Sources/Networking/Operations/GetOfferingsOperation.swift b/Sources/Networking/Operations/GetOfferingsOperation.swift index 82ec9d009a..943aedf8b1 100644 --- a/Sources/Networking/Operations/GetOfferingsOperation.swift +++ b/Sources/Networking/Operations/GetOfferingsOperation.swift @@ -46,14 +46,16 @@ private extension GetOfferingsOperation { let request = HTTPRequest(method: .get, path: .getOfferings(appUserID: appUserID)) - httpClient.perform(request, authHeaders: self.authHeaders) { statusCode, result in + httpClient.perform(request, authHeaders: self.authHeaders) { response in defer { completion() } - let parsedResponse: Result<[String: Any], Error> = result + let parsedResponse: Result<[String: Any], Error> = response .mapError { ErrorUtils.networkError(withUnderlyingError: $0) } .flatMap { response in + let (statusCode, response) = (response.statusCode, response.jsonObject) + return statusCode.isSuccessfulResponse ? .success(response) : .failure( diff --git a/Sources/Networking/Operations/Handling/CustomerInfoResponseHandler.swift b/Sources/Networking/Operations/Handling/CustomerInfoResponseHandler.swift index fc757bb3c2..9d5440f71d 100644 --- a/Sources/Networking/Operations/Handling/CustomerInfoResponseHandler.swift +++ b/Sources/Networking/Operations/Handling/CustomerInfoResponseHandler.swift @@ -15,15 +15,10 @@ import Foundation class CustomerInfoResponseHandler { - let userInfoAttributeParser: UserInfoAttributeParser - - init(userInfoAttributeParser: UserInfoAttributeParser = UserInfoAttributeParser()) { - self.userInfoAttributeParser = userInfoAttributeParser - } + init() { } // swiftlint:disable:next function_body_length - func handle(customerInfoResponse response: Result<[String: Any], Error>, - statusCode: HTTPStatusCode, + func handle(customerInfoResponse response: Result, file: String = #fileID, function: String = #function, line: UInt = #line, @@ -36,6 +31,7 @@ class CustomerInfoResponseHandler { )) case let .success(response): + let (statusCode, response) = (response.statusCode, response.jsonObject) let isErrorStatusCode = !statusCode.isSuccessfulResponse var result: Result = { @@ -57,7 +53,7 @@ class CustomerInfoResponseHandler { } }() - let subscriberAttributesErrorInfo = self.userInfoAttributeParser + let subscriberAttributesErrorInfo = UserInfoAttributeParser .attributesUserInfoFromResponse(response: response, statusCode: statusCode) let hasError = (isErrorStatusCode diff --git a/Sources/Networking/Operations/Handling/NoContentResponseHandler.swift b/Sources/Networking/Operations/Handling/NoContentResponseHandler.swift index f55c368ca2..020726d869 100644 --- a/Sources/Networking/Operations/Handling/NoContentResponseHandler.swift +++ b/Sources/Networking/Operations/Handling/NoContentResponseHandler.swift @@ -15,14 +15,13 @@ import Foundation class NoContentResponseHandler { - func handle(statusCode: HTTPStatusCode, - result: Result<[String: Any], Error>, + func handle(_ response: Result, completion: SimpleResponseHandler) { - switch result { + switch response { case let .success(response): - guard statusCode.isSuccessfulResponse else { - let backendErrorCode = BackendErrorCode(code: response["code"]) - let message = response["message"] as? String + guard response.statusCode.isSuccessfulResponse else { + let backendErrorCode = BackendErrorCode(code: response.jsonObject["code"]) + let message = response.jsonObject["message"] as? String let responseError = ErrorUtils.backendError(withBackendCode: backendErrorCode, backendMessage: message) completion(responseError) diff --git a/Sources/Networking/Operations/Handling/SubscriberAttributeHandler.swift b/Sources/Networking/Operations/Handling/SubscriberAttributeHandler.swift index b5a1a67f3f..dfe9b54295 100644 --- a/Sources/Networking/Operations/Handling/SubscriberAttributeHandler.swift +++ b/Sources/Networking/Operations/Handling/SubscriberAttributeHandler.swift @@ -15,22 +15,19 @@ import Foundation class SubscriberAttributeHandler { - let userInfoAttributeParser: UserInfoAttributeParser + init() { } - init(userInfoAttributeParser: UserInfoAttributeParser = UserInfoAttributeParser()) { - self.userInfoAttributeParser = userInfoAttributeParser - } - - func handleSubscriberAttributesResult(statusCode: HTTPStatusCode, - response: Result<[String: Any], Error>, + func handleSubscriberAttributesResult(_ response: Result, completion: SimpleResponseHandler) { let result: Result<[String: Any], Error> = response .mapError { ErrorUtils.networkError(withUnderlyingError: $0) } .flatMap { response in + let (statusCode, response) = (response.statusCode, response.jsonObject) + if !statusCode.isSuccessfulResponse { - let extraUserInfo = self.userInfoAttributeParser + let extraUserInfo = UserInfoAttributeParser .attributesUserInfoFromResponse(response: response, statusCode: statusCode) let backendErrorCode = BackendErrorCode(code: response["code"]) return .failure( diff --git a/Sources/Networking/Operations/Handling/UserInfoAttributeParser.swift b/Sources/Networking/Operations/Handling/UserInfoAttributeParser.swift index d15ddb8ed9..0c3cea1aa4 100644 --- a/Sources/Networking/Operations/Handling/UserInfoAttributeParser.swift +++ b/Sources/Networking/Operations/Handling/UserInfoAttributeParser.swift @@ -13,9 +13,9 @@ import Foundation -class UserInfoAttributeParser { +enum UserInfoAttributeParser { - func attributesUserInfoFromResponse(response: [String: Any], statusCode: HTTPStatusCode) -> [String: Any] { + static func attributesUserInfoFromResponse(response: [String: Any], statusCode: HTTPStatusCode) -> [String: Any] { var resultDict: [String: Any] = [:] let isServerError = statusCode.isServerError let isNotFoundError = statusCode == .notFoundError diff --git a/Sources/Networking/Operations/LogInOperation.swift b/Sources/Networking/Operations/LogInOperation.swift index 4e457b7b9e..306321a3fe 100644 --- a/Sources/Networking/Operations/LogInOperation.swift +++ b/Sources/Networking/Operations/LogInOperation.swift @@ -51,23 +51,22 @@ private extension LogInOperation { newAppUserID: newAppUserID)), path: .logIn) - self.httpClient.perform(request, authHeaders: self.authHeaders) { statusCode, result in + self.httpClient.perform(request, authHeaders: self.authHeaders) { response in self.loginCallbackCache.performOnAllItemsAndRemoveFromCache(withCacheable: self) { callbackObject in - self.handleLogin(result: result, - statusCode: statusCode, - completion: callbackObject.completion) + self.handleLogin(response, completion: callbackObject.completion) } completion() } } - func handleLogin(result: Result<[String: Any], Error>, - statusCode: HTTPStatusCode, + func handleLogin(_ result: Result, completion: LogInResponseHandler) { let result: Result<(info: CustomerInfo, created: Bool), Error> = result .mapError { ErrorUtils.networkError(withUnderlyingError: $0) } .flatMap { response in + let (statusCode, response) = (response.statusCode, response.jsonObject) + if !statusCode.isSuccessfulResponse { let backendCode = BackendErrorCode(code: response["code"]) let backendMessage = response["message"] as? String diff --git a/Sources/Networking/Operations/PostAttributionDataOperation.swift b/Sources/Networking/Operations/PostAttributionDataOperation.swift index 47d57d2b64..71a2711cbd 100644 --- a/Sources/Networking/Operations/PostAttributionDataOperation.swift +++ b/Sources/Networking/Operations/PostAttributionDataOperation.swift @@ -50,7 +50,7 @@ class PostAttributionDataOperation: NetworkOperation { let request = HTTPRequest(method: .post(Body(network: self.network, attributionData: self.attributionData)), path: .postAttributionData(appUserID: appUserID)) - self.httpClient.perform(request, authHeaders: self.authHeaders) { statusCode, result in + self.httpClient.perform(request, authHeaders: self.authHeaders) { response in defer { completion() } @@ -59,9 +59,7 @@ class PostAttributionDataOperation: NetworkOperation { return } - self.postAttributionDataResponseHandler.handle(statusCode: statusCode, - result: result, - completion: responseHandler) + self.postAttributionDataResponseHandler.handle(response, completion: responseHandler) } } diff --git a/Sources/Networking/Operations/PostOfferForSigningOperation.swift b/Sources/Networking/Operations/PostOfferForSigningOperation.swift index aa5c4ecf7d..ff9360e194 100644 --- a/Sources/Networking/Operations/PostOfferForSigningOperation.swift +++ b/Sources/Networking/Operations/PostOfferForSigningOperation.swift @@ -50,10 +50,12 @@ class PostOfferForSigningOperation: NetworkOperation { path: .postOfferForSigning ) - self.httpClient.perform(request, authHeaders: self.authHeaders) { statusCode, result in - let result: Result = result + self.httpClient.perform(request, authHeaders: self.authHeaders) { response in + let result: Result = response .mapError { ErrorUtils.networkError(withUnderlyingError: $0) } .flatMap { response in + let (statusCode, response) = (response.statusCode, response.jsonObject) + guard statusCode.isSuccessfulResponse else { let backendCode = BackendErrorCode(code: response["code"]) let backendMessage = response["message"] as? String diff --git a/Sources/Networking/Operations/PostReceiptDataOperation.swift b/Sources/Networking/Operations/PostReceiptDataOperation.swift index 7711f50f85..c5f1a7ac04 100644 --- a/Sources/Networking/Operations/PostReceiptDataOperation.swift +++ b/Sources/Networking/Operations/PostReceiptDataOperation.swift @@ -60,10 +60,9 @@ class PostReceiptDataOperation: CacheableNetworkOperation { let request = HTTPRequest(method: .post(self.postData), path: .postReceiptData) - httpClient.perform(request, authHeaders: self.authHeaders) { statusCode, result in + httpClient.perform(request, authHeaders: self.authHeaders) { response in self.customerInfoCallbackCache.performOnAllItemsAndRemoveFromCache(withCacheable: self) { callbackObject in - self.customerInfoResponseHandler.handle(customerInfoResponse: result, - statusCode: statusCode, + self.customerInfoResponseHandler.handle(customerInfoResponse: response, completion: callbackObject.completion) } diff --git a/Sources/Networking/Operations/PostSubscriberAttributesOperation.swift b/Sources/Networking/Operations/PostSubscriberAttributesOperation.swift index 11ab40b06d..d277620d5f 100644 --- a/Sources/Networking/Operations/PostSubscriberAttributesOperation.swift +++ b/Sources/Networking/Operations/PostSubscriberAttributesOperation.swift @@ -55,7 +55,7 @@ class PostSubscriberAttributesOperation: NetworkOperation { let request = HTTPRequest(method: .post(Body(self.subscriberAttributes)), path: .postSubscriberAttributes(appUserID: appUserID)) - httpClient.perform(request, authHeaders: self.authHeaders) { statusCode, result in + httpClient.perform(request, authHeaders: self.authHeaders) { response in defer { completion() } @@ -64,8 +64,7 @@ class PostSubscriberAttributesOperation: NetworkOperation { return } - self.subscriberAttributeHandler.handleSubscriberAttributesResult(statusCode: statusCode, - response: result, + self.subscriberAttributeHandler.handleSubscriberAttributesResult(response, completion: responseHandler) } } diff --git a/Tests/UnitTests/Mocks/MockETagManager.swift b/Tests/UnitTests/Mocks/MockETagManager.swift index b1dbbff920..7457e7d02e 100644 --- a/Tests/UnitTests/Mocks/MockETagManager.swift +++ b/Tests/UnitTests/Mocks/MockETagManager.swift @@ -31,8 +31,7 @@ class MockETagManager: ETagManager { private struct InvokedHTTPResultFromCacheOrBackendParams { let response: HTTPURLResponse - let responseObject: [String: Any]? - let error: Error? + let responseObject: [String: Any] let request: URLRequest let retried: Bool } @@ -45,8 +44,7 @@ class MockETagManager: ETagManager { var shouldReturnResultFromBackend = true override func httpResultFromCacheOrBackend(with response: HTTPURLResponse, - jsonObject: [String: Any]?, - error: Error?, + jsonObject: [String: Any], request: URLRequest, retried: Bool) -> HTTPResponse? { return lock.perform { @@ -55,7 +53,6 @@ class MockETagManager: ETagManager { let params = InvokedHTTPResultFromCacheOrBackendParams( response: response, responseObject: jsonObject, - error: error, request: request, retried: retried ) diff --git a/Tests/UnitTests/Mocks/MockHTTPClient.swift b/Tests/UnitTests/Mocks/MockHTTPClient.swift index 35ea47b889..1c692bb744 100644 --- a/Tests/UnitTests/Mocks/MockHTTPClient.swift +++ b/Tests/UnitTests/Mocks/MockHTTPClient.swift @@ -15,22 +15,18 @@ class MockHTTPClient: HTTPClient { struct Response { - let statusCode: HTTPStatusCode - let response: Result<[String: Any], Error> + let response: Result - init(statusCode: HTTPStatusCode, response: Result<[String: Any], Error>) { - self.statusCode = statusCode + private init(response: Result) { self.response = response } init(statusCode: HTTPStatusCode, response: [String: Any] = [:]) { - self.init(statusCode: statusCode, - response: .success(response)) + self.init(response: .success(.init(statusCode: statusCode, jsonObject: response))) } - init(statusCode: HTTPStatusCode, error: Error) { - self.init(statusCode: statusCode, - response: .failure(error)) + init(error: Error) { + self.init(response: .failure(error)) } } @@ -68,8 +64,7 @@ class MockHTTPClient: HTTPClient { let response = self.mocks[request.path] - completionHandler?(response?.statusCode ?? .success, - response?.response ?? .success([:])) + completionHandler?(response?.response ?? .success(.init(statusCode: .success, jsonObject: [:]))) } } diff --git a/Tests/UnitTests/Networking/Backend/BackendCreateAliasTests.swift b/Tests/UnitTests/Networking/Backend/BackendCreateAliasTests.swift index 326c14b3a1..95c42b90df 100644 --- a/Tests/UnitTests/Networking/Backend/BackendCreateAliasTests.swift +++ b/Tests/UnitTests/Networking/Backend/BackendCreateAliasTests.swift @@ -24,18 +24,19 @@ class BackendCreateAliasTests: BaseBackendTests { } func testAliasCallsBackendProperly() throws { - var completionCalled = false + let completionCalled: Atomic = .init(nil) let path: HTTPRequest.Path = .createAlias(appUserID: Self.userID) let response = MockHTTPClient.Response(statusCode: .success) self.httpClient.mock(requestPath: path, response: response) - backend.createAlias(appUserID: Self.userID, newAppUserID: "new_alias", completion: { (_) in - completionCalled = true - }) + backend.createAlias(appUserID: Self.userID, newAppUserID: "new_alias") { _ in + completionCalled.value = true + } - expect(completionCalled).toEventually(beTrue()) + expect(completionCalled.value).toEventuallyNot(beNil()) + expect(completionCalled.value) == true expect(self.httpClient.calls).toEventually(haveCount(1)) } @@ -116,8 +117,7 @@ class BackendCreateAliasTests: BaseBackendTests { func testNetworkErrorIsForwarded() { self.httpClient.mock( requestPath: .createAlias(appUserID: Self.userID), - response: .init(statusCode: .success, - response: .failure(NSError(domain: NSURLErrorDomain, code: -1009))) + response: .init(error: NSError(domain: NSURLErrorDomain, code: -1009)) ) var receivedError: NSError? diff --git a/Tests/UnitTests/Networking/Backend/BackendGetCustomerInfoTests.swift b/Tests/UnitTests/Networking/Backend/BackendGetCustomerInfoTests.swift index 7acf851320..7448361e02 100644 --- a/Tests/UnitTests/Networking/Backend/BackendGetCustomerInfoTests.swift +++ b/Tests/UnitTests/Networking/Backend/BackendGetCustomerInfoTests.swift @@ -80,8 +80,7 @@ class BackendGetCustomerInfoTests: BaseBackendTests { httpClient.mock(requestPath: .getCustomerInfo(appUserID: encodedUserID), response: response) httpClient.mock(requestPath: .getCustomerInfo(appUserID: encodeableUserID), - response: .init(statusCode: .notFoundError, - error: ErrorUtils.networkError(withUnderlyingError: ErrorUtils.unknownError()))) + response: .init(error: ErrorUtils.networkError(withUnderlyingError: ErrorUtils.unknownError()))) var customerInfo: Result? diff --git a/Tests/UnitTests/Networking/Backend/BackendGetIntroEligibilityTests.swift b/Tests/UnitTests/Networking/Backend/BackendGetIntroEligibilityTests.swift index ce5502d59f..991ed43299 100644 --- a/Tests/UnitTests/Networking/Backend/BackendGetIntroEligibilityTests.swift +++ b/Tests/UnitTests/Networking/Backend/BackendGetIntroEligibilityTests.swift @@ -91,8 +91,7 @@ class BackendGetIntroEligibilityTests: BaseBackendTests { // Set us up for a 404 because if the input sanitizing code fails, it will execute and we'd get a 404. self.httpClient.mock( requestPath: .getIntroEligibility(appUserID: ""), - response: .init(statusCode: .notFoundError, - error: ErrorUtils.networkError(withUnderlyingError: ErrorUtils.unknownError())) + response: .init(error: ErrorUtils.networkError(withUnderlyingError: ErrorUtils.unknownError())) ) var eligibility: [String: IntroEligibility]? @@ -139,7 +138,7 @@ class BackendGetIntroEligibilityTests: BaseBackendTests { let error = NSError(domain: "myhouse", code: 12, userInfo: nil) as Error self.httpClient.mock( requestPath: .getIntroEligibility(appUserID: Self.userID), - response: .init(statusCode: .success, response: .failure(error)) + response: .init(error: error) ) var eligibility: [String: IntroEligibility]? diff --git a/Tests/UnitTests/Networking/Backend/BackendGetOfferingsTests.swift b/Tests/UnitTests/Networking/Backend/BackendGetOfferingsTests.swift index 85d770a388..6adf7f31e6 100644 --- a/Tests/UnitTests/Networking/Backend/BackendGetOfferingsTests.swift +++ b/Tests/UnitTests/Networking/Backend/BackendGetOfferingsTests.swift @@ -111,8 +111,7 @@ class BackendGetOfferingsTests: BaseBackendTests { func testGetOfferingsNetworkErrorSendsNilAndError() { self.httpClient.mock( requestPath: .getOfferings(appUserID: Self.userID), - response: .init(statusCode: .success, - response: .failure(NSError(domain: NSURLErrorDomain, code: -1009))) + response: .init(error: NSError(domain: NSURLErrorDomain, code: -1009)) ) var receivedError: NSError? diff --git a/Tests/UnitTests/Networking/Backend/BackendLoginTests.swift b/Tests/UnitTests/Networking/Backend/BackendLoginTests.swift index 5f0ec414a9..18e70e21c3 100644 --- a/Tests/UnitTests/Networking/Backend/BackendLoginTests.swift +++ b/Tests/UnitTests/Networking/Backend/BackendLoginTests.swift @@ -43,7 +43,7 @@ class BackendLoginTests: BaseBackendTests { let errorCode = 123465 let stubbedError = NSError(domain: RCPurchasesErrorCodeDomain, code: errorCode, userInfo: [:]) let currentAppUserID = "old id" - _ = mockLoginRequest(appUserID: currentAppUserID, response: .failure(stubbedError)) + _ = mockLoginRequest(appUserID: currentAppUserID, error: stubbedError) var receivedResult: Result<(info: CustomerInfo, created: Bool), Error>? @@ -69,7 +69,7 @@ class BackendLoginTests: BaseBackendTests { code: errorCode, userInfo: [:]) let currentAppUserID = "old id" - _ = self.mockLoginRequest(appUserID: currentAppUserID, response: .failure(stubbedError)) + _ = self.mockLoginRequest(appUserID: currentAppUserID, error: stubbedError) var receivedResult: Result<(info: CustomerInfo, created: Bool), Error>? @@ -94,7 +94,7 @@ class BackendLoginTests: BaseBackendTests { let underlyingErrorCode = BackendErrorCode.cannotTransferPurchase.rawValue _ = self.mockLoginRequest(appUserID: currentAppUserID, statusCode: 431, - response: .success(["code": underlyingErrorCode, "message": underlyingErrorMessage])) + response: ["code": underlyingErrorCode, "message": underlyingErrorMessage]) var receivedResult: Result<(info: CustomerInfo, created: Bool), Error>? @@ -143,7 +143,7 @@ class BackendLoginTests: BaseBackendTests { let currentAppUserID = "old id" _ = self.mockLoginRequest(appUserID: currentAppUserID, statusCode: .createdSuccess, - response: .success(Self.mockCustomerInfoData)) + response: Self.mockCustomerInfoData) var receivedResult: Result<(info: CustomerInfo, created: Bool), Error>? @@ -163,7 +163,7 @@ class BackendLoginTests: BaseBackendTests { let currentAppUserID = "old id" _ = self.mockLoginRequest(appUserID: currentAppUserID, statusCode: .success, - response: .success(Self.mockCustomerInfoData)) + response: Self.mockCustomerInfoData) var receivedResult: Result<(info: CustomerInfo, created: Bool), Error>? @@ -184,7 +184,7 @@ class BackendLoginTests: BaseBackendTests { let currentAppUserID = "old id" _ = self.mockLoginRequest(appUserID: currentAppUserID, statusCode: .createdSuccess, - response: .success(Self.mockCustomerInfoData)) + response: Self.mockCustomerInfoData) backend.logIn(currentAppUserID: currentAppUserID, newAppUserID: newAppUserID) { _ in } @@ -201,7 +201,7 @@ class BackendLoginTests: BaseBackendTests { let currentAppUserID = "old id" _ = self.mockLoginRequest(appUserID: currentAppUserID, statusCode: .createdSuccess, - response: .success(Self.mockCustomerInfoData)) + response: Self.mockCustomerInfoData) backend.logIn(currentAppUserID: currentAppUserID, newAppUserID: newAppUserID) { _ in } @@ -218,7 +218,7 @@ class BackendLoginTests: BaseBackendTests { let currentAppUserID2 = "old id 2" _ = self.mockLoginRequest(appUserID: currentAppUserID, statusCode: .createdSuccess, - response: .success(Self.mockCustomerInfoData)) + response: Self.mockCustomerInfoData) backend.logIn(currentAppUserID: currentAppUserID, newAppUserID: newAppUserID) { _ in } @@ -234,7 +234,7 @@ class BackendLoginTests: BaseBackendTests { let currentAppUserID = "old id" _ = self.mockLoginRequest(appUserID: currentAppUserID, statusCode: .createdSuccess, - response: .success(Self.mockCustomerInfoData)) + response: Self.mockCustomerInfoData) var completion1Called = false var completion2Called = false @@ -259,9 +259,19 @@ private extension BackendLoginTests { func mockLoginRequest(appUserID: String, statusCode: HTTPStatusCode = .success, - response: Result<[String: Any], Error> = .success([:])) -> HTTPRequest.Path { + response: [String: Any] = [:]) -> HTTPRequest.Path { let path: HTTPRequest.Path = .logIn - let response = MockHTTPClient.Response(statusCode: statusCode, response: response) + let response = MockHTTPClient.Response(statusCode: statusCode, response: response) + + self.httpClient.mock(requestPath: path, response: response) + + return path + } + + func mockLoginRequest(appUserID: String, + error: Error) -> HTTPRequest.Path { + let path: HTTPRequest.Path = .logIn + let response = MockHTTPClient.Response(error: error) self.httpClient.mock(requestPath: path, response: response) diff --git a/Tests/UnitTests/Networking/Backend/BackendPostOfferForSigningTests.swift b/Tests/UnitTests/Networking/Backend/BackendPostOfferForSigningTests.swift index 0a68392947..ecdf88f352 100644 --- a/Tests/UnitTests/Networking/Backend/BackendPostOfferForSigningTests.swift +++ b/Tests/UnitTests/Networking/Backend/BackendPostOfferForSigningTests.swift @@ -66,8 +66,7 @@ class BackendPostOfferForSigningTests: BaseBackendTests { func testOfferForSigningNetworkError() { self.httpClient.mock( requestPath: .postOfferForSigning, - response: .init(statusCode: .success, - response: .failure(NSError(domain: NSURLErrorDomain, code: -1009))) + response: .init(error: NSError(domain: NSURLErrorDomain, code: -1009)) ) let productIdentifier = "a_great_product" diff --git a/Tests/UnitTests/Networking/Backend/BackendPostReceiptDataTestBase.swift b/Tests/UnitTests/Networking/Backend/BackendPostReceiptDataTestBase.swift index 113254dc46..45e5300e0f 100644 --- a/Tests/UnitTests/Networking/Backend/BackendPostReceiptDataTestBase.swift +++ b/Tests/UnitTests/Networking/Backend/BackendPostReceiptDataTestBase.swift @@ -497,8 +497,7 @@ class BackendPostReceiptDataTestBase: BaseBackendTests { self.httpClient.mock( requestPath: .postReceiptData, - response: .init(statusCode: .networkConnectTimeoutError, - response: .failure(error)) + response: .init(error: error) ) var receivedError: NSError? diff --git a/Tests/UnitTests/Networking/ETagManagerTests.swift b/Tests/UnitTests/Networking/ETagManagerTests.swift index da37147441..ee98aa47c0 100644 --- a/Tests/UnitTests/Networking/ETagManagerTests.swift +++ b/Tests/UnitTests/Networking/ETagManagerTests.swift @@ -8,7 +8,7 @@ class ETagManagerTests: XCTestCase { private var mockUserDefaults: MockUserDefaults! = nil private var eTagManager: ETagManager! - private var baseURL: URL = URL(string: "https://api.revenuecat.com")! + private let baseURL = URL(string: "https://api.revenuecat.com")! override func setUp() { super.setUp() @@ -56,7 +56,6 @@ class ETagManagerTests: XCTestCase { } let response = eTagManager.httpResultFromCacheOrBackend(with: httpURLResponse, jsonObject: [:], - error: nil, request: request, retried: false) expect(response).toNot(beNil()) @@ -72,31 +71,12 @@ class ETagManagerTests: XCTestCase { let responseObject = ["a": "response"] let httpURLResponse = httpURLResponseForTest(url: url, eTag: eTag, statusCode: .success) let response = eTagManager.httpResultFromCacheOrBackend( - with: httpURLResponse, jsonObject: responseObject, error: nil, request: request, retried: false) + with: httpURLResponse, jsonObject: responseObject, request: request, retried: false) expect(response).toNot(beNil()) expect(response?.statusCode) == .success expect(response?.jsonObject as? [String: String]).to(equal(responseObject)) } - func testBackendResponseIsReturnedIfThereIsAnError() { - let eTag = "an_etag" - let url = urlForTest() - let request = URLRequest(url: url) - _ = mockStoredETagAndResponse(for: url, statusCode: .success, eTag: eTag) - let responseObject = ["a": "response"] - let error = NSError(domain: NSCocoaErrorDomain, code: 123, userInfo: [:]) - let httpURLResponse = httpURLResponseForTest( - url: url, - eTag: eTag, - statusCode: .notModified - ) - let response = eTagManager.httpResultFromCacheOrBackend( - with: httpURLResponse, jsonObject: responseObject, error: error, request: request, retried: false) - expect(response).toNot(beNil()) - expect(response?.statusCode) == .notModified - expect(response?.jsonObject as? [String: String]).to(equal(responseObject)) - } - func testBackendResponseIsReturnedIf304AndCantFindCachedAndItHasAlreadyRetried() { let eTag = "an_etag" let url = urlForTest() @@ -108,7 +88,7 @@ class ETagManagerTests: XCTestCase { statusCode: .notModified ) let response = eTagManager.httpResultFromCacheOrBackend( - with: httpURLResponse, jsonObject: responseObject, error: nil, request: request, retried: true) + with: httpURLResponse, jsonObject: responseObject, request: request, retried: true) expect(response).toNot(beNil()) expect(response?.statusCode) == .notModified expect(response?.jsonObject as? [String: String]).to(equal(responseObject)) @@ -125,7 +105,7 @@ class ETagManagerTests: XCTestCase { statusCode: .notModified ) let response = eTagManager.httpResultFromCacheOrBackend( - with: httpURLResponse, jsonObject: responseObject, error: nil, request: request, retried: false) + with: httpURLResponse, jsonObject: responseObject, request: request, retried: false) expect(response).to(beNil()) } @@ -140,7 +120,7 @@ class ETagManagerTests: XCTestCase { statusCode: .success ) let response = eTagManager.httpResultFromCacheOrBackend( - with: httpURLResponse, jsonObject: responseObject, error: nil, request: request, retried: false) + with: httpURLResponse, jsonObject: responseObject, request: request, retried: false) expect(response).toNot(beNil()) expect(self.mockUserDefaults.setObjectForKeyCallCount) == 1 @@ -171,7 +151,7 @@ class ETagManagerTests: XCTestCase { statusCode: .internalServerError ) let response = eTagManager.httpResultFromCacheOrBackend( - with: httpURLResponse, jsonObject: responseObject, error: nil, request: request, retried: false) + with: httpURLResponse, jsonObject: responseObject, request: request, retried: false) expect(response).toNot(beNil()) expect(response?.statusCode) == .internalServerError @@ -195,8 +175,11 @@ class ETagManagerTests: XCTestCase { expect(self.mockUserDefaults.mockValues.count) == 0 } +} + +private extension ETagManagerTests { - private func getHeaders(eTag: String) -> [String: String] { + func getHeaders(eTag: String) -> [String: String] { return [ "Content-Type": "application/json", "X-Platform": "android", @@ -211,9 +194,9 @@ class ETagManagerTests: XCTestCase { .merging(HTTPClient.authorizationHeader(withAPIKey: "apikey")) } - private func mockStoredETagAndResponse(for url: URL, - statusCode: HTTPStatusCode = .success, - eTag: String = "an_etag") -> [String: String] { + func mockStoredETagAndResponse(for url: URL, + statusCode: HTTPStatusCode = .success, + eTag: String = "an_etag") -> [String: String] { let jsonObject = ["arg": "value"] let etagAndResponse = ETagAndResponseWrapper( eTag: eTag, @@ -224,14 +207,14 @@ class ETagManagerTests: XCTestCase { return jsonObject } - private func urlForTest() -> URL { + func urlForTest() -> URL { guard let url: URL = URL(string: "/v1/subscribers/appUserID", relativeTo: baseURL) else { fatalError("Error initializing URL") } return url } - private func httpURLResponseForTest(url: URL, eTag: String, statusCode: HTTPStatusCode) -> HTTPURLResponse { + func httpURLResponseForTest(url: URL, eTag: String, statusCode: HTTPStatusCode) -> HTTPURLResponse { guard let httpURLResponse = HTTPURLResponse( url: url, statusCode: statusCode.rawValue, diff --git a/Tests/UnitTests/Networking/HTTPClientTests.swift b/Tests/UnitTests/Networking/HTTPClientTests.swift index 1ab7490f51..563002c8b9 100644 --- a/Tests/UnitTests/Networking/HTTPClientTests.swift +++ b/Tests/UnitTests/Networking/HTTPClientTests.swift @@ -171,7 +171,7 @@ class HTTPClientTests: XCTestCase { return .emptySuccessResponse } - self.client.perform(request, authHeaders: [:]) { _, _ in + self.client.perform(request, authHeaders: [:]) { _ in completionCalled.value = true } @@ -189,10 +189,9 @@ class HTTPClientTests: XCTestCase { response.error = error return response } - self.client.perform(request, authHeaders: [:]) { (status, result) in + self.client.perform(request, authHeaders: [:]) { result in if let responseNSError = result.error as NSError? { - correctResult.value = (status.isServerError - && error.domain == responseNSError.domain + correctResult.value = (error.domain == responseNSError.domain && error.code == responseNSError.code) } else { correctResult.value = false @@ -207,8 +206,7 @@ class HTTPClientTests: XCTestCase { let request = HTTPRequest(method: .get, path: .mockPath) let errorCode = HTTPStatusCode.invalidRequest.rawValue + Int.random(in: 0..<50) - let isResponseCorrect: Atomic = .init(false) - let message: Atomic = .init(nil) + let result: Atomic?> = .init(nil) stub(condition: isPath(request.path)) { _ in let json = "{\"message\": \"something is broken up in the cloud\"}" @@ -219,46 +217,44 @@ class HTTPClientTests: XCTestCase { ) } - self.client.perform(request, authHeaders: [:]) { (status, result) in - isResponseCorrect.value = (status.rawValue == errorCode) && (result.value != nil) - message.value = result.value?["message"] as? String + self.client.perform(request, authHeaders: [:]) { + result.value = $0 } - expect(message.value).toEventuallyNot(beNil(), timeout: .seconds(1)) - expect(message.value) == "something is broken up in the cloud" - expect(isResponseCorrect.value) == true + expect(result.value).toEventuallyNot(beNil(), timeout: .seconds(1)) + expect(result.value?.value?.statusCode.rawValue) == errorCode + expect(result.value?.value?.jsonObject["message"] as? String) == "something is broken up in the cloud" } func testServerSide500s() { let request = HTTPRequest(method: .get, path: .mockPath) - let errorCode = Int32(500 + Int.random(in: 0..<50)) - let isResponseCorrect: Atomic = .init(false) - let message: Atomic = .init(nil) + let errorCode = 500 + Int.random(in: 0..<50) + let result: Atomic?> = .init(nil) stub(condition: isPath(request.path)) { _ in let json = "{\"message\": \"something is broken up in the cloud\"}" return HTTPStubsResponse( data: json.data(using: String.Encoding.utf8)!, - statusCode: errorCode, + statusCode: Int32(errorCode), headers: nil ) } - self.client.perform(request, authHeaders: [:]) { (status, result) in - isResponseCorrect.value = (status.rawValue == errorCode) && (result.value != nil) - message.value = result.value?["message"] as? String + self.client.perform(request, authHeaders: [:]) { + result.value = $0 } - expect(message.value).toEventually(equal("something is broken up in the cloud"), timeout: .seconds(1)) - expect(isResponseCorrect.value) == true + expect(result.value).toEventuallyNot(beNil(), timeout: .seconds(1)) + expect(result.value?.value?.statusCode.rawValue) == errorCode + expect(result.value?.value?.jsonObject["message"] as? String) == "something is broken up in the cloud" } func testParseError() { let request = HTTPRequest(method: .get, path: .mockPath) - let errorCode = HTTPStatusCode.success.rawValue + Int.random(in: 0..<300) - let correctResponse: Atomic = .init(false) + let errorCode = HTTPStatusCode.success.rawValue + let result: Atomic?> = .init(nil) stub(condition: isPath(request.path)) { _ in let json = "{this is not JSON.csdsd" @@ -269,18 +265,18 @@ class HTTPClientTests: XCTestCase { ) } - self.client.perform(request, authHeaders: [:]) { (status, result) in - correctResponse.value = (status.rawValue == errorCode) && (result.error != nil) + self.client.perform(request, authHeaders: [:]) { + result.value = $0 } - expect(correctResponse.value).toEventually(beTrue(), timeout: .seconds(1)) + expect(result.value).toEventuallyNot(beNil(), timeout: .seconds(1)) + expect(result.value).to(beFailure()) } func testServerSide200s() { let request = HTTPRequest(method: .get, path: .mockPath) - let isResponseCorrect: Atomic = .init(false) - let message: Atomic = .init(nil) + let result: Atomic?> = .init(nil) stub(condition: isPath(request.path)) { _ in let json = "{\"message\": \"something is great up in the cloud\"}" @@ -289,13 +285,13 @@ class HTTPClientTests: XCTestCase { headers: nil) } - self.client.perform(request, authHeaders: [:]) { (status, result) in - isResponseCorrect.value = (status == .success) && (result.error == nil) - message.value = result.value?["message"] as? String + self.client.perform(request, authHeaders: [:]) { + result.value = $0 } - expect(message.value).toEventually(equal("something is great up in the cloud"), timeout: .seconds(1)) - expect(isResponseCorrect.value) == true + expect(result.value).toEventuallyNot(beNil(), timeout: .seconds(1)) + expect(result.value).to(beSuccess()) + expect(result.value?.value?.jsonObject["message"] as? String) == "something is great up in the cloud" } func testAlwaysPassesClientVersion() { @@ -359,9 +355,8 @@ class HTTPClientTests: XCTestCase { expect(obtainedIdentifierForVendor).to(beNil()) } - #endif + #else - #if !os(macOS) && !targetEnvironment(macCatalyst) func testAlwaysPassesAppleDeviceIdentifier() { let request = HTTPRequest(method: .post([:]), path: .mockPath) @@ -486,7 +481,7 @@ class HTTPClientTests: XCTestCase { let serialRequests = 10 for requestNumber in 0..? + let response: Atomic?> = .init(nil) self.client.perform(.init(method: .invalidBody(), path: .mockPath), - authHeaders: [:]) { (status, result) in - receivedStatus = status - receivedResult = result + authHeaders: [:]) { + response.value = $0 } - expect(receivedResult).toEventuallyNot(beNil()) + expect(response).toEventuallyNot(beNil()) - let receivedNSError = try XCTUnwrap(receivedResult?.error as NSError?) + let receivedNSError = try XCTUnwrap(response.value?.error as NSError?) expect(receivedNSError.code) == ErrorCode.networkError.rawValue - expect(receivedStatus) == .invalidRequest } func testPerformRequestDoesntPerformRequestIfBodyCouldntBeParsedIntoJSON() { let path: HTTPRequest.Path = .mockPath - var completionCalled = false + let completionCalled: Atomic = .init(false) let httpCallMade: Atomic = .init(false) stub(condition: isPath(path)) { _ in @@ -606,11 +598,11 @@ class HTTPClientTests: XCTestCase { } self.client.perform(.init(method: .invalidBody(), path: path), - authHeaders: [:]) { (_, _) in - completionCalled = true + authHeaders: [:]) { _ in + completionCalled.value = true } - expect(completionCalled).toEventually(beTrue()) + expect(completionCalled.value).toEventually(beTrue()) expect(httpCallMade.value).toEventually(beFalse()) } @@ -630,7 +622,7 @@ class HTTPClientTests: XCTestCase { self.eTagManager.shouldReturnResultFromBackend = false self.eTagManager.stubbedHTTPResultFromCacheOrBackendResult = nil - self.client.perform(.init(method: .get, path: path), authHeaders: [:]) { (_, _) in + self.client.perform(.init(method: .get, path: path), authHeaders: [:]) { _ in completionCalled.value = true } @@ -644,7 +636,7 @@ class HTTPClientTests: XCTestCase { "test": "data" ] - let response: Atomic<(code: HTTPStatusCode, result: Result)?> = .init(nil) + let response: Atomic?> = .init(nil) self.eTagManager.shouldReturnResultFromBackend = false self.eTagManager.stubbedHTTPResultFromCacheOrBackendResult = .init( @@ -659,13 +651,13 @@ class HTTPClientTests: XCTestCase { } self.client.perform(.init(method: .get, path: path), authHeaders: [:]) { - response.value = ($0, $1) + response.value = $0 } expect(response.value).toEventuallyNot(beNil(), timeout: .seconds(1)) - expect(response.value?.code) == .success - expect(response.value?.result.value as? [String: String]) == mockedCachedResponse + expect(response.value?.value?.statusCode) == .success + expect(response.value?.value?.jsonObject as? [String: String]) == mockedCachedResponse } func testDNSCheckerIsCalledWhenGETRequestFailedWithUnknownError() { @@ -774,7 +766,7 @@ class HTTPClientTests: XCTestCase { } let obtainedError: Atomic = .init(nil) - self.client.perform(.init(method: .get, path: path), authHeaders: [:] ) { _, result in + self.client.perform(.init(method: .get, path: path), authHeaders: [:] ) { result in obtainedError.value = result.error as? DNSError } diff --git a/Tests/UnitTests/SubscriberAttributes/BackendSubscriberAttributesTestBase.swift b/Tests/UnitTests/SubscriberAttributes/BackendSubscriberAttributesTestBase.swift index a477ab6911..e5805980a0 100644 --- a/Tests/UnitTests/SubscriberAttributes/BackendSubscriberAttributesTestBase.swift +++ b/Tests/UnitTests/SubscriberAttributes/BackendSubscriberAttributesTestBase.swift @@ -267,8 +267,7 @@ class BackendSubscriberAttributesTestBase: XCTestCase { self.mockHTTPClient.mock( requestPath: .postSubscriberAttributes(appUserID: appUserID), - response: .init(statusCode: .invalidRequest, - response: .failure(ErrorUtils.networkError(withUnderlyingError: underlyingError))) + response: .init(error: ErrorUtils.networkError(withUnderlyingError: underlyingError)) ) var receivedError: Error?