From ed9f7158da86156ade487d0bfb4e693511ec95a6 Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Fri, 15 Dec 2023 16:20:32 -0600 Subject: [PATCH 1/5] WIP - convert GET requests to Encodable --- Braintree.xcodeproj/project.pbxproj | 6 +- .../BTAmericanExpressClient.swift | 2 +- .../BTAmexRewardsBalanceRequest.swift | 13 ++ Sources/BraintreeCore/BTAPIClient.swift | 15 ++- Sources/BraintreeCore/BTGraphQLHTTP.swift | 2 +- Sources/BraintreeCore/BTHTTP.swift | 16 ++- .../BTAmericanExpressClient_Tests.swift | 15 ++- .../BraintreeCoreTests/BTHTTP_Tests.swift | 120 +++++++++--------- UnitTests/BraintreeTestShared/FakeHTTP.swift | 6 +- .../BraintreeTestShared/MockAPIClient.swift | 6 +- 10 files changed, 120 insertions(+), 81 deletions(-) create mode 100644 Sources/BraintreeAmericanExpress/BTAmexRewardsBalanceRequest.swift diff --git a/Braintree.xcodeproj/project.pbxproj b/Braintree.xcodeproj/project.pbxproj index 40bf1d1650..ed1b37c4e7 100644 --- a/Braintree.xcodeproj/project.pbxproj +++ b/Braintree.xcodeproj/project.pbxproj @@ -81,14 +81,15 @@ 80482F8629D3A498007E5F50 /* BTThreeDSecureShippingMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80482F8529D3A498007E5F50 /* BTThreeDSecureShippingMethod.swift */; }; 80482F8829D3A571007E5F50 /* BTThreeDSecureRequestedExemptionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80482F8729D3A571007E5F50 /* BTThreeDSecureRequestedExemptionType.swift */; }; 804B5712275AAF8500E51A52 /* BTAmericanExpressRewardsBalance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 804B5711275AAF8500E51A52 /* BTAmericanExpressRewardsBalance.swift */; }; + 804DC45B2B2CF3DC00F17A15 /* BTAmexRewardsBalanceRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 804DC45A2B2CF3DC00F17A15 /* BTAmexRewardsBalanceRequest.swift */; }; 8053F05429FAB6B00076F988 /* FPTIBatchData_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8053F05329FAB6B00076F988 /* FPTIBatchData_Tests.swift */; }; 8053F05729FAD4790076F988 /* Encodable+Dictionary_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8053F05629FAD4790076F988 /* Encodable+Dictionary_Tests.swift */; }; 8053F05929FB2F700076F988 /* URL+IsPayPal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8053F05829FB2F700076F988 /* URL+IsPayPal.swift */; }; 80581A8C25531D0A00006F53 /* BTConfiguration+ThreeDSecure_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80581A8B25531D0A00006F53 /* BTConfiguration+ThreeDSecure_Tests.swift */; }; 80581B1D2553319C00006F53 /* BTGraphQLHTTP_SSLPinning_IntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80581B1C2553319C00006F53 /* BTGraphQLHTTP_SSLPinning_IntegrationTests.swift */; }; 8075CBEE2B1B735200CA6265 /* BTAPIRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8075CBED2B1B735200CA6265 /* BTAPIRequest.swift */; }; - 80BA3C292B23892700900BBB /* FakeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80BA3C282B23892700900BBB /* FakeRequest.swift */; }; 80A6C6192B21205900416D50 /* UIApplication+URLOpener.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80A6C6182B21205900416D50 /* UIApplication+URLOpener.swift */; }; + 80BA3C292B23892700900BBB /* FakeRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80BA3C282B23892700900BBB /* FakeRequest.swift */; }; 80BA64AC29D788E000E15264 /* BTLocalPaymentResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80BA64AB29D788E000E15264 /* BTLocalPaymentResult.swift */; }; 80BA64B229D7937E00E15264 /* BTLocalPaymentRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80BA64B129D7937E00E15264 /* BTLocalPaymentRequest.swift */; }; 80BA64B429D795D000E15264 /* BTLocalPaymentRequestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80BA64B329D795D000E15264 /* BTLocalPaymentRequestDelegate.swift */; }; @@ -701,6 +702,7 @@ 80482F8529D3A498007E5F50 /* BTThreeDSecureShippingMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTThreeDSecureShippingMethod.swift; sourceTree = ""; }; 80482F8729D3A571007E5F50 /* BTThreeDSecureRequestedExemptionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTThreeDSecureRequestedExemptionType.swift; sourceTree = ""; }; 804B5711275AAF8500E51A52 /* BTAmericanExpressRewardsBalance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTAmericanExpressRewardsBalance.swift; sourceTree = ""; }; + 804DC45A2B2CF3DC00F17A15 /* BTAmexRewardsBalanceRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTAmexRewardsBalanceRequest.swift; sourceTree = ""; }; 8053F05329FAB6B00076F988 /* FPTIBatchData_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FPTIBatchData_Tests.swift; sourceTree = ""; }; 8053F05629FAD4790076F988 /* Encodable+Dictionary_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Encodable+Dictionary_Tests.swift"; sourceTree = ""; }; 8053F05829FB2F700076F988 /* URL+IsPayPal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+IsPayPal.swift"; sourceTree = ""; }; @@ -1128,6 +1130,7 @@ 9CE8C7F9275ABDE700FB11F9 /* BTAmericanExpressClient.swift */, 9C3F55EE275E80DA00C21C8A /* BTAmericanExpressError.swift */, 804B5711275AAF8500E51A52 /* BTAmericanExpressRewardsBalance.swift */, + 804DC45A2B2CF3DC00F17A15 /* BTAmexRewardsBalanceRequest.swift */, ); path = BraintreeAmericanExpress; sourceTree = ""; @@ -2705,6 +2708,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 804DC45B2B2CF3DC00F17A15 /* BTAmexRewardsBalanceRequest.swift in Sources */, 804B5712275AAF8500E51A52 /* BTAmericanExpressRewardsBalance.swift in Sources */, 9C3F55EF275E80DA00C21C8A /* BTAmericanExpressError.swift in Sources */, 3B0EF89429B28C0800456973 /* BTAmericanExpressAnalytics.swift in Sources */, diff --git a/Sources/BraintreeAmericanExpress/BTAmericanExpressClient.swift b/Sources/BraintreeAmericanExpress/BTAmericanExpressClient.swift index c2c7165c3c..72eff32f8d 100644 --- a/Sources/BraintreeAmericanExpress/BTAmericanExpressClient.swift +++ b/Sources/BraintreeAmericanExpress/BTAmericanExpressClient.swift @@ -26,7 +26,7 @@ import BraintreeCore /// - Note: If the nonce is associated with an ineligible card or a card with insufficient points, the rewardsBalance will contain this information as `errorMessage` and `errorCode`. @objc(getRewardsBalanceForNonce:currencyIsoCode:completion:) public func getRewardsBalance(forNonce nonce: String, currencyISOCode: String, completion: @escaping (BTAmericanExpressRewardsBalance?, Error?) -> Void) { - let parameters = ["currencyIsoCode": currencyISOCode, "paymentMethodNonce": nonce] + let parameters = BTAmexRewardsBalanceRequest(currencyIsoCode: currencyISOCode, paymentMethodNonce: nonce) apiClient.sendAnalyticsEvent(BTAmericanExpressAnalytics.started) apiClient.get("v1/payment_methods/amex_rewards_balance", parameters: parameters) { [weak self] body, response, error in diff --git a/Sources/BraintreeAmericanExpress/BTAmexRewardsBalanceRequest.swift b/Sources/BraintreeAmericanExpress/BTAmexRewardsBalanceRequest.swift new file mode 100644 index 0000000000..3704b271ef --- /dev/null +++ b/Sources/BraintreeAmericanExpress/BTAmexRewardsBalanceRequest.swift @@ -0,0 +1,13 @@ +import Foundation + +/// The GET parameters for `v1/payment_methods/amex_rewards_balance` +struct BTAmexRewardsBalanceRequest: Encodable { + + private let currencyIsoCode: String + private let paymentMethodNonce: String + + init(currencyIsoCode: String, paymentMethodNonce: String) { + self.currencyIsoCode = currencyIsoCode + self.paymentMethodNonce = paymentMethodNonce + } +} diff --git a/Sources/BraintreeCore/BTAPIClient.swift b/Sources/BraintreeCore/BTAPIClient.swift index f8cfc8ca2c..5c5a915d64 100644 --- a/Sources/BraintreeCore/BTAPIClient.swift +++ b/Sources/BraintreeCore/BTAPIClient.swift @@ -143,8 +143,12 @@ import Foundation if let clientToken { configPath = clientToken.configURL.absoluteString } + + struct ConfigPost: Encodable { + let configVersion: String + } - let parameters: [String: Any] = ["configVersion": "3"] + let parameters = ConfigPost(configVersion: "3") configurationHTTP?.get(configPath, parameters: parameters, shouldCache: true) { [weak self] body, response, error in guard let self else { @@ -235,9 +239,9 @@ import Foundation completion(paymentMethodNonces, nil) } } - + /// :nodoc: This method is exposed for internal Braintree use only. Do not use. It is not covered by Semantic Versioning and may change or be removed at any time. - /// + /// /// Perfom an HTTP GET on a URL composed of the configured from environment and the given path. /// - Parameters: /// - path: The endpoint URI path. @@ -248,11 +252,10 @@ import Foundation /// HTTP response and `error` will be `nil`; on failure, `body` and `response` will be /// `nil` and `error` will contain the error that occurred. @_documentation(visibility: private) - @objc(GET:parameters:httpType:completion:) public func get( _ path: String, - parameters: [String: String]? = nil, - httpType: BTAPIClientHTTPService = .gateway, + parameters: Encodable? = nil, + httpType: BTAPIClientHTTPService = .gateway, completion: @escaping RequestCompletion ) { fetchOrReturnRemoteConfiguration { [weak self] configuration, error in diff --git a/Sources/BraintreeCore/BTGraphQLHTTP.swift b/Sources/BraintreeCore/BTGraphQLHTTP.swift index aebfbc5cd8..99e534b87a 100644 --- a/Sources/BraintreeCore/BTGraphQLHTTP.swift +++ b/Sources/BraintreeCore/BTGraphQLHTTP.swift @@ -10,7 +10,7 @@ class BTGraphQLHTTP: BTHTTP { // MARK: - Overrides - override func get(_ path: String, parameters: [String: Any]? = nil, shouldCache: Bool = false, completion: @escaping RequestCompletion) { + override func get(_ path: String, parameters: Encodable? = nil, shouldCache: Bool = false, completion: @escaping RequestCompletion) { NSException(name: exceptionName, reason: "GET is unsupported").raise() } diff --git a/Sources/BraintreeCore/BTHTTP.swift b/Sources/BraintreeCore/BTHTTP.swift index e134410ea6..96fc46cafc 100644 --- a/Sources/BraintreeCore/BTHTTP.swift +++ b/Sources/BraintreeCore/BTHTTP.swift @@ -99,11 +99,17 @@ class BTHTTP: NSObject, NSCopying, URLSessionDelegate { // MARK: - HTTP Methods - func get(_ path: String, parameters: [String: Any]? = nil, shouldCache: Bool = false, completion: @escaping RequestCompletion) { - if shouldCache { - httpRequestWithCaching(method: "GET", path: path, parameters: parameters, completion: completion) - } else { - httpRequest(method: "GET", path: path, parameters: parameters, completion: completion) + func get(_ path: String, parameters: Encodable? = nil, shouldCache: Bool = false, completion: @escaping RequestCompletion) { + do { + let dict = try parameters?.toDictionary() + + if shouldCache { + httpRequestWithCaching(method: "GET", path: path, parameters: dict, completion: completion) + } else { + httpRequest(method: "GET", path: path, parameters: dict, completion: completion) + } + } catch let error { + completion(nil, nil, error) } } diff --git a/UnitTests/BraintreeAmericanExpressTests/BTAmericanExpressClient_Tests.swift b/UnitTests/BraintreeAmericanExpressTests/BTAmericanExpressClient_Tests.swift index 845cd32043..f55e284a72 100644 --- a/UnitTests/BraintreeAmericanExpressTests/BTAmericanExpressClient_Tests.swift +++ b/UnitTests/BraintreeAmericanExpressTests/BTAmericanExpressClient_Tests.swift @@ -12,7 +12,20 @@ class BTAmericanExpressClient_Tests: XCTestCase { super.setUp() mockAPIClient = MockAPIClient(authorization: "development_tokenization_key")! amexClient = BTAmericanExpressClient(apiClient: mockAPIClient) - } + } + + func testGetRewardsBalance_formatsGETRequest() async { + let result = try? await amexClient!.getRewardsBalance(forNonce: "fake-nonce", currencyISOCode: "fake-code") + + XCTAssertEqual(mockAPIClient.lastGETPath, "v1/payment_methods/amex_rewards_balance") + + guard let lastGetParameters = mockAPIClient.lastGETParameters else { + XCTFail("Expected GET parameters") + return + } + XCTAssertEqual(lastGetParameters["currencyIsoCode"] as! String, "fake-code") + XCTAssertEqual(lastGetParameters["paymentMethodNonce"] as! String, "fake-nonce") + } func testGetRewardsBalance_returnsSendsAnalyticsEventOnSuccess() { let responseBody = [ diff --git a/UnitTests/BraintreeCoreTests/BTHTTP_Tests.swift b/UnitTests/BraintreeCoreTests/BTHTTP_Tests.swift index acf00f862c..a59cb41c60 100644 --- a/UnitTests/BraintreeCoreTests/BTHTTP_Tests.swift +++ b/UnitTests/BraintreeCoreTests/BTHTTP_Tests.swift @@ -651,66 +651,66 @@ final class BTHTTP_Tests: XCTestCase { // MARK: - Parameters tests - func testTransmitsTheParametersAsURLEncodedQueryParameters() { - let expectation = expectation(description: "GET request") - let expectedQueryParameters = [ - "numericParameter=42", - "falseBooleanParameter=0", - "dictionaryParameter%5BdictionaryKey%5D=dictionaryValue", - "trueBooleanParameter=1", - "stringParameter=value", - "crazyStringParameter%5B%5D=crazy%2520and%26value", - "arrayParameter%5B%5D=arrayItem1", - "arrayParameter%5B%5D=arrayItem2" - ] - - http?.get("200.json", parameters: parameterDictionary) { body, response, error in - XCTAssertNotNil(body) - XCTAssertNotNil(response) - XCTAssertNil(error) - - let httpRequest = BTHTTPTestProtocol.parseRequestFromTestResponseBody(body!) - let actualQueryComponents = httpRequest.url?.query?.components(separatedBy: "&") - - expectedQueryParameters.forEach { expectedComponent in - XCTAssertTrue(actualQueryComponents!.contains(expectedComponent)) - } - - expectation.fulfill() - } - - waitForExpectations(timeout: 2) - } - - func testTransmitsTheParametersAsJSON() { - let expectation = expectation(description: "POST request") - let expectedParameters: [String: Any] = [ - "numericParameter": 42, - "falseBooleanParameter": false, - "dictionaryParameter": ["dictionaryKey": "dictionaryValue"], - "trueBooleanParameter": true, - "stringParameter": "value", - "crazyStringParameter[]": "crazy%20and&value", - "arrayParameter": [ "arrayItem1", "arrayItem2" ], - "authorization_fingerprint": "test-authorization-fingerprint" - ] - - http?.post("200.json", parameters: parameterDictionary) { body, response, error in - XCTAssertNotNil(body) - XCTAssertNotNil(response) - XCTAssertNil(error) - - let httpRequest = BTHTTPTestProtocol.parseRequestFromTestResponseBody(body!) - let httpRequestBody = BTHTTPTestProtocol.parseRequestBodyFromTestResponseBody(body!) - XCTAssertEqual(httpRequest.value(forHTTPHeaderField: "Content-Type"), "application/json; charset=utf-8") - - let actualParameters = try? JSONSerialization.jsonObject(with: httpRequestBody.data(using: .utf8)!) as? [String: Any] ?? [:] - XCTAssertTrue(actualParameters! == expectedParameters) - expectation.fulfill() - } - - waitForExpectations(timeout: 2) - } +// func testTransmitsTheParametersAsURLEncodedQueryParameters() { +// let expectation = expectation(description: "GET request") +// let expectedQueryParameters = [ +// "numericParameter=42", +// "falseBooleanParameter=0", +// "dictionaryParameter%5BdictionaryKey%5D=dictionaryValue", +// "trueBooleanParameter=1", +// "stringParameter=value", +// "crazyStringParameter%5B%5D=crazy%2520and%26value", +// "arrayParameter%5B%5D=arrayItem1", +// "arrayParameter%5B%5D=arrayItem2" +// ] +// +// http?.get("200.json", parameters: parameterDictionary) { body, response, error in +// XCTAssertNotNil(body) +// XCTAssertNotNil(response) +// XCTAssertNil(error) +// +// let httpRequest = BTHTTPTestProtocol.parseRequestFromTestResponseBody(body!) +// let actualQueryComponents = httpRequest.url?.query?.components(separatedBy: "&") +// +// expectedQueryParameters.forEach { expectedComponent in +// XCTAssertTrue(actualQueryComponents!.contains(expectedComponent)) +// } +// +// expectation.fulfill() +// } +// +// waitForExpectations(timeout: 2) +// } + +// func testTransmitsTheParametersAsJSON() { +// let expectation = expectation(description: "POST request") +// let expectedParameters: [String: Any] = [ +// "numericParameter": 42, +// "falseBooleanParameter": false, +// "dictionaryParameter": ["dictionaryKey": "dictionaryValue"], +// "trueBooleanParameter": true, +// "stringParameter": "value", +// "crazyStringParameter[]": "crazy%20and&value", +// "arrayParameter": [ "arrayItem1", "arrayItem2" ], +// "authorization_fingerprint": "test-authorization-fingerprint" +// ] +// +// http?.post("200.json", parameters: parameterDictionary) { body, response, error in +// XCTAssertNotNil(body) +// XCTAssertNotNil(response) +// XCTAssertNil(error) +// +// let httpRequest = BTHTTPTestProtocol.parseRequestFromTestResponseBody(body!) +// let httpRequestBody = BTHTTPTestProtocol.parseRequestBodyFromTestResponseBody(body!) +// XCTAssertEqual(httpRequest.value(forHTTPHeaderField: "Content-Type"), "application/json; charset=utf-8") +// +// let actualParameters = try? JSONSerialization.jsonObject(with: httpRequestBody.data(using: .utf8)!) as? [String: Any] ?? [:] +// XCTAssertTrue(actualParameters! == expectedParameters) +// expectation.fulfill() +// } +// +// waitForExpectations(timeout: 2) +// } // MARK: - DispatchQueue tests diff --git a/UnitTests/BraintreeTestShared/FakeHTTP.swift b/UnitTests/BraintreeTestShared/FakeHTTP.swift index ec43876117..5e8d46d2e1 100644 --- a/UnitTests/BraintreeTestShared/FakeHTTP.swift +++ b/UnitTests/BraintreeTestShared/FakeHTTP.swift @@ -6,7 +6,7 @@ import Foundation @objc public var POSTRequestCount: Int = 0 @objc public var lastRequestEndpoint: String? public var lastRequestMethod: String? - @objc public var lastRequestParameters: [String: Any]? + public var lastRequestParameters: [String: Any]? var stubMethod: String? var stubEndpoint: String? public var cannedResponse: BTJSON? @@ -35,10 +35,10 @@ import Foundation cannedError = error } - public override func get(_ path: String, parameters: [String: Any]? = nil, shouldCache: Bool, completion: BTHTTP.RequestCompletion?) { + public override func get(_ path: String, parameters: Encodable? = nil, shouldCache: Bool, completion: BTHTTP.RequestCompletion?) { GETRequestCount += 1 lastRequestEndpoint = path - lastRequestParameters = parameters + lastRequestParameters = try? parameters?.toDictionary() lastRequestMethod = "GET" if cannedError != nil { diff --git a/UnitTests/BraintreeTestShared/MockAPIClient.swift b/UnitTests/BraintreeTestShared/MockAPIClient.swift index a981a051c8..e9e7539209 100644 --- a/UnitTests/BraintreeTestShared/MockAPIClient.swift +++ b/UnitTests/BraintreeTestShared/MockAPIClient.swift @@ -7,7 +7,7 @@ public class MockAPIClient: BTAPIClient { public var lastPOSTAPIClientHTTPType: BTAPIClientHTTPService? public var lastGETPath = "" - public var lastGETParameters = [:] as [String : String]? + public var lastGETParameters = [:] as [AnyHashable: Any]? public var lastGETAPIClientHTTPType: BTAPIClientHTTPService? public var postedAnalyticsEvents : [String] = [] @@ -27,9 +27,9 @@ public class MockAPIClient: BTAPIClient { super.init(authorization: authorization, sendAnalyticsEvent: sendAnalyticsEvent) } - public override func get(_ path: String, parameters: [String: String]?, httpType: BTAPIClientHTTPService, completion completionBlock: ((BTJSON?, HTTPURLResponse?, Error?) -> Void)? = nil) { + public override func get(_ path: String, parameters: Encodable?, httpType: BTAPIClientHTTPService, completion completionBlock: ((BTJSON?, HTTPURLResponse?, Error?) -> Void)? = nil) { lastGETPath = path - lastGETParameters = parameters + lastGETParameters = try? parameters?.toDictionary() lastGETAPIClientHTTPType = httpType guard let completionBlock = completionBlock else { From 6190fb3d7678ff3d4f156c72d0f29e22263df9d1 Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Fri, 15 Dec 2023 16:24:27 -0600 Subject: [PATCH 2/5] Move config post into own file --- Braintree.xcodeproj/project.pbxproj | 4 ++++ Sources/BraintreeCore/BTAPIClient.swift | 6 +----- Sources/BraintreeCore/BTConfigurationRequest.swift | 9 +++++++++ 3 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 Sources/BraintreeCore/BTConfigurationRequest.swift diff --git a/Braintree.xcodeproj/project.pbxproj b/Braintree.xcodeproj/project.pbxproj index ed1b37c4e7..c74c966ff8 100644 --- a/Braintree.xcodeproj/project.pbxproj +++ b/Braintree.xcodeproj/project.pbxproj @@ -82,6 +82,7 @@ 80482F8829D3A571007E5F50 /* BTThreeDSecureRequestedExemptionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80482F8729D3A571007E5F50 /* BTThreeDSecureRequestedExemptionType.swift */; }; 804B5712275AAF8500E51A52 /* BTAmericanExpressRewardsBalance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 804B5711275AAF8500E51A52 /* BTAmericanExpressRewardsBalance.swift */; }; 804DC45B2B2CF3DC00F17A15 /* BTAmexRewardsBalanceRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 804DC45A2B2CF3DC00F17A15 /* BTAmexRewardsBalanceRequest.swift */; }; + 804DC45D2B2D08FF00F17A15 /* BTConfigurationRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 804DC45C2B2D08FF00F17A15 /* BTConfigurationRequest.swift */; }; 8053F05429FAB6B00076F988 /* FPTIBatchData_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8053F05329FAB6B00076F988 /* FPTIBatchData_Tests.swift */; }; 8053F05729FAD4790076F988 /* Encodable+Dictionary_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8053F05629FAD4790076F988 /* Encodable+Dictionary_Tests.swift */; }; 8053F05929FB2F700076F988 /* URL+IsPayPal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8053F05829FB2F700076F988 /* URL+IsPayPal.swift */; }; @@ -703,6 +704,7 @@ 80482F8729D3A571007E5F50 /* BTThreeDSecureRequestedExemptionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTThreeDSecureRequestedExemptionType.swift; sourceTree = ""; }; 804B5711275AAF8500E51A52 /* BTAmericanExpressRewardsBalance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTAmericanExpressRewardsBalance.swift; sourceTree = ""; }; 804DC45A2B2CF3DC00F17A15 /* BTAmexRewardsBalanceRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTAmexRewardsBalanceRequest.swift; sourceTree = ""; }; + 804DC45C2B2D08FF00F17A15 /* BTConfigurationRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTConfigurationRequest.swift; sourceTree = ""; }; 8053F05329FAB6B00076F988 /* FPTIBatchData_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FPTIBatchData_Tests.swift; sourceTree = ""; }; 8053F05629FAD4790076F988 /* Encodable+Dictionary_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Encodable+Dictionary_Tests.swift"; sourceTree = ""; }; 8053F05829FB2F700076F988 /* URL+IsPayPal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+IsPayPal.swift"; sourceTree = ""; }; @@ -1191,6 +1193,7 @@ BED00CB128A57AD400D74AEC /* BTClientTokenError.swift */, BE2F98CF28A2BCCD008EF189 /* BTConfiguration.swift */, BE5C8C0228A2C183004F9130 /* BTConfiguration+Core.swift */, + 804DC45C2B2D08FF00F17A15 /* BTConfigurationRequest.swift */, BE698EA328AD2C10001D9B10 /* BTCoreConstants.swift */, BC17F9BD28D25054004B18CC /* BTGraphQLErrorNode.swift */, BC17F9BB28D24C9E004B18CC /* BTGraphQLErrorTree.swift */, @@ -2772,6 +2775,7 @@ 57CBBCE828B033760037F4EE /* BTGraphQLHTTP.swift in Sources */, BE63A3A7288F3026001936DA /* BTPostalAddress.swift in Sources */, BE2F98D028A2BCCD008EF189 /* BTConfiguration.swift in Sources */, + 804DC45D2B2D08FF00F17A15 /* BTConfigurationRequest.swift in Sources */, BED00CB228A57AD400D74AEC /* BTClientTokenError.swift in Sources */, BE24C67328E73E810067B11A /* BTAPIClientHTTPType.swift in Sources */, BE9EC0982899CF040022EC63 /* BTAPIPinnedCertificates.swift in Sources */, diff --git a/Sources/BraintreeCore/BTAPIClient.swift b/Sources/BraintreeCore/BTAPIClient.swift index 5c5a915d64..2eb1b53d20 100644 --- a/Sources/BraintreeCore/BTAPIClient.swift +++ b/Sources/BraintreeCore/BTAPIClient.swift @@ -143,12 +143,8 @@ import Foundation if let clientToken { configPath = clientToken.configURL.absoluteString } - - struct ConfigPost: Encodable { - let configVersion: String - } - let parameters = ConfigPost(configVersion: "3") + let parameters = BTConfigurationRequest(version: "3") configurationHTTP?.get(configPath, parameters: parameters, shouldCache: true) { [weak self] body, response, error in guard let self else { diff --git a/Sources/BraintreeCore/BTConfigurationRequest.swift b/Sources/BraintreeCore/BTConfigurationRequest.swift new file mode 100644 index 0000000000..be311899ad --- /dev/null +++ b/Sources/BraintreeCore/BTConfigurationRequest.swift @@ -0,0 +1,9 @@ +/// The POST body for `v1/configuration` +struct BTConfigurationRequest: Encodable { + + private let configVersion: String + + init(version: String) { + self.configVersion = version + } +} From eea36bff0afdcf12700136fa0300ab1c8d34bc99 Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Fri, 15 Dec 2023 16:42:12 -0600 Subject: [PATCH 3/5] Update BTHTTP_Tests to use Encodable --- .../BraintreeCoreTests/BTHTTP_Tests.swift | 152 +++++++++--------- UnitTests/BraintreeTestShared/FakeHTTP.swift | 2 +- 2 files changed, 81 insertions(+), 73 deletions(-) diff --git a/UnitTests/BraintreeCoreTests/BTHTTP_Tests.swift b/UnitTests/BraintreeCoreTests/BTHTTP_Tests.swift index a59cb41c60..0b2d728eb0 100644 --- a/UnitTests/BraintreeCoreTests/BTHTTP_Tests.swift +++ b/UnitTests/BraintreeCoreTests/BTHTTP_Tests.swift @@ -21,18 +21,6 @@ final class BTHTTP_Tests: XCTestCase { return URL(string: dataURLString)! } - var parameterDictionary: [String: Any] { - [ - "stringParameter": "value", - "crazyStringParameter[]": "crazy%20and&value", - "numericParameter": 42, - "trueBooleanParameter": true, - "falseBooleanParameter": false, - "dictionaryParameter": [ "dictionaryKey": "dictionaryValue" ], - "arrayParameter": ["arrayItem1", "arrayItem2"] - ] - } - var testURLSession: URLSession { let testConfiguration: URLSessionConfiguration = URLSessionConfiguration.ephemeral testConfiguration.protocolClasses = [BTHTTPTestProtocol.self] @@ -650,67 +638,87 @@ final class BTHTTP_Tests: XCTestCase { } // MARK: - Parameters tests + + struct SampleRequest: Encodable { + enum CodingKeys: String, CodingKey { + case stringParameter + case crazyStringParameter = "crazyStringParameter[]" + case numericParameter + case trueBooleanParameter + case falseBooleanParameter + case dictionaryParameter + case arrayParameter + } + + let stringParameter = "value" + let crazyStringParameter = "crazy%20and&value" + let numericParameter = 42 + let trueBooleanParameter = true + let falseBooleanParameter = false + let dictionaryParameter = ["dictionaryKey": "dictionaryValue"] + let arrayParameter = ["arrayItem1", "arrayItem2"] + } -// func testTransmitsTheParametersAsURLEncodedQueryParameters() { -// let expectation = expectation(description: "GET request") -// let expectedQueryParameters = [ -// "numericParameter=42", -// "falseBooleanParameter=0", -// "dictionaryParameter%5BdictionaryKey%5D=dictionaryValue", -// "trueBooleanParameter=1", -// "stringParameter=value", -// "crazyStringParameter%5B%5D=crazy%2520and%26value", -// "arrayParameter%5B%5D=arrayItem1", -// "arrayParameter%5B%5D=arrayItem2" -// ] -// -// http?.get("200.json", parameters: parameterDictionary) { body, response, error in -// XCTAssertNotNil(body) -// XCTAssertNotNil(response) -// XCTAssertNil(error) -// -// let httpRequest = BTHTTPTestProtocol.parseRequestFromTestResponseBody(body!) -// let actualQueryComponents = httpRequest.url?.query?.components(separatedBy: "&") -// -// expectedQueryParameters.forEach { expectedComponent in -// XCTAssertTrue(actualQueryComponents!.contains(expectedComponent)) -// } -// -// expectation.fulfill() -// } -// -// waitForExpectations(timeout: 2) -// } - -// func testTransmitsTheParametersAsJSON() { -// let expectation = expectation(description: "POST request") -// let expectedParameters: [String: Any] = [ -// "numericParameter": 42, -// "falseBooleanParameter": false, -// "dictionaryParameter": ["dictionaryKey": "dictionaryValue"], -// "trueBooleanParameter": true, -// "stringParameter": "value", -// "crazyStringParameter[]": "crazy%20and&value", -// "arrayParameter": [ "arrayItem1", "arrayItem2" ], -// "authorization_fingerprint": "test-authorization-fingerprint" -// ] -// -// http?.post("200.json", parameters: parameterDictionary) { body, response, error in -// XCTAssertNotNil(body) -// XCTAssertNotNil(response) -// XCTAssertNil(error) -// -// let httpRequest = BTHTTPTestProtocol.parseRequestFromTestResponseBody(body!) -// let httpRequestBody = BTHTTPTestProtocol.parseRequestBodyFromTestResponseBody(body!) -// XCTAssertEqual(httpRequest.value(forHTTPHeaderField: "Content-Type"), "application/json; charset=utf-8") -// -// let actualParameters = try? JSONSerialization.jsonObject(with: httpRequestBody.data(using: .utf8)!) as? [String: Any] ?? [:] -// XCTAssertTrue(actualParameters! == expectedParameters) -// expectation.fulfill() -// } -// -// waitForExpectations(timeout: 2) -// } + func testTransmitsTheParametersAsURLEncodedQueryParameters() { + let expectation = expectation(description: "GET request") + let expectedQueryParameters = [ + "numericParameter=42", + "falseBooleanParameter=0", + "dictionaryParameter%5BdictionaryKey%5D=dictionaryValue", + "trueBooleanParameter=1", + "stringParameter=value", + "crazyStringParameter%5B%5D=crazy%2520and%26value", + "arrayParameter%5B%5D=arrayItem1", + "arrayParameter%5B%5D=arrayItem2" + ] + + http?.get("200.json", parameters: SampleRequest()) { body, response, error in + XCTAssertNotNil(body) + XCTAssertNotNil(response) + XCTAssertNil(error) + + let httpRequest = BTHTTPTestProtocol.parseRequestFromTestResponseBody(body!) + let actualQueryComponents = httpRequest.url?.query?.components(separatedBy: "&") + + expectedQueryParameters.forEach { expectedComponent in + XCTAssertTrue(actualQueryComponents!.contains(expectedComponent)) + } + + expectation.fulfill() + } + + waitForExpectations(timeout: 2) + } + + func testTransmitsTheParametersAsJSON() { + let expectation = expectation(description: "POST request") + let expectedParameters: [String: Any] = [ + "numericParameter": 42, + "falseBooleanParameter": false, + "dictionaryParameter": ["dictionaryKey": "dictionaryValue"], + "trueBooleanParameter": true, + "stringParameter": "value", + "crazyStringParameter[]": "crazy%20and&value", + "arrayParameter": [ "arrayItem1", "arrayItem2" ], + "authorization_fingerprint": "test-authorization-fingerprint" + ] + + http?.post("200.json", parameters: SampleRequest()) { body, response, error in + XCTAssertNotNil(body) + XCTAssertNotNil(response) + XCTAssertNil(error) + + let httpRequest = BTHTTPTestProtocol.parseRequestFromTestResponseBody(body!) + let httpRequestBody = BTHTTPTestProtocol.parseRequestBodyFromTestResponseBody(body!) + XCTAssertEqual(httpRequest.value(forHTTPHeaderField: "Content-Type"), "application/json; charset=utf-8") + + let actualParameters = try? JSONSerialization.jsonObject(with: httpRequestBody.data(using: .utf8)!) as? [String: Any] ?? [:] + XCTAssertTrue(actualParameters! == expectedParameters) + expectation.fulfill() + } + + waitForExpectations(timeout: 2) + } // MARK: - DispatchQueue tests diff --git a/UnitTests/BraintreeTestShared/FakeHTTP.swift b/UnitTests/BraintreeTestShared/FakeHTTP.swift index 5e8d46d2e1..516e9accff 100644 --- a/UnitTests/BraintreeTestShared/FakeHTTP.swift +++ b/UnitTests/BraintreeTestShared/FakeHTTP.swift @@ -6,7 +6,7 @@ import Foundation @objc public var POSTRequestCount: Int = 0 @objc public var lastRequestEndpoint: String? public var lastRequestMethod: String? - public var lastRequestParameters: [String: Any]? + @objc public var lastRequestParameters: [String: Any]? var stubMethod: String? var stubEndpoint: String? public var cannedResponse: BTJSON? From 908936b2e5e37a8e8a3eb28abee7cfc52c83b6fc Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Fri, 15 Dec 2023 16:47:48 -0600 Subject: [PATCH 4/5] Cleanup MockAPIClient to use String:Any dict over AnyHashable --- UnitTests/BraintreeTestShared/MockAPIClient.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UnitTests/BraintreeTestShared/MockAPIClient.swift b/UnitTests/BraintreeTestShared/MockAPIClient.swift index e9e7539209..f0efd938d5 100644 --- a/UnitTests/BraintreeTestShared/MockAPIClient.swift +++ b/UnitTests/BraintreeTestShared/MockAPIClient.swift @@ -7,7 +7,7 @@ public class MockAPIClient: BTAPIClient { public var lastPOSTAPIClientHTTPType: BTAPIClientHTTPService? public var lastGETPath = "" - public var lastGETParameters = [:] as [AnyHashable: Any]? + public var lastGETParameters = [:] as [String: Any]? public var lastGETAPIClientHTTPType: BTAPIClientHTTPService? public var postedAnalyticsEvents : [String] = [] From fd5d2dd8f1282c20add02587a0df05641231c58e Mon Sep 17 00:00:00 2001 From: Sammy Cannillo Date: Fri, 15 Dec 2023 16:51:19 -0600 Subject: [PATCH 5/5] Cleanup BTApiClient setting of configVersion value, when always 3 --- Sources/BraintreeCore/BTAPIClient.swift | 4 +--- Sources/BraintreeCore/BTConfigurationRequest.swift | 6 +----- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/Sources/BraintreeCore/BTAPIClient.swift b/Sources/BraintreeCore/BTAPIClient.swift index 2eb1b53d20..9264307d38 100644 --- a/Sources/BraintreeCore/BTAPIClient.swift +++ b/Sources/BraintreeCore/BTAPIClient.swift @@ -144,9 +144,7 @@ import Foundation configPath = clientToken.configURL.absoluteString } - let parameters = BTConfigurationRequest(version: "3") - - configurationHTTP?.get(configPath, parameters: parameters, shouldCache: true) { [weak self] body, response, error in + configurationHTTP?.get(configPath, parameters: BTConfigurationRequest(), shouldCache: true) { [weak self] body, response, error in guard let self else { completion(nil, BTAPIClientError.deallocated) return diff --git a/Sources/BraintreeCore/BTConfigurationRequest.swift b/Sources/BraintreeCore/BTConfigurationRequest.swift index be311899ad..8f742005e8 100644 --- a/Sources/BraintreeCore/BTConfigurationRequest.swift +++ b/Sources/BraintreeCore/BTConfigurationRequest.swift @@ -1,9 +1,5 @@ /// The POST body for `v1/configuration` struct BTConfigurationRequest: Encodable { - private let configVersion: String - - init(version: String) { - self.configVersion = version - } + private let configVersion = "3" }