Skip to content
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
12 changes: 12 additions & 0 deletions PayPal.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
3B783DC32B7A69C2004623DB /* FakeUpdateSetupTokenResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B783DC22B7A69C2004623DB /* FakeUpdateSetupTokenResponse.swift */; };
3B79E4F72A8503CA00C01D06 /* UpdateVaultVariables.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B79E4F62A8503C900C01D06 /* UpdateVaultVariables.swift */; };
3B80D50C2A27979000D2EAC4 /* FailingJSONEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B80D50B2A27979000D2EAC4 /* FailingJSONEncoder.swift */; };
3B857B172E69E9D8006F3CBC /* AuthenticationTokenServiceAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B857B162E69E99E006F3CBC /* AuthenticationTokenServiceAPI.swift */; };
3B857B192E69FACA006F3CBC /* AuthenticationSecureTokenServiceAPI_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B857B182E69FAB9006F3CBC /* AuthenticationSecureTokenServiceAPI_Tests.swift */; };
3B857B1B2E6A02F5006F3CBC /* AuthenticationTokenResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B857B1A2E6A02ED006F3CBC /* AuthenticationTokenResponse.swift */; };
3B8F848C2D93380B00A151AC /* TestShared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 80E743F8270E40CE00BACECA /* TestShared.framework */; };
3BD82DBB2A835AF900CBE764 /* UpdateSetupTokenResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BD82DBA2A835AF900CBE764 /* UpdateSetupTokenResponse.swift */; };
3BDB34942A80CE6E008100D7 /* CardVaultRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BDB34932A80CE6E008100D7 /* CardVaultRequest.swift */; };
Expand Down Expand Up @@ -199,6 +202,9 @@
3B783DC22B7A69C2004623DB /* FakeUpdateSetupTokenResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeUpdateSetupTokenResponse.swift; sourceTree = "<group>"; };
3B79E4F62A8503C900C01D06 /* UpdateVaultVariables.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateVaultVariables.swift; sourceTree = "<group>"; };
3B80D50B2A27979000D2EAC4 /* FailingJSONEncoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailingJSONEncoder.swift; sourceTree = "<group>"; };
3B857B162E69E99E006F3CBC /* AuthenticationTokenServiceAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationTokenServiceAPI.swift; sourceTree = "<group>"; };
3B857B182E69FAB9006F3CBC /* AuthenticationSecureTokenServiceAPI_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationSecureTokenServiceAPI_Tests.swift; sourceTree = "<group>"; };
3B857B1A2E6A02ED006F3CBC /* AuthenticationTokenResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationTokenResponse.swift; sourceTree = "<group>"; };
3B8F84852D93318D00A151AC /* UpdateClientConfigAP_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateClientConfigAP_Tests.swift; sourceTree = "<group>"; };
3B8F84912D933FAE00A151AC /* MockUpdateClientConfigAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUpdateClientConfigAPI.swift; sourceTree = "<group>"; };
3BD82DBA2A835AF900CBE764 /* UpdateSetupTokenResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateSetupTokenResponse.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -467,6 +473,7 @@
3BF06F422E16477D002CA7B4 /* CorePaymentTests */ = {
isa = PBXGroup;
children = (
3B857B182E69FAB9006F3CBC /* AuthenticationSecureTokenServiceAPI_Tests.swift */,
3B8F84852D93318D00A151AC /* UpdateClientConfigAP_Tests.swift */,
);
path = CorePaymentTests;
Expand Down Expand Up @@ -675,6 +682,8 @@
BEA100E526EF9EDA0036A6A5 /* Networking */ = {
isa = PBXGroup;
children = (
3B857B1A2E6A02ED006F3CBC /* AuthenticationTokenResponse.swift */,
3B857B162E69E99E006F3CBC /* AuthenticationTokenServiceAPI.swift */,
E646231928369B71008AC8E1 /* GraphQL */,
BEA100E626EF9EDA0036A6A5 /* NetworkingClient.swift */,
804E628529380B04004B9FEF /* AnalyticsService.swift */,
Expand Down Expand Up @@ -1163,6 +1172,7 @@
buildActionMask = 2147483647;
files = (
808EEA81291321FE001B6765 /* AnalyticsEventData_Tests.swift in Sources */,
3B857B192E69FACA006F3CBC /* AuthenticationSecureTokenServiceAPI_Tests.swift in Sources */,
8036C1E5270F9BE700C0F091 /* Environment_Tests.swift in Sources */,
80B96AAE2A980F6B00C62916 /* MockTrackingEventsAPI.swift in Sources */,
80FC261D29847AC7008EC841 /* HTTP_Tests.swift in Sources */,
Expand All @@ -1180,13 +1190,15 @@
files = (
E6022E802857C6BE008B0E27 /* GraphQLHTTPResponse.swift in Sources */,
80E643832A1EBBD2008FD705 /* HTTPResponse.swift in Sources */,
3B857B1B2E6A02F5006F3CBC /* AuthenticationTokenResponse.swift in Sources */,
807C5E6929102D9800ECECD8 /* AnalyticsEventData.swift in Sources */,
3BF06F3D2E15CAB4002CA7B4 /* UpdateClientConfigAPI.swift in Sources */,
80E237DF2A84434B00FF18CA /* HTTPRequest.swift in Sources */,
3BF06F3F2E15CC35002CA7B4 /* ClientConfigResponse.swift in Sources */,
8021B69029144E6D000FBC54 /* PayPalCoreConstants.swift in Sources */,
807D56B02A869F97009E591D /* GraphQLErrorResponse.swift in Sources */,
80DB2F762980795D00CFB86A /* CorePaymentsError.swift in Sources */,
3B857B172E69E9D8006F3CBC /* AuthenticationTokenServiceAPI.swift in Sources */,
06CE009926F3D1660000CC46 /* CoreConfig.swift in Sources */,
BEA100EC26EFA7790036A6A5 /* HTTPMethod.swift in Sources */,
BEA100EE26EFA7990036A6A5 /* HTTPHeader.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Foundation

@_documentation(visibility: private)
public struct AuthTokenResponse: Decodable {

let accessToken: String
let tokenType: String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import Foundation

@_documentation(visibility: private)
public class AuthenticationSecureTokenServiceAPI {

// MARK: - Private Properties

private var coreConfig: CoreConfig
private var networkingClient: NetworkingClient

// MARK: - Initializer

public init(coreConfig: CoreConfig) {
self.coreConfig = coreConfig
self.networkingClient = NetworkingClient(coreConfig: coreConfig)
}

/// Exposed for injecting MockNetworkingClient in tests
init(coreConfig: CoreConfig, networkingClient: NetworkingClient) {
self.coreConfig = coreConfig
self.networkingClient = networkingClient
}

// MARK: - Internal Methods

public func createLowScopedAccessToken() async throws -> AuthTokenResponse {
let restRequest = RESTRequest(
path: "v1/oauth2/token",
method: .post,
postParameters: "grant_type=client_credentials&response_type=token",
contentType: .formURLEncoded
)

let httpResponse = try await networkingClient.fetch(request: restRequest)
return try HTTPResponseParser().parseREST(httpResponse, as: AuthTokenResponse.self)
}
}
12 changes: 8 additions & 4 deletions Sources/CorePayments/Networking/NetworkingClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,19 @@ public class NetworkingClient {
.authorization: "Basic \(base64EncodedCredentials)"
]
if request.method == .post {
headers[.contentType] = "application/json"
headers[.contentType] = request.contentType.headerValue
}

// TODO: - Move JSON encoding into custom class, similar to HTTPResponseParser
var data: Data?
if let postBody = request.postParameters {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
data = try encoder.encode(postBody)
if request.contentType == .formURLEncoded, let formString = postBody as? String {
data = formString.data(using: .utf8)
} else {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
data = try encoder.encode(postBody)
}
}

let httpRequest = HTTPRequest(headers: headers, method: request.method, url: url, body: data)
Expand Down
20 changes: 19 additions & 1 deletion Sources/CorePayments/Networking/RESTRequest.swift
Original file line number Diff line number Diff line change
@@ -1,22 +1,40 @@
import Foundation

@_documentation(visibility: private)
public enum ContentType {
case json
case formURLEncoded

var headerValue: String {
switch self {
case .json:
return "application/json"
case .formURLEncoded:
return "application/x-www-form-urlencoded"
}
}
}

@_documentation(visibility: private)
public struct RESTRequest {

var path: String
var method: HTTPMethod
var queryParameters: [String: String]?
var postParameters: Encodable?
var contentType: ContentType

public init(
path: String,
method: HTTPMethod,
queryParameters: [String: String]? = nil,
postParameters: Encodable? = nil
postParameters: Encodable? = nil,
contentType: ContentType = .json
) {
self.path = path
self.method = method
self.queryParameters = queryParameters
self.postParameters = postParameters
self.contentType = contentType
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import XCTest
@testable import CorePayments
@testable import TestShared

class AuthenticationSecureTokenServiceAPI_Tests: XCTestCase {

// MARK: - Helper Properties

var sut: AuthenticationSecureTokenServiceAPI!
var mockNetworkingClient: MockNetworkingClient!
var coreConfig = CoreConfig(clientID: "fake-client-id", environment: .sandbox)

// MARK: - Test lifecycle

override func setUp() {
super.setUp()

mockNetworkingClient = MockNetworkingClient(coreConfig: coreConfig)
sut = AuthenticationSecureTokenServiceAPI(coreConfig: coreConfig, networkingClient: mockNetworkingClient)
}

// MARK: - Tests

func testCreateLowScopedAccessToken_UsesFormURLEncodedContentType() async throws {

let responseBodyString = """
{
"access_token": "mock-token",
"token_type": "Bearer"
}
"""

let responseBody = responseBodyString.data(using: .utf8)

mockNetworkingClient.stubHTTPResponse = HTTPResponse(status: 200, body: responseBody)

_ = try await sut.createLowScopedAccessToken()

guard let capturedRequest = mockNetworkingClient.capturedRESTRequest else {
XCTFail("No REST request was captured")
return
}

XCTAssertEqual(capturedRequest.path, "v1/oauth2/token")
XCTAssertEqual(capturedRequest.method, .post)
XCTAssertEqual(capturedRequest.contentType, .formURLEncoded)
if let postParams = capturedRequest.postParameters as? String {
XCTAssertEqual(postParams, "grant_type=client_credentials&response_type=token")
} else {
XCTFail("Post parameters should be a String for form-urlencoded requests")
}
}
}