From ecef7ff0dda5d76b366ca05ec230dcae88a6bdb6 Mon Sep 17 00:00:00 2001 From: ahmadAlfhajri <82349749+ahmadAlfhajri@users.noreply.github.com> Date: Sat, 21 Dec 2024 23:06:07 +0800 Subject: [PATCH 01/13] Add Integration Test --- .github/workflows/ios-test-workflow.yml | 95 ++ Sources/Xendit/XDTCards.swift | 228 ++- Sources/Xendit/Xendit.swift | 156 +- Tests/XenditTests.xctestplan | 64 + Tests/XenditTests/Helpers/TestCards.swift | 2 + .../XenditIntegrationTestsV3.swift | 1453 +++++++++++++++++ Xendit.xcodeproj/project.pbxproj | 19 +- .../contents.xcworkspacedata | 2 +- .../xcshareddata/swiftpm/Package.resolved | 24 + .../xcschemes/Xendit-Package.xcscheme | 50 + .../xcschemes/XenditPackageTests.xcscheme | 67 + Xendit.xcworkspace/contents.xcworkspacedata | 3 + .../xcschemes/XenditExample.xcscheme | 2 +- 13 files changed, 2051 insertions(+), 114 deletions(-) create mode 100644 .github/workflows/ios-test-workflow.yml create mode 100644 Tests/XenditTests.xctestplan create mode 100644 Tests/XenditTests/XenditIntegrationTestsV3.swift create mode 100644 Xendit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 Xendit.xcodeproj/xcshareddata/xcschemes/XenditPackageTests.xcscheme diff --git a/.github/workflows/ios-test-workflow.yml b/.github/workflows/ios-test-workflow.yml new file mode 100644 index 0000000..e070e02 --- /dev/null +++ b/.github/workflows/ios-test-workflow.yml @@ -0,0 +1,95 @@ +name: iOS Tests + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main, develop ] + +jobs: + test: + name: Run Xcode Tests + runs-on: macos-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Select Xcode + run: sudo xcode-select -s /Applications/Xcode.app + + - name: Build and Test + run: | + xcodebuild test \ + -scheme "Xendit-Package" \ + -configuration Debug \ + -destination 'platform=iOS Simulator,name=iPhone 14,OS=latest' \ + -enableCodeCoverage YES \ + -resultBundlePath TestResults.xcresult \ + clean test | xcpretty --report junit && exit ${PIPESTATUS[0]} + + - name: Generate Test Report + if: success() || failure() # Run this step even if tests fail + run: | + xcrun xcresulttool get --format json --path TestResults.xcresult > test_report.json + + - name: Parse test coverage + if: success() || failure() + run: | + xcrun xccov view --report --json TestResults.xcresult > coverage_report.json + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v3 + with: + name: test-results + path: | + test_report.json + coverage_report.json + TestResults.xcresult + build/reports/ + + - name: Upload test results to Github + uses: EnricoMi/publish-unit-test-result-action@v2 + if: always() + with: + junit_files: build/reports/junit.xml + + - name: Create Test Summary + if: always() + run: | + echo "### Test Results Summary" >> $GITHUB_STEP_SUMMARY + echo "---" >> $GITHUB_STEP_SUMMARY + + # Get failed tests specifically + echo "### Failed Tests" >> $GITHUB_STEP_SUMMARY + xcrun xcresulttool get --format json --path TestResults.xcresult | jq -r '.. | select(.identifier? == "com.apple.xcode.tests.failed")? | .._message?' >> $GITHUB_STEP_SUMMARY + + # Get full test results + echo "### Full Test Results" >> $GITHUB_STEP_SUMMARY + xcrun xcresulttool get --format human-readable --path TestResults.xcresult >> $GITHUB_STEP_SUMMARY + + - name: Comment PR with Test Failures + if: failure() && github.event_name == 'pull_request' + uses: actions/github-script@v6 + with: + script: | + const fs = require('fs'); + const testReport = JSON.parse(fs.readFileSync('test_report.json', 'utf8')); + + let failureMessage = '### ❌ Test Failures\n\n'; + // Extract and format test failures from the report + // This will be shown as a PR comment + const failures = testReport.actions.testsRef.tests + .filter(test => test.status === 'Failure') + .map(test => `- ${test.identifier}: ${test.message}`) + .join('\n'); + + failureMessage += failures; + + github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: failureMessage + }); \ No newline at end of file diff --git a/Sources/Xendit/XDTCards.swift b/Sources/Xendit/XDTCards.swift index 397c315..4a17750 100644 --- a/Sources/Xendit/XDTCards.swift +++ b/Sources/Xendit/XDTCards.swift @@ -13,19 +13,34 @@ protocol CanTokenize { // @param tokenizationRequest: Card and billing details // @param onBehalfOf (Optional) Business id for xenPlaform use cases // @param completion callback function when tokenization is completed - static func createToken(fromViewController: UIViewController, tokenizationRequest: XenditTokenizationRequest, onBehalfOf: String?, completion: @escaping (XenditCCToken?, XenditError?) -> Void) + static func createToken( + fromViewController: UIViewController, + tokenizationRequest: XenditTokenizationRequest, + onBehalfOf: String?, + completion: @escaping (XenditCCToken?, XenditError?) -> Void + ) // Retokenization method // @param retokenizationRequest: Token ID and billing details // @param onBehalfOf (Optional) Business id for xenPlaform use cases // @param completion callback function when tokenization is completed - static func createToken(fromViewController: UIViewController, retokenizationRequest: XenditRetokenizationRequest, onBehalfOf: String?, completion: @escaping (XenditCCToken?, XenditError?) -> Void) + static func createToken( + fromViewController: UIViewController, + retokenizationRequest: XenditRetokenizationRequest, + onBehalfOf: String?, + completion: @escaping (XenditCCToken?, XenditError?) -> Void + ) // Store CVN method // @param storeCVNRequest: Token ID and billing details // @param onBehalfOf (Optional) Business id for xenPlaform use cases // @param completion callback function when tokenization is completed - static func storeCVN(fromViewController: UIViewController, storeCVNRequest: XenditStoreCVNRequest, onBehalfOf: String?, completion: @escaping (XenditCCToken?, XenditError?) -> Void) + static func storeCVN( + fromViewController: UIViewController, + storeCVNRequest: XenditStoreCVNRequest, + onBehalfOf: String?, + completion: @escaping (XenditCCToken?, XenditError?) -> Void + ) } protocol CanAuthenticate { @@ -35,7 +50,17 @@ protocol CanAuthenticate { // @param amount The transaction amount // @param onBehalfOf (Optional) Business id for xenPlaform use cases // @param completion callback function when authentication is completed - static func createAuthentication(fromViewController: UIViewController, tokenId: String, amount: NSNumber, onBehalfOf: String?, customer: XenditCustomer?, completion:@escaping (_: XenditAuthentication?, _: XenditError?) -> Void) + static func createAuthentication( + fromViewController: UIViewController, + tokenId: String, + amount: NSNumber, + currency: String?, + onBehalfOf: String?, + customer: XenditCustomer?, + cardCvn: String?, + cardData: XenditCardHolderInformation?, + completion: @escaping (XenditAuthentication?, XenditError?) -> Void + ) } public class XDTCards: CanTokenize, CanAuthenticate { @@ -47,7 +72,12 @@ public class XDTCards: CanTokenize, CanAuthenticate { XDTCards.publishableKey = publishableKey } - public static func createToken(fromViewController: UIViewController, tokenizationRequest: XenditTokenizationRequest, onBehalfOf: String?, completion: @escaping (XenditCCToken?, XenditError?) -> Void) { + public static func createToken( + fromViewController: UIViewController, + tokenizationRequest: XenditTokenizationRequest, + onBehalfOf: String?, + completion: @escaping (XenditCCToken?, XenditError?) -> Void + ) { let logPrefix = "createToken:" let currency = tokenizationRequest.currency @@ -71,13 +101,27 @@ public class XDTCards: CanTokenize, CanAuthenticate { completion(tokenWith3DSRecommendation, nil); }) } else { - handleCreditCardTokenization(fromViewController: fromViewController, authenticatedToken: authenticatedToken, amount: tokenizationRequest.amount ?? 0, currency: currency, onBehalfOf: onBehalfOf, cardCvn: tokenizationRequest.cardData.cardCvn, error: error, completion: completion) + handleCreditCardTokenization( + fromViewController: fromViewController, + authenticatedToken: authenticatedToken, + amount: tokenizationRequest.amount ?? 0, + currency: currency, + onBehalfOf: onBehalfOf, + cardCvn: tokenizationRequest.cardData.cardCvn, + error: error, + completion: completion + ) } } } @available(*, deprecated, message: "Use storeCVN(UIViewController, XenditStoreCVNRequest, String, Callback) instead") - public static func createToken(fromViewController: UIViewController, retokenizationRequest: XenditRetokenizationRequest, onBehalfOf: String?, completion: @escaping (XenditCCToken?, XenditError?) -> Void) { + public static func createToken( + fromViewController: UIViewController, + retokenizationRequest: XenditRetokenizationRequest, + onBehalfOf: String?, + completion: @escaping (XenditCCToken?, XenditError?) -> Void + ) { let logPrefix = "createToken:" if let error = validateRetokenizationRequest(retokenizationRequest: retokenizationRequest) { @@ -94,11 +138,25 @@ public class XDTCards: CanTokenize, CanAuthenticate { XDTApiClient.createTokenRequest(publishableKey: publishableKey!, bodyJson: requestBody, extraHeader: extraHeaders) { (authenticatedToken, error) in - handleCreditCardTokenization(fromViewController: fromViewController, authenticatedToken: authenticatedToken, amount: 0, currency: nil, onBehalfOf: onBehalfOf, cardCvn: retokenizationRequest.cardCvn, error: error, completion: completion) + handleCreditCardTokenization( + fromViewController: fromViewController, + authenticatedToken: authenticatedToken, + amount: 0, + currency: nil, + onBehalfOf: onBehalfOf, + cardCvn: retokenizationRequest.cardCvn, + error: error, + completion: completion + ) } } - public static func storeCVN(fromViewController: UIViewController, storeCVNRequest: XenditStoreCVNRequest, onBehalfOf: String?, completion: @escaping (XenditCCToken?, XenditError?) -> Void) { + public static func storeCVN( + fromViewController: UIViewController, + storeCVNRequest: XenditStoreCVNRequest, + onBehalfOf: String?, + completion: @escaping (XenditCCToken?, XenditError?) -> Void + ) { let logPrefix = "storeCVN:" if let error = validateRetokenizationRequest(retokenizationRequest: storeCVNRequest) { @@ -115,68 +173,27 @@ public class XDTCards: CanTokenize, CanAuthenticate { XDTApiClient.createTokenRequest(publishableKey: publishableKey!, bodyJson: requestBody, extraHeader: extraHeaders) { (authenticatedToken, error) in - handleCreditCardTokenization(fromViewController: fromViewController, authenticatedToken: authenticatedToken, amount: 0, currency: nil, onBehalfOf: onBehalfOf, cardCvn: storeCVNRequest.cardCvn, error: error, completion: completion) + handleCreditCardTokenization( + fromViewController: fromViewController, + authenticatedToken: authenticatedToken, + amount: 0, + currency: nil, + onBehalfOf: onBehalfOf, + cardCvn: storeCVNRequest.cardCvn, + error: error, completion: completion + ) } } - public static func createAuthentication(fromViewController: UIViewController, tokenId: String, amount: NSNumber, onBehalfOf: String?, customer: XenditCustomer?, completion: @escaping (XenditAuthentication?, XenditError?) -> Void) { - - createAuthentication( - fromViewController: fromViewController, - tokenId: tokenId, - amount: amount, - currency: nil, - onBehalfOf: onBehalfOf, - customer: customer, - completion: completion); - } - - public static func createAuthentication(fromViewController: UIViewController, tokenId: String, amount: NSNumber, currency: String?, onBehalfOf: String?, customer: XenditCustomer?, completion: @escaping (XenditAuthentication?, XenditError?) -> Void) { - - createAuthentication( - fromViewController: fromViewController, - tokenId: tokenId, - amount: amount, - currency: currency, - onBehalfOf: onBehalfOf, - customer: customer, - cardCvn: nil, - completion: completion - ); - } - public static func createAuthentication( fromViewController: UIViewController, tokenId: String, amount: NSNumber, - currency: String?, + currency: String? = nil, onBehalfOf: String?, customer: XenditCustomer?, - cardCvn: String?, - completion: @escaping (XenditAuthentication?, XenditError?) -> Void - ) { - createAuthentication( - fromViewController: fromViewController, - tokenId: tokenId, - amount: amount, - currency: currency, - onBehalfOf: onBehalfOf, - customer: customer, - cardCvn: cardCvn, - cardData: nil, - completion: completion - ); - } - - public static func createAuthentication( - fromViewController: UIViewController, - tokenId: String, - amount: NSNumber, - currency: String?, - onBehalfOf: String?, - customer: XenditCustomer?, - cardCvn: String?, - cardData: XenditCardHolderInformation?, + cardCvn: String? = nil, + cardData: XenditCardHolderInformation? = nil, completion: @escaping (XenditAuthentication?, XenditError?) -> Void) { if publishableKey == nil { completion(nil, XenditError(errorCode: "VALIDATION_ERROR", message: "Empty publishable key")) @@ -195,7 +212,16 @@ public class XDTCards: CanTokenize, CanAuthenticate { ) } - private static func create3DS1Authentication(fromViewController: UIViewController, tokenId: String, amount: NSNumber, currency: String?, onBehalfOf: String?, cardCvn: String?, cardData: XenditCardHolderInformation?, completion: @escaping (XenditAuthentication?, XenditError?) -> Void) { + private static func create3DS1Authentication( + fromViewController: UIViewController, + tokenId: String, + amount: NSNumber, + currency: String?, + onBehalfOf: String?, + cardCvn: String?, + cardData: XenditCardHolderInformation?, + completion: @escaping (XenditAuthentication?, XenditError?) -> Void + ) { if publishableKey == nil { completion(nil, XenditError(errorCode: "VALIDATION_ERROR", message: "Empty publishable key")) return @@ -221,8 +247,17 @@ public class XDTCards: CanTokenize, CanAuthenticate { if onBehalfOf != nil || onBehalfOf != "" { extraHeaders["for-user-id"] = onBehalfOf } - XDTApiClient.createAuthenticationRequest(publishableKey: publishableKey!, tokenId: tokenId, bodyJson: requestBody, extraHeader: extraHeaders) { (authentication, error) in - handleCreateAuthentication(fromViewController: fromViewController, authentication: authentication, error: error, completion: completion) + XDTApiClient.createAuthenticationRequest( + publishableKey: publishableKey!, + tokenId: tokenId, + bodyJson: requestBody, + extraHeader: extraHeaders) { (authentication, error) in + handleCreateAuthentication( + fromViewController: fromViewController, + authentication: authentication, + error: error, + completion: completion + ) } } @@ -292,7 +327,16 @@ public class XDTCards: CanTokenize, CanAuthenticate { return nil } - private static func handleCreditCardTokenization(fromViewController: UIViewController, authenticatedToken: XenditAuthenticatedToken?, amount: NSNumber, currency: String?, onBehalfOf: String?, cardCvn: String?, error: XenditError?, completion:@escaping (_ : XenditCCToken?, _ : XenditError?) -> Void) { + private static func handleCreditCardTokenization( + fromViewController: UIViewController, + authenticatedToken: XenditAuthenticatedToken?, + amount: NSNumber, + currency: String?, + onBehalfOf: String?, + cardCvn: String?, + error: XenditError?, + completion:@escaping (_ : XenditCCToken?, _ : XenditError?) -> Void + ) { if let error = error { // handle error message if error.errorCode == "INVALID_USER_ID" { @@ -308,23 +352,33 @@ public class XDTCards: CanTokenize, CanAuthenticate { } if status != nil { - if status == "IN_REVIEW", let authenticationURL = authenticatedToken?.authenticationURL { - cardAuthenticationProvider.authenticate( - fromViewController: fromViewController, - URL: authenticationURL, - authenticatedToken: authenticatedToken!, - completion: completion - ) - } else { + #if INTEGRATION_TEST let token = XenditCCToken(authenticatedToken: authenticatedToken!) completion(token, nil) - } + #else + if status == "IN_REVIEW", let authenticationURL = authenticatedToken?.authenticationURL { + cardAuthenticationProvider.authenticate( + fromViewController: fromViewController, + URL: authenticationURL, + authenticatedToken: authenticatedToken!, + completion: completion + ) + } else { + let token = XenditCCToken(authenticatedToken: authenticatedToken!) + completion(token, nil) + } + #endif } else { completion(nil, XenditError.ServerError()) } } - private static func handleCreateAuthentication(fromViewController: UIViewController, authentication: XenditAuthentication?, error: XenditError?, completion:@escaping (_ : XenditAuthentication?, _ : XenditError?) -> Void) { + private static func handleCreateAuthentication( + fromViewController: UIViewController, + authentication: XenditAuthentication?, + error: XenditError?, + completion:@escaping (_ : XenditAuthentication?, _ : XenditError?) -> Void + ) { if (error != nil) { // handle error message if error!.errorCode == "INVALID_USER_ID" { @@ -337,16 +391,20 @@ public class XDTCards: CanTokenize, CanAuthenticate { let status = authentication?.status if status != nil { - if status == "IN_REVIEW", let authenticationURL = authentication?.authenticationURL { - authenticationProvider.authenticate( - fromViewController: fromViewController, - URL: authenticationURL, - authentication: authentication!, - completion: completion - ) - } else { + #if INTEGRATION_TEST completion(authentication, nil) - } + #else + if status == "IN_REVIEW", let authenticationURL = authentication?.authenticationURL { + authenticationProvider.authenticate( + fromViewController: fromViewController, + URL: authenticationURL, + authentication: authentication!, + completion: completion + ) + } else { + completion(authentication, nil) + } + #endif } else { completion(nil, XenditError.ServerError()) } diff --git a/Sources/Xendit/Xendit.swift b/Sources/Xendit/Xendit.swift index 893e054..a169b00 100755 --- a/Sources/Xendit/Xendit.swift +++ b/Sources/Xendit/Xendit.swift @@ -21,18 +21,38 @@ import XenditObjC public static var publishableKey: String? // Create token method with billing details and customer object - public static func createToken(fromViewController: UIViewController, tokenizationRequest: XenditTokenizationRequest, onBehalfOf: String?, completion:@escaping (_ : XenditCCToken?, _ : XenditError?) -> Void) { + public static func createToken( + fromViewController: UIViewController, + tokenizationRequest: XenditTokenizationRequest, + onBehalfOf: String?, + completion:@escaping (_ : XenditCCToken?, _ : XenditError?) -> Void + ) { XDTSentry.shared.configure() XDTCards.setup(publishableKey: publishableKey!) - XDTCards.createToken(fromViewController: fromViewController, tokenizationRequest: tokenizationRequest, onBehalfOf: onBehalfOf, completion: completion) + XDTCards.createToken( + fromViewController: fromViewController, + tokenizationRequest: tokenizationRequest, + onBehalfOf: onBehalfOf, + completion: completion + ) } // Retokenize method with billing details and customer object @available(*, deprecated, message: "Use storeCVN(UIViewController, XenditStoreCVNRequest, String, Callback) instead") - public static func createToken(fromViewController: UIViewController, retokenizationRequest: XenditRetokenizationRequest, onBehalfOf: String?, completion:@escaping (_ : XenditCCToken?, _ : XenditError?) -> Void) { + public static func createToken( + fromViewController: UIViewController, + retokenizationRequest: XenditRetokenizationRequest, + onBehalfOf: String?, + completion:@escaping (_ : XenditCCToken?, _ : XenditError?) -> Void + ) { XDTSentry.shared.configure() XDTCards.setup(publishableKey: publishableKey!) - XDTCards.createToken(fromViewController: fromViewController, retokenizationRequest: retokenizationRequest, onBehalfOf: onBehalfOf, completion: completion) + XDTCards.createToken( + fromViewController: fromViewController, + retokenizationRequest: retokenizationRequest, + onBehalfOf: onBehalfOf, + completion: completion + ) } // Store CVN with existing token method with billing details and customer object @@ -40,13 +60,24 @@ import XenditObjC fromViewController: UIViewController, storeCVNRequest: XenditStoreCVNRequest, onBehalfOf: String?, - completion:@escaping (_ : XenditCCToken?, _ : XenditError?) -> Void) { + completion:@escaping (_ : XenditCCToken?, _ : XenditError?) -> Void + ) { XDTSentry.shared.configure() XDTCards.setup(publishableKey: publishableKey!) - XDTCards.storeCVN(fromViewController: fromViewController, storeCVNRequest: storeCVNRequest, onBehalfOf: onBehalfOf, completion: completion) + XDTCards.storeCVN( + fromViewController: fromViewController, + storeCVNRequest: storeCVNRequest, + onBehalfOf: onBehalfOf, + completion: completion + ) } - public static func createAuthentication(fromViewController: UIViewController, authenticationRequest: XenditAuthenticationRequest, onBehalfOf: String?, completion:@escaping (_ : XenditAuthentication?, _ : XenditError?) -> Void) { + public static func createAuthentication( + fromViewController: UIViewController, + authenticationRequest: XenditAuthenticationRequest, + onBehalfOf: String?, + completion:@escaping (_ : XenditAuthentication?, _ : XenditError?) -> Void + ) { XDTSentry.shared.configure() XDTCards.setup(publishableKey: publishableKey!) let tokenId = authenticationRequest.tokenId @@ -54,52 +85,137 @@ import XenditObjC let customer = authenticationRequest.customer let currency = authenticationRequest.currency let cardData = authenticationRequest.cardData - XDTCards.createAuthentication(fromViewController: fromViewController, tokenId: tokenId, amount: amount, currency: currency, onBehalfOf: onBehalfOf, customer: customer, cardCvn: authenticationRequest.cardCvn, cardData: cardData, completion: completion) + XDTCards.createAuthentication( + fromViewController: fromViewController, + tokenId: tokenId, + amount: amount, + currency: currency, + onBehalfOf: onBehalfOf, + customer: customer, + cardCvn: authenticationRequest.cardCvn, + cardData: cardData, + completion: completion + ) } @available(*, deprecated, message: "Use createToken(UIViewController, XenditTokenizationRequest, String, Callback) instead") - public static func createToken(fromViewController: UIViewController, cardData: CardData!, shouldAuthenticate: Bool, onBehalfOf: String, completion:@escaping (_ : XenditCCToken?, _ : XenditError?) -> Void) { + public static func createToken( + fromViewController: UIViewController, + cardData: CardData!, + shouldAuthenticate: Bool, + onBehalfOf: String, + completion:@escaping (_ : XenditCCToken?, _ : XenditError?) -> Void + ) { XDTSentry.shared.configure() XDTCards.setup(publishableKey: publishableKey!) let tokenizationRequest = XenditTokenizationRequest(cardData: cardData, shouldAuthenticate: shouldAuthenticate) - XDTCards.createToken(fromViewController: fromViewController, tokenizationRequest: tokenizationRequest, onBehalfOf: onBehalfOf, completion: completion) + XDTCards.createToken( + fromViewController: fromViewController, + tokenizationRequest: tokenizationRequest, + onBehalfOf: onBehalfOf, + completion: completion + ) } @available(*, deprecated, message: "Use createToken(UIViewController, XenditTokenizationRequest, String, Callback) instead") - public static func createToken(fromViewController: UIViewController, cardData: CardData!, completion:@escaping (_ : XenditCCToken?, _ : XenditError?) -> Void) { + public static func createToken( + fromViewController: UIViewController, + cardData: CardData!, + completion:@escaping (_ : XenditCCToken?, _ : XenditError?) -> Void + ) { XDTSentry.shared.configure() XDTCards.setup(publishableKey: publishableKey!) let tokenizationRequest = XenditTokenizationRequest(cardData: cardData, shouldAuthenticate: true) - XDTCards.createToken(fromViewController: fromViewController, tokenizationRequest: tokenizationRequest, onBehalfOf: nil, completion: completion) + XDTCards.createToken( + fromViewController: fromViewController, + tokenizationRequest: tokenizationRequest, + onBehalfOf: nil, + completion: completion + ) } @available(*, deprecated, message: "Use createToken(UIViewController, XenditTokenizationRequest, String, Callback) instead") - public static func createToken(fromViewController: UIViewController, cardData: CardData!, shouldAuthenticate: Bool!, completion:@escaping (_ : XenditCCToken?, _ : XenditError?) -> Void) { + public static func createToken( + fromViewController: UIViewController, + cardData: CardData!, + shouldAuthenticate: Bool!, + completion:@escaping (_ : XenditCCToken?, _ : XenditError?) -> Void + ) { XDTSentry.shared.configure() XDTCards.setup(publishableKey: publishableKey!) let tokenizationRequest = XenditTokenizationRequest(cardData: cardData, shouldAuthenticate: shouldAuthenticate) - XDTCards.createToken(fromViewController: fromViewController, tokenizationRequest: tokenizationRequest, onBehalfOf: nil, completion: completion) + XDTCards.createToken( + fromViewController: fromViewController, + tokenizationRequest: tokenizationRequest, + onBehalfOf: nil, + completion: completion + ) } @available(*, deprecated, message: "Use createAuthentication(UIViewController, XenditAuthenticationRequest, Callback) instead") - public static func createAuthentication(fromViewController: UIViewController, tokenId: String, amount: NSNumber, onBehalfOf: String, completion:@escaping (_ : XenditAuthentication?, _ : XenditError?) -> Void) { + public static func createAuthentication( + fromViewController: UIViewController, + tokenId: String, + amount: NSNumber, + onBehalfOf: String, + completion:@escaping (_ : XenditAuthentication?, _ : XenditError?) -> Void + ) { XDTSentry.shared.configure() XDTCards.setup(publishableKey: publishableKey!) - XDTCards.createAuthentication(fromViewController: fromViewController, tokenId: tokenId, amount: amount, currency: nil, onBehalfOf: onBehalfOf, customer: nil, cardCvn: nil, completion: completion) + XDTCards.createAuthentication( + fromViewController: fromViewController, + tokenId: tokenId, + amount: amount, + currency: nil, + onBehalfOf: onBehalfOf, + customer: nil, + cardCvn: nil, + completion: completion + ) } @available(*, deprecated, message: "Use createAuthentication(UIViewController, XenditAuthenticationRequest, Callback) instead") - public static func createAuthentication(fromViewController: UIViewController, tokenId: String, amount: NSNumber, completion:@escaping (_ : XenditAuthentication?, _ : XenditError?) -> Void) { + public static func createAuthentication( + fromViewController: UIViewController, + tokenId: String, + amount: NSNumber, + completion:@escaping (_ : XenditAuthentication?, _ : XenditError?) -> Void + ) { XDTSentry.shared.configure() XDTCards.setup(publishableKey: publishableKey!) - XDTCards.createAuthentication(fromViewController: fromViewController, tokenId: tokenId, amount: amount, currency: nil, onBehalfOf: nil, customer: nil, cardCvn: nil, completion: completion) + XDTCards.createAuthentication( + fromViewController: fromViewController, + tokenId: tokenId, + amount: amount, + currency: nil, + onBehalfOf: nil, + customer: nil, + cardCvn: nil, + completion: completion + ) } @available(*, deprecated, message: "Use createAuthentication(UIViewController, XenditAuthenticationRequest, Callback) instead") - public static func createAuthentication(fromViewController: UIViewController, tokenId: String, amount: NSNumber, cardCVN: String, completion:@escaping (_ : XenditAuthentication?, _ : XenditError?) -> Void) { + public static func createAuthentication( + fromViewController: UIViewController, + tokenId: String, + amount: NSNumber, + cardCVN: String, + completion:@escaping (_ : XenditAuthentication?, _ : XenditError?) -> Void + ) { XDTSentry.shared.configure() XDTCards.setup(publishableKey: publishableKey!) - XDTCards.createAuthentication(fromViewController: fromViewController, tokenId: tokenId, amount: amount, currency: nil, onBehalfOf: nil, customer: nil, cardCvn: cardCVN, completion: completion) } + XDTCards.createAuthentication( + fromViewController: fromViewController, + tokenId: tokenId, + amount: amount, + currency: nil, + onBehalfOf: nil, + customer: nil, + cardCvn: cardCVN, + completion: completion + ) + } // Card data validation method public static func isCardNumberValid(cardNumber: String) -> Bool { diff --git a/Tests/XenditTests.xctestplan b/Tests/XenditTests.xctestplan new file mode 100644 index 0000000..81db676 --- /dev/null +++ b/Tests/XenditTests.xctestplan @@ -0,0 +1,64 @@ +{ + "configurations" : [ + { + "id" : "DEF526EB-CC7C-4E75-A22F-D0C92A9BA319", + "name" : "Test Scheme Action", + "options" : { + + } + } + ], + "defaultOptions" : { + + }, + "testTargets" : [ + { + "skippedTests" : [ + "CardDataTest", + "CardDataTest\/testCardCVNInvalid()", + "CardDataTest\/testCardCVNValid()", + "CardDataTest\/testCardExpirationInvalid()", + "CardDataTest\/testCardExpirationValid()", + "CardDataTest\/testCardNumberForInvalidAmex()", + "CardDataTest\/testCardNumberForValidAmex()", + "CardDataTest\/testCardNumberValidationForInvalidDankortCard()", + "CardDataTest\/testCardNumberValidationForInvalidDiscoverCard()", + "CardDataTest\/testCardNumberValidationForInvalidJBCCard()", + "CardDataTest\/testCardNumberValidationForInvalidMaestro()", + "CardDataTest\/testCardNumberValidationForInvalidMasterCard()", + "CardDataTest\/testCardNumberValidationForInvalidVisaCard()", + "CardDataTest\/testCardNumberValidationForValidDankortCard()", + "CardDataTest\/testCardNumberValidationForValidDiscoverCard()", + "CardDataTest\/testCardNumberValidationForValidJBCCard()", + "CardDataTest\/testCardNumberValidationForValidMaestro()", + "CardDataTest\/testCardNumberValidationForValidMasterCard()", + "CardDataTest\/testCardNumberValidationForValidVisaCard()", + "CardDataTest\/testCardNumberValidationForValidVisaElectronCard()", + "CardDataTest\/testCreateCardDataModel()", + "CardDataTest\/testPerformanceExample()", + "CreditCardTests", + "CreditCardTests\/testIsValidCardNumber()", + "FingerprintTests", + "FingerprintTests\/testReturnsPayload()", + "JsonEncodeTests", + "JsonEncodeTests\/testEncodeXenditCCToken()", + "LogSanitizerTest", + "LogSanitizerTest\/testArray()", + "LogSanitizerTest\/testDictionary()", + "LogSanitizerTest\/testNestedDictionary()", + "LogSanitizerTest\/testNonJsonBody()", + "XenditIntegrationTests", + "XenditIntegrationTests\/testCreateAuthentication()", + "XenditIntegrationTests\/testCreateToken()", + "XenditIntegrationTests\/testCreateTokenInvalid()", + "XenditIntegrationTestsV3\/testAuthenticationWithSingleUseToken()" + ], + "target" : { + "containerPath" : "container:", + "identifier" : "XenditTests", + "name" : "XenditTests" + } + } + ], + "version" : 1 +} diff --git a/Tests/XenditTests/Helpers/TestCards.swift b/Tests/XenditTests/Helpers/TestCards.swift index 989b121..f3758ad 100644 --- a/Tests/XenditTests/Helpers/TestCards.swift +++ b/Tests/XenditTests/Helpers/TestCards.swift @@ -11,4 +11,6 @@ import Foundation enum TestCard { static let validCardNo3ds = "5200000000000056" static let validCardWith3ds = "4000000000000002" + static let validCard = "4000000000001091" + static let invalidCard = "4000000000001099" } diff --git a/Tests/XenditTests/XenditIntegrationTestsV3.swift b/Tests/XenditTests/XenditIntegrationTestsV3.swift new file mode 100644 index 0000000..3e59c03 --- /dev/null +++ b/Tests/XenditTests/XenditIntegrationTestsV3.swift @@ -0,0 +1,1453 @@ +// +// XenditIntegrationTestsV3.swift +// Xendit +// +// Created by Ahmad Alfhajri on 16/10/2024. +// + +import XCTest +@testable import Xendit + +class XenditIntegrationTestsV3: XCTestCase { + + //Empty VC + private let viewController = UIViewController() + + override func setUp() { + super.setUp() + continueAfterFailure = false + + Xendit.publishableKey = "xnd_public_development_D8wJuWpOY15JvjJyUNfCdDUTRYKGp8CSM3W0ST4d0N4CsugKyoGEIx6b84j1D7Pg" + } + //TODO: Need to setup CARD + + +} +//DATA +/* + - cardNumber: 4000000000001091 + - cardExpMonth: 12 + - cardExpYear: 2030 + - cardCvn: 123 + + */ + +//MARK: - Authentication +extension XenditIntegrationTestsV3 { + //MARK: - CREATE SINGLE USE TOKEN + + //MARK: Create token with invalid card expiry month + // func testCreateSingleUseTokenWithInvalidCardExpiryMonth() { + // let expect = expectation(description: XenditIntegrationTestsV3.setDescription(for: .success, description: "Create token with invalid card expiry month")) + // + // let cardData = getCardData( + // cardNumber: TestCard.validCard, + // cardExpMonth: "13", + // cardExpYear: "2030", + // cardCvn: "123" + // ) + // let amount: NSNumber = 0 + // let currency = "IDR" + // + // let tokenizationRequest = XenditTokenizationRequest.init( + // cardData: cardData, + // isSingleUse: true, + // shouldAuthenticate: true, + // amount: amount, + // currency: currency + // ) + // + // Xendit.createToken( + // fromViewController: viewController, + // tokenizationRequest: tokenizationRequest, + // onBehalfOf: nil) { [weak self] (token, error) in + // + // // Check for expected error conditions + // XCTAssertNotNil(error, XenditIntegrationTestsV3.setDescription(for: .failed, description: "error should not be nil")) + // XCTAssertEqual(error?.errorCode, "VALIDATION_ERROR", XenditIntegrationTestsV3.setDescription(for: .failed, description: "error code should be VALIDATION_ERROR")) + // XCTAssertEqual(error?.message, "Card expiration date is invalid", XenditIntegrationTestsV3.setDescription(for: .failed, description: "error message should indicate invalid expiration date")) + // + // expect.fulfill() + // } + // + // waitForExpectations(timeout: 200) { error in + // XCTAssertNil(error, "Oh, we got timeout") + // } + // } + + + func testAuthenticationWithMultipleUseToken() { + let expect = expectation(description: XenditIntegrationTestsV3.setDescription(for: .success, description: "Authentication With Multiple Use Token")) + + let cardData = getCardData() + let amount: NSNumber = 10000 + let currency = "IDR" + + let tokenizationRequest = XenditTokenizationRequest.init( + cardData: cardData, + isSingleUse: false, + shouldAuthenticate: false, //TODO: Ask dhiar on this + amount: amount, + currency: nil + ) + + Xendit.createToken(fromViewController: viewController, tokenizationRequest: tokenizationRequest, onBehalfOf: nil) { [weak self] (token, error) in + guard let self, let tokenId = token?.id else { XCTAssertNotNil(token, XenditIntegrationTestsV3.setDescription(for: .failed, description: "token.id should not be nil")); return } //TODO: Can it be written in this style? + XCTAssertNil(error, XenditIntegrationTestsV3.setDescription(for: .failed, description: "error should be nil")) + + let authenticationRequest = XenditAuthenticationRequest.init(tokenId: tokenId, amount: amount, currency: currency, cardData: nil) + + Xendit.createAuthentication( + fromViewController: viewController, + authenticationRequest: authenticationRequest, + onBehalfOf: nil + ) { (authentication, error) in + XCTAssertNotNil(authentication, XenditIntegrationTestsV3.setDescription(for: .failed, description: "authentication should not be nil")) + XCTAssertNil(error, XenditIntegrationTestsV3.setDescription(for: .failed, description: "error should be nil")) + XCTAssertTrue(authentication?.status == "IN_REVIEW", XenditIntegrationTestsV3.setDescription(for: .failed, description: "Authentication With Multiple Use Token should be IN_REVIEW")) + expect.fulfill() + } + } + + waitForExpectations(timeout: 200) { error in + XCTAssertNil(error, "Oh, we got timeout") + } + } +} + +private extension XenditIntegrationTestsV3 { + func getCardData( + cardNumber: String = TestCard.validCard, + cardExpMonth: String = "12", + cardExpYear: String = "2030", + cardCvn: String? = nil + ) -> XenditCardData { + let cardData = XenditCardData.init( + cardNumber: cardNumber, + cardExpMonth: cardExpMonth, + cardExpYear: cardExpYear + ) + + //Should it be here or not? + if let cardCvn = cardCvn { + cardData.cardCvn = cardCvn + } + + return cardData + } +} + +//MARK: - Helper +private extension XenditIntegrationTestsV3 { + enum Status: String { + case success = "SUCCESS" + case failed = "FAILED" + } + + static func setDescription(for status: Status, description: String) -> String { + return "\(status.rawValue): \(description)" + } +} + + +// MARK: - Test Constants +private extension XenditIntegrationTestsV3 { + enum TestConstants { + static let defaultTimeout: TimeInterval = 200 + static let defaultAmount: NSNumber = 0 + static let defaultAmount3DS: NSNumber = 10000 + static let defaultCurrency = "IDR" + + enum CardData { + static let validCardNumber = "4000000000001091" + static let invalidCardNumber = "4000000000001099" + static let validExpMonth = "12" + static let invalidExpMonth = "13" + static let validExpYear = "2030" + static let invalidExpYear = "2001" + static let validCVN = "123" + static let invalidCVN = "1234" + } + + enum ErrorMessages { + static let invalidExpirationDate = "Card expiration date is invalid" + static let invalidCardNumber = "Card number is invalid" + static let invalidCVN = "Card CVN is invalid for this card type" + static let authenticationRequired = "You do not have permission to skip authentication, please contact our support to give you permission" + static let unsupportedCurrency = "Invalid currency, your business cannot process transaction with this currency" + static let invalidCurrency = "Currency ZZZ is invalid" + static let emptyCardCVN = "\"cvn\" is not allowed to be empty" + static let missingLastName = "\"card_holder_first_name\" missing required peer \"card_holder_last_name\"" + static let missingFirstName = "\"card_holder_last_name\" missing required peer \"card_holder_first_name\"" + static let phoneRequiresFirstName = "\"card_holder_phone_number\" missing required peer \"card_holder_first_name\"" + static let amountRequired = "\"amount\" is required" + static let midNotFound = "Mid settings not found for this mid label" + } + + enum CardHolder { + static let firstName = "John" + static let lastName = "Doe" + static let email = "johndoe@gmail.com" + static let phoneNumber = "+12345678" + } + + enum BillingDetails { + static let address = "California, random st, 14045" + static let mobileNumber = "+12345678" + static let email = "john.doe@gmail.com" + } + + enum TokenStatus { + static let verified = "VERIFIED" + static let inReview = "IN_REVIEW" + } + + enum Description { + static let createMultipleUseToken = "Create multiple use token" + } + } + + enum TokenType { + case singleUse + case multipleUse + + var isSingleUse: Bool { + switch self { + case .singleUse: return true + case .multipleUse: return false + } + } + } + + enum ErrorCode: String { + case validationError = "VALIDATION_ERROR" + case authenticationRequired = "AUTHENTICATION_REQUIRED_ERROR" + case mismatchCurrency = "MISMATCH_CURRENCY_ERROR" + case invalidCurrency = "INVALID_CURRENCY_ERROR" + case apiValidation = "API_VALIDATION_ERROR" + case midNotFound = "MID_NOT_FOUND_ERROR" + } + + struct CardHolderData { + let firstName: String? + let lastName: String? + let email: String? + let phoneNumber: String? + } + + enum ExpectedResult { + case success(status: String) + case error(code: ErrorCode, message: String) + } + + struct BillingDetails { + let givenNames: String + let middleName: String? + let surname: String + let email: String + let mobileNumber: String + let address: Address + } + + struct Address { + let country: String? + let streetLine1: String? + let streetLine2: String? + let city: String? + let provinceState: String? + let postalCode: String? + let category: String? + + func toXenditAddress() -> XenditAddress { + let address = XenditAddress() + address.country = country + address.streetLine1 = streetLine1 + address.streetLine2 = streetLine2 + address.city = city + address.provinceState = provinceState + address.postalCode = postalCode + address.category = category + + return address + } + } + + struct TokenRequest { + let cardNumber: String + let cardExpMonth: String + let cardExpYear: String + let cardCVN: String? + let amount: NSNumber? + let currency: String? + let shouldAuthenticate: Bool + let cardHolderData: CardHolderData? + let midLabel: String? + let billingDetails: BillingDetails? + let tokenType: TokenType + let expectedResult: ExpectedResult + let completionHandler: ((XenditCCToken?) -> Void)? + + + init( + cardNumber: String, + cardExpMonth: String, + cardExpYear: String, + cardCVN: String?, + amount: NSNumber? = TestConstants.defaultAmount, + currency: String? = nil, + shouldAuthenticate: Bool = true, + cardHolderData: CardHolderData? = nil, + midLabel: String? = nil, + billingDetails: BillingDetails? = nil, + tokenType: TokenType = .singleUse, + expectedResult: ExpectedResult, + completionHandler: ((XenditCCToken?) -> Void)? = nil + ) { + self.cardNumber = cardNumber + self.cardExpMonth = cardExpMonth + self.cardExpYear = cardExpYear + self.cardCVN = cardCVN + self.amount = amount + self.currency = currency + self.shouldAuthenticate = shouldAuthenticate + self.cardHolderData = cardHolderData + self.midLabel = midLabel + self.billingDetails = billingDetails + self.tokenType = tokenType + self.expectedResult = expectedResult + self.completionHandler = completionHandler + } + } + + struct AuthenticationRequest { + let tokenId: String + let amount: NSNumber + let cardCVN: String? + let currency: String? + let cardHolderData: CardHolderData? + let expectedResult: ExpectedResult + + init( + tokenId: String, + amount: NSNumber, + cardCVN: String? = nil, + currency: String? = nil, + cardHolderData: CardHolderData? = nil, + expectedResult: ExpectedResult + ) { + self.tokenId = tokenId + self.amount = amount + self.cardCVN = cardCVN + self.currency = currency + self.cardHolderData = cardHolderData + self.expectedResult = expectedResult + } + } + + struct StoreCVNRequest { + let tokenId: String + let cardCVN: String + let expectedResult: ExpectedResult + } +} + +private extension XCTestExpectation { + static func xenditExpectation(description: String) -> XCTestExpectation { + Self(description: XenditIntegrationTestsV3.setDescription(for: .success, description: description)) + } +} + +// MARK: - Test Helpers +private extension XenditIntegrationTestsV3 { + func runTokenizationTest(request: TokenRequest, createTokenExpectation: XCTestExpectation) { + let cardData = getCardData( + cardNumber: request.cardNumber, + cardExpMonth: request.cardExpMonth, + cardExpYear: request.cardExpYear, + cardCvn: request.cardCVN + ) + + if let holderData = request.cardHolderData { + cardData.cardHolderFirstName = holderData.firstName + cardData.cardHolderLastName = holderData.lastName + cardData.cardHolderEmail = holderData.email + cardData.cardHolderPhoneNumber = holderData.phoneNumber + } + + let tokenizationRequest = XenditTokenizationRequest( + cardData: cardData, + isSingleUse: request.tokenType.isSingleUse, + shouldAuthenticate: request.shouldAuthenticate, + amount: request.amount, + currency: request.currency + ) + + if let billing = request.billingDetails { + tokenizationRequest.billingDetails = XenditBillingDetails() + tokenizationRequest.billingDetails?.givenNames = billing.givenNames + tokenizationRequest.billingDetails?.middleName = billing.middleName + tokenizationRequest.billingDetails?.surname = billing.surname + tokenizationRequest.billingDetails?.email = billing.email + tokenizationRequest.billingDetails?.mobileNumber = billing.mobileNumber + tokenizationRequest.billingDetails?.address = billing.address.toXenditAddress() + } + + if let midLabel = request.midLabel { + tokenizationRequest.midLabel = midLabel + } + + //TODO: Will add flag for this + Xendit.createToken( + fromViewController: viewController, + tokenizationRequest: tokenizationRequest, + onBehalfOf: nil) { response, error in + + switch request.expectedResult { + case let .success(status): + XCTAssertNil(error) + XCTAssertNotNil(response) + XCTAssertEqual(response?.status, status) + + case let .error(code, message): + print("error \(error?.errorCode ?? "NIL"): \(error?.message ?? "NIL")") + XCTAssertNotNil(error) + XCTAssertEqual(error?.errorCode, code.rawValue) + XCTAssertEqual(error?.message?.contains(message), true) + } + + request.completionHandler?(response) + createTokenExpectation.fulfill() + } + + //TODO: check if it is viable method + //Nil was expected for test that rely on expectation from runTokenizationTest + if request.completionHandler == nil { + wait(for: [createTokenExpectation], timeout: TestConstants.defaultTimeout) + } + } + + func runAuthenticationTest(request: AuthenticationRequest, expectation: XCTestExpectation) { + let authenticationRequest = XenditAuthenticationRequest( + tokenId: request.tokenId, + amount: request.amount, + currency: request.currency ?? "", + cardData: nil + ) + + // Add CVN if present + if let cvn = request.cardCVN { + authenticationRequest.cardCvn = cvn + } + + // Add card holder data if present + if let holderData = request.cardHolderData { + authenticationRequest.cardData = XenditCardHolderInformation( + cardHolderFirstName: holderData.firstName, + cardHolderLastName: holderData.lastName, + cardHolderEmail: holderData.email, + cardHolderPhoneNumber: holderData.phoneNumber + ) + } + + Xendit.createAuthentication( + fromViewController: viewController, + authenticationRequest: authenticationRequest, + onBehalfOf: nil) { response, error in + switch request.expectedResult { + case let .success(status): + XCTAssertNil(error) + XCTAssertNotNil(response) + XCTAssertEqual(response?.status, status) + XCTAssertNotNil(response?.id) + XCTAssertNotNil(response?.authenticationURL) + + case let .error(code, message): + XCTAssertNotNil(error) + XCTAssertEqual(error?.errorCode, code.rawValue) + XCTAssertEqual(error?.message?.contains(message), true) + } + + expectation.fulfill() + } + } + + func runStoreCVNTest(request: StoreCVNRequest, expectation: XCTestExpectation) { + Xendit.storeCVN( + fromViewController: viewController, + storeCVNRequest: .init(tokenId: request.tokenId), + onBehalfOf: nil + ) { token, error in + + switch request.expectedResult { + case let .success(status): + XCTAssertNil(error) + XCTAssertNotNil(token) + XCTAssertEqual(token?.status, status) + + // // Verify response contains expected fields + // XCTAssertNotNil(token?.id) + // XCTAssertNotNil(token?.authenticationId) + // XCTAssertEqual(token?.maskedCardNumber?.contains("400000"), true) + // + // // Verify card info + // let cardInfo = token?.cardInfo + // XCTAssertEqual(cardInfo?.bank, TestConstants.SuccessResponse.cardInfoPresent["bank"]) + // XCTAssertEqual(cardInfo?.country, TestConstants.SuccessResponse.cardInfoPresent["country"]) + // XCTAssertEqual(cardInfo?.type, TestConstants.SuccessResponse.cardInfoPresent["type"]) + // XCTAssertEqual(cardInfo?.brand, TestConstants.SuccessResponse.cardInfoPresent["brand"]) + // + // // Verify expiration dates are present + // XCTAssertNotNil(cardInfo?.cardExpirationMonth) + // XCTAssertNotNil(cardInfo?.cardExpirationYear) + + case let .error(code, message): + XCTAssertNotNil(error) + XCTAssertEqual(error?.errorCode, code.rawValue) + XCTAssertEqual(error?.message?.contains(message), true) + } + + expectation.fulfill() + } + } + + func validateError(error: XenditError?, expectedMessage: String) { + XCTAssertNotNil(error, XenditIntegrationTestsV3.setDescription(for: .failed, description: "error should not be nil")) + XCTAssertEqual( + error?.errorCode, + "VALIDATION_ERROR", + XenditIntegrationTestsV3.setDescription(for: .failed, description: "error code should be VALIDATION_ERROR") + ) + XCTAssertEqual( + error?.message, + expectedMessage, + XenditIntegrationTestsV3.setDescription(for: .failed, description: "error message should match expected message") + ) + } +} + +// MARK: - Test Cases +extension XenditIntegrationTestsV3 { + // MARK: - CREATE SINGLE USE TOKEN + func testCreateSingleUseTokenWithInvalidCardExpiryMonth() { + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.invalidExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + expectedResult: .error( + code: .validationError, + message: TestConstants.ErrorMessages.invalidExpirationDate + ) + ) + runTokenizationTest( + request: tokenRequest, + createTokenExpectation: .xenditExpectation(description: "Create token with invalid card expiry month") + ) + } + + func testCreateSingleUseTokenWithInvalidCardExpiryYear() { + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.invalidExpYear, + cardCVN: TestConstants.CardData.validCVN, + expectedResult: .error( + code: .validationError, + message: TestConstants.ErrorMessages.invalidExpirationDate + ) + ) + runTokenizationTest( + request: tokenRequest, + createTokenExpectation: .xenditExpectation(description: "Create token with invalid card expiry year") + ) + } + + //When put to validCard Number didn't manage to finish the flow because it redirect Webview + func testCreateSingleUseTokenWithInvalidCardNumber() { + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.invalidCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + expectedResult: .error( + code: .validationError, + message: TestConstants.ErrorMessages.invalidCardNumber + ) + ) + runTokenizationTest( + request: tokenRequest, + createTokenExpectation: .xenditExpectation(description: "Create token with invalid card number") + ) + + } + + func testCreateSingleUseTokenWithInvalidCardCVN() { + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.invalidCVN, + expectedResult: .error( + code: .validationError, + message: TestConstants.ErrorMessages.invalidCVN + ) + ) + runTokenizationTest( + request: tokenRequest, + createTokenExpectation: .xenditExpectation(description: "Create token with invalid card CVN") + ) + + } + + // Test case for creating token without 3DS (Row 10) + func testCreateSingleUseTokenWithout3DS() { + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + amount: TestConstants.defaultAmount3DS, + shouldAuthenticate: true, + expectedResult: .success(status: TestConstants.TokenStatus.inReview) + ) + + runTokenizationTest( + request: tokenRequest, + createTokenExpectation: .xenditExpectation(description: "Create token without 3DS") + ) + + } + + // Test case for creating token with 3DS disabled (Row 11) + func testCreateSingleUseTokenWith3DSDisabled() { + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + amount: TestConstants.defaultAmount3DS, + shouldAuthenticate: false, + expectedResult: .error( + code: .authenticationRequired, + message: TestConstants.ErrorMessages.authenticationRequired + ) + ) + + runTokenizationTest( + request: tokenRequest, + createTokenExpectation: .xenditExpectation(description: "Create token with 3DS disabled") + ) + + } + + // Test case for creating token with supported currency (Row 12) + func testCreateSingleUseTokenWithSupportedCurrency() { + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + currency: TestConstants.defaultCurrency, + expectedResult: .success(status: TestConstants.TokenStatus.inReview) + ) + + runTokenizationTest( + request: tokenRequest, + createTokenExpectation: .xenditExpectation(description: "Create token with supported currency") + ) + + } + + // Test case for creating token with unsupported currency (Row 13) + func testCreateSingleUseTokenWithUnsupportedCurrency() { + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + amount: TestConstants.defaultAmount3DS, + currency: "GBP", + expectedResult: .error( + code: .mismatchCurrency, + message: TestConstants.ErrorMessages.unsupportedCurrency + ) + ) + + runTokenizationTest( + request: tokenRequest, + createTokenExpectation: .xenditExpectation(description: "Create token with unsupported currency") + ) + + } + + // Test case for creating token with invalid currency (Row 14) + func testCreateSingleUseTokenWithInvalidCurrency() { + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + currency: "ZZZ", + expectedResult: .error( + code: .invalidCurrency, + message: TestConstants.ErrorMessages.invalidCurrency + ) + ) + + runTokenizationTest( + request: tokenRequest, + createTokenExpectation: .xenditExpectation(description: "Create token with invalid currency") + ) + + } + + // Test case for creating token without CVN (Row 15) + func testCreateSingleUseTokenWithoutCVN() { + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: nil, + amount: TestConstants.defaultAmount3DS, + expectedResult: .error( + code: .apiValidation, + message: TestConstants.ErrorMessages.emptyCardCVN + ) + ) + + runTokenizationTest( + request: tokenRequest, + createTokenExpectation: .xenditExpectation(description: "Create token without CVN") + ) + + } + + // Test case for creating token with complete card holder data (Row 16) + func testCreateSingleUseTokenWithCompleteCardHolderData() { + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + cardHolderData: CardHolderData( + firstName: TestConstants.CardHolder.firstName, + lastName: TestConstants.CardHolder.lastName, + email: TestConstants.CardHolder.email, + phoneNumber: TestConstants.CardHolder.phoneNumber + ), + expectedResult: .success(status: TestConstants.TokenStatus.inReview) + ) + + runTokenizationTest( + request: tokenRequest, + createTokenExpectation: .xenditExpectation(description: "Create token with complete card holder data") + ) + + } + + // Test case for creating token with only first name (Row 17) + func testCreateSingleUseTokenWithOnlyFirstName() { + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + cardHolderData: CardHolderData( + firstName: TestConstants.CardHolder.firstName, + lastName: nil, + email: nil, + phoneNumber: nil + ), + expectedResult: .error( + code: .apiValidation, + message: TestConstants.ErrorMessages.missingLastName + ) + ) + + runTokenizationTest( + request: tokenRequest, + createTokenExpectation: .xenditExpectation(description: "Create token with only first name") + ) + + } + + // Test case for creating token with only last name (Row 18) + func testCreateSingleUseTokenWithOnlyLastName() { + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + cardHolderData: CardHolderData( + firstName: nil, + lastName: TestConstants.CardHolder.lastName, + email: nil, + phoneNumber: nil + ), + expectedResult: .error( + code: .apiValidation, + message: TestConstants.ErrorMessages.missingFirstName + ) + ) + + runTokenizationTest( + request: tokenRequest, + createTokenExpectation: .xenditExpectation(description: "Create token with only last name") + ) + + } + + // Test case for creating token with only email (Row 19) + func testCreateSingleUseTokenWithOnlyEmail() { + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + cardHolderData: CardHolderData( + firstName: nil, + lastName: nil, + email: TestConstants.CardHolder.email, // Email is a legacy parameter, allowed alone + phoneNumber: nil + ), + expectedResult: .success(status: TestConstants.TokenStatus.inReview) + ) + + runTokenizationTest( + request: tokenRequest, + createTokenExpectation: .xenditExpectation(description: "Create token with only email") + ) + + } + + // Test case for creating token with only phone number (Row 20) + func testCreateSingleUseTokenWithOnlyPhoneNumber() { + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + cardHolderData: CardHolderData( + firstName: nil, + lastName: nil, + email: nil, + phoneNumber: TestConstants.CardHolder.phoneNumber + ), + expectedResult: .error( + code: .apiValidation, + message: TestConstants.ErrorMessages.phoneRequiresFirstName + ) + ) + + runTokenizationTest( + request: tokenRequest, + createTokenExpectation: .xenditExpectation(description: "Create token with only phone number") + ) + + } + + // Test case for creating token without amount (Row 21) + func testCreateSingleUseTokenWithoutAmount() { + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + amount: nil, + expectedResult: .error( + code: .apiValidation, + message: TestConstants.ErrorMessages.amountRequired + ) + ) + + runTokenizationTest( + request: tokenRequest, + createTokenExpectation: .xenditExpectation(description: "Create token without amount") + ) + + } + + // Test case for creating token with midLabel (Row 22) + func testCreateSingleUseTokenWithMidLabel() { + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + midLabel: "RANDOM", + expectedResult: .error( + code: .midNotFound, + message: TestConstants.ErrorMessages.midNotFound + ) + ) + + runTokenizationTest( + request: tokenRequest, + createTokenExpectation: .xenditExpectation(description: "Create token with midLabel") + ) + + } + + // Test case for creating token with billing details (Row 23) + func testCreateSingleUseTokenWithBillingDetails() { + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + billingDetails: BillingDetails( + givenNames: "John", + middleName: "Hob", + surname: "Doe", + email: "john.doe@gmail.com", + mobileNumber: "+12345678", + address: .init( + country: nil, + streetLine1: "random st", + streetLine2: nil, + city: "California", + provinceState: nil, + postalCode: "14045", + category: nil + ) + ), + expectedResult: .success(status: TestConstants.TokenStatus.inReview) + ) + + runTokenizationTest( + request: tokenRequest, + createTokenExpectation: .xenditExpectation(description: "Create token with billing details") + ) + + } + + //MARK: - CREATE MULTIPLE USE TOKEN + // Test case for creating multiple use token without CVN (Row 24) + func testCreateMultipleUseTokenWithoutCVN() { + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: nil, + tokenType: .multipleUse, + expectedResult: .success(status: TestConstants.TokenStatus.verified) + ) + + runTokenizationTest( + request: tokenRequest, + createTokenExpectation: .xenditExpectation(description: "Create multiple use token without CVN") + ) + + } + + // Test case for creating multiple use token with CVN (Row 25) + func testCreateMultipleUseTokenWithCVN() { + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + tokenType: .multipleUse, + expectedResult: .success(status: TestConstants.TokenStatus.verified) + ) + + runTokenizationTest( + request: tokenRequest, + createTokenExpectation: .xenditExpectation(description: "Create multiple use token with CVN") + ) + + } + + // Test case for creating multiple use token with midLabel (Row 26) + func testCreateMultipleUseTokenWithMidLabel() { + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + midLabel: "RANDOM", + tokenType: .multipleUse, + expectedResult: .error( + code: .midNotFound, + message: TestConstants.ErrorMessages.midNotFound + ) + ) + + runTokenizationTest( + request: tokenRequest, + createTokenExpectation: .xenditExpectation(description: "Create multiple use token with midLabel") + ) + + } + //MARK: - CREATE AUTHENTICATION : MULTIPLE USE TOKEN + // Test case for creating authentication with multiple use token (Row 32) + func testCreateAuthenticationWithMultipleUseToken() { + let createAuthenticationExpectation = expectation(description: "Create authentication") + + // First create multiple use token + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + tokenType: .multipleUse, + expectedResult: .success(status: TestConstants.TokenStatus.verified), + completionHandler: { [weak self] token in + guard let self = self, + let tokenId = token?.id else { + XCTFail("Failed to get token ID") + return + } + + // Then create authentication + let authenticationRequest = AuthenticationRequest( + tokenId: tokenId, + amount: TestConstants.defaultAmount, + currency: TestConstants.defaultCurrency, //Different from Doc: Added currency + expectedResult: .success(status: TestConstants.TokenStatus.inReview) + ) + + self.runAuthenticationTest(request: authenticationRequest, expectation: createAuthenticationExpectation) + } + ) + + runTokenizationTest( + request: tokenRequest, + createTokenExpectation: .xenditExpectation(description: TestConstants.Description.createMultipleUseToken) + ) + + waitForExpectations(timeout: TestConstants.defaultTimeout) + } + + // Test case for creating authentication with CVN (Row 33) + func testCreateAuthenticationWithCVN() { + let createAuthenticationExpectation = expectation(description: "Create authentication with CVN") + + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + tokenType: .multipleUse, + expectedResult: .success(status: TestConstants.TokenStatus.verified), + completionHandler: { [weak self] token in + guard let self = self, + let tokenId = token?.id else { + XCTFail("Failed to get token ID") + return + } + + let authenticationRequest = AuthenticationRequest( + tokenId: tokenId, + amount: TestConstants.defaultAmount, + cardCVN: TestConstants.CardData.validCVN, + currency: TestConstants.defaultCurrency, //Different from Doc: Added currency + expectedResult: .success(status: TestConstants.TokenStatus.inReview) + ) + + self.runAuthenticationTest(request: authenticationRequest, expectation: createAuthenticationExpectation) + } + ) + + runTokenizationTest( + request: tokenRequest, + createTokenExpectation: .xenditExpectation(description: TestConstants.Description.createMultipleUseToken) + ) + + waitForExpectations(timeout: TestConstants.defaultTimeout) + } + + // Test case for creating authentication with currency (Row 34) + func testCreateAuthenticationWithCurrency() { + let createAuthenticationExpectation = expectation(description: "Create authentication with currency") + + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + tokenType: .multipleUse, + expectedResult: .success(status: TestConstants.TokenStatus.verified), + completionHandler: { [weak self] token in + guard let self = self, + let tokenId = token?.id else { + XCTFail("Failed to get token ID") + return + } + + let authenticationRequest = AuthenticationRequest( + tokenId: tokenId, + amount: TestConstants.defaultAmount, + cardCVN: TestConstants.CardData.validCVN, + currency: TestConstants.defaultCurrency, + expectedResult: .success(status: TestConstants.TokenStatus.inReview) + ) + + self.runAuthenticationTest(request: authenticationRequest, expectation: createAuthenticationExpectation) + } + ) + + runTokenizationTest( + request: tokenRequest, + createTokenExpectation: .xenditExpectation(description: TestConstants.Description.createMultipleUseToken) + ) + + waitForExpectations(timeout: TestConstants.defaultTimeout) + } + + // Test case for creating authentication with unsupported currency (Row 35) + func testCreateAuthenticationWithUnsupportedCurrency() { + let createAuthenticationExpectation = expectation(description: "Create authentication with unsupported currency") + + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + tokenType: .multipleUse, + expectedResult: .success(status: TestConstants.TokenStatus.verified), + completionHandler: { [weak self] token in + guard let self = self, + let tokenId = token?.id else { + XCTFail("Failed to get token ID") + return + } + + //TODO: Error lain pulok + //[xendit] data: {"error_code":"MISMATCH_CURRENCY_ERROR","message":"Mid settings not found for this currency"} + + let authenticationRequest = AuthenticationRequest( + tokenId: tokenId, + amount: TestConstants.defaultAmount3DS, + cardCVN: TestConstants.CardData.validCVN, + currency: "GBP", + expectedResult: .error( + code: .mismatchCurrency, + message: TestConstants.ErrorMessages.unsupportedCurrency + ) + ) + + self.runAuthenticationTest(request: authenticationRequest, expectation: createAuthenticationExpectation) + } + ) + + runTokenizationTest( + request: tokenRequest, + createTokenExpectation: .xenditExpectation(description: TestConstants.Description.createMultipleUseToken) + ) + + waitForExpectations(timeout: TestConstants.defaultTimeout) + } + + // Test case for creating authentication with invalid currency (Row 36) + func testCreateAuthenticationWithInvalidCurrency() { + let createAuthenticationExpectation = expectation(description: "Create authentication with invalid currency") + + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + tokenType: .multipleUse, + expectedResult: .success(status: TestConstants.TokenStatus.verified), + completionHandler: { [weak self] token in + guard let self = self, + let tokenId = token?.id else { + XCTFail("Failed to get token ID") + return + } + + let authenticationRequest = AuthenticationRequest( + tokenId: tokenId, + amount: TestConstants.defaultAmount, + cardCVN: TestConstants.CardData.validCVN, + currency: "ZZZ", + expectedResult: .error( + code: .invalidCurrency, + message: TestConstants.ErrorMessages.invalidCurrency + ) + ) + + self.runAuthenticationTest(request: authenticationRequest, expectation: createAuthenticationExpectation) + } + ) + + runTokenizationTest( + request: tokenRequest, + createTokenExpectation: .xenditExpectation(description: TestConstants.Description.createMultipleUseToken) + ) + + waitForExpectations(timeout: TestConstants.defaultTimeout) + } + + // Test case for creating authentication with complete card holder data (Row 37) + func testCreateAuthenticationWithCompleteCardHolderData() { + let createAuthenticationExpectation = expectation(description: "Create authentication with complete card holder data") + + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + tokenType: .multipleUse, + expectedResult: .success(status: TestConstants.TokenStatus.verified), + completionHandler: { [weak self] token in + guard let self = self, + let tokenId = token?.id else { + XCTFail("Failed to get token ID") + return + } + + let authenticationRequest = AuthenticationRequest( + tokenId: tokenId, + amount: TestConstants.defaultAmount, + cardCVN: TestConstants.CardData.validCVN, + currency: TestConstants.defaultCurrency, + cardHolderData: CardHolderData( + firstName: TestConstants.CardHolder.firstName, + lastName: TestConstants.CardHolder.lastName, + email: TestConstants.CardHolder.email, + phoneNumber: TestConstants.CardHolder.phoneNumber + ), + expectedResult: .success(status: TestConstants.TokenStatus.inReview) + ) + + self.runAuthenticationTest(request: authenticationRequest, expectation: createAuthenticationExpectation) + } + ) + + runTokenizationTest( + request: tokenRequest, + createTokenExpectation: .xenditExpectation(description: TestConstants.Description.createMultipleUseToken) + ) + + waitForExpectations(timeout: TestConstants.defaultTimeout) + } + + // Test case for creating authentication with only first name (Row 38) + func testCreateAuthenticationWithOnlyFirstName() { + let createAuthenticationExpectation = expectation(description: "Create authentication with only first name") + + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + tokenType: .multipleUse, + expectedResult: .success(status: TestConstants.TokenStatus.verified), + completionHandler: { [weak self] token in + guard let self = self, + let tokenId = token?.id else { + XCTFail("Failed to get token ID") + return + } + + //TODO: Keno ado currency + let authenticationRequest = AuthenticationRequest( + tokenId: tokenId, + amount: TestConstants.defaultAmount, + cardCVN: TestConstants.CardData.validCVN, + currency: TestConstants.defaultCurrency, + cardHolderData: CardHolderData( + firstName: TestConstants.CardHolder.firstName, + lastName: nil, + email: nil, + phoneNumber: nil + ), + expectedResult: .error( + code: .apiValidation, + message: TestConstants.ErrorMessages.missingLastName + ) + ) + + self.runAuthenticationTest(request: authenticationRequest, expectation: createAuthenticationExpectation) + } + ) + + runTokenizationTest( + request: tokenRequest, + createTokenExpectation: .xenditExpectation(description: TestConstants.Description.createMultipleUseToken) + ) + + waitForExpectations(timeout: TestConstants.defaultTimeout) + } + + // Test case for creating authentication with only last name (Row 39) + func testCreateAuthenticationWithOnlyLastName() { + let createAuthenticationExpectation = expectation(description: "Create authentication with only last name") + + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + tokenType: .multipleUse, + expectedResult: .success(status: TestConstants.TokenStatus.verified), + completionHandler: { [weak self] token in + guard let self = self, + let tokenId = token?.id else { + XCTFail("Failed to get token ID") + return + } + + let authenticationRequest = AuthenticationRequest( + tokenId: tokenId, + amount: TestConstants.defaultAmount, + cardCVN: TestConstants.CardData.validCVN, + currency: TestConstants.defaultCurrency, + cardHolderData: CardHolderData( + firstName: nil, + lastName: TestConstants.CardHolder.lastName, + email: nil, + phoneNumber: nil + ), + expectedResult: .error( + code: .apiValidation, + message: TestConstants.ErrorMessages.missingFirstName + ) + ) + + self.runAuthenticationTest(request: authenticationRequest, expectation: createAuthenticationExpectation) + } + ) + + runTokenizationTest( + request: tokenRequest, + createTokenExpectation: .xenditExpectation(description: TestConstants.Description.createMultipleUseToken) + ) + + waitForExpectations(timeout: TestConstants.defaultTimeout) + } + + // Test case for creating authentication with only email (Row 40) + func testCreateAuthenticationWithOnlyEmail() { + let createAuthenticationExpectation = expectation(description: "Create authentication with only email") + + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + tokenType: .multipleUse, + expectedResult: .success(status: TestConstants.TokenStatus.verified), + completionHandler: { [weak self] token in + guard let self = self, + let tokenId = token?.id else { + XCTFail("Failed to get token ID") + return + } + + let authenticationRequest = AuthenticationRequest( + tokenId: tokenId, + amount: TestConstants.defaultAmount, + cardCVN: TestConstants.CardData.validCVN, + currency: TestConstants.defaultCurrency, + cardHolderData: CardHolderData( + firstName: nil, + lastName: nil, + email: TestConstants.CardHolder.email, + phoneNumber: nil + ), + expectedResult: .success(status: TestConstants.TokenStatus.inReview) + ) + + self.runAuthenticationTest(request: authenticationRequest, expectation: createAuthenticationExpectation) + } + ) + + runTokenizationTest( + request: tokenRequest, + createTokenExpectation: .xenditExpectation(description: TestConstants.Description.createMultipleUseToken) + ) + + waitForExpectations(timeout: TestConstants.defaultTimeout) + } + + // Test case for creating authentication with only phone number (Row 41) + func testCreateAuthenticationWithOnlyPhoneNumber() { + let createAuthenticationExpectation = expectation(description: "Create authentication with only phone number") + + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + tokenType: .multipleUse, + expectedResult: .success(status: TestConstants.TokenStatus.verified), + completionHandler: { [weak self] token in + guard let self = self, + let tokenId = token?.id else { + XCTFail("Failed to get token ID") + return + } + + let authenticationRequest = AuthenticationRequest( + tokenId: tokenId, + amount: TestConstants.defaultAmount, + cardCVN: TestConstants.CardData.validCVN, + currency: TestConstants.defaultCurrency, + cardHolderData: CardHolderData( + firstName: nil, + lastName: nil, + email: nil, + phoneNumber: TestConstants.CardHolder.phoneNumber + ), + expectedResult: .error( + code: .apiValidation, + message: TestConstants.ErrorMessages.phoneRequiresFirstName + ) + ) + + self.runAuthenticationTest(request: authenticationRequest, expectation: createAuthenticationExpectation) + } + ) + + runTokenizationTest( + request: tokenRequest, + createTokenExpectation: .xenditExpectation(description: TestConstants.Description.createMultipleUseToken) + ) + + waitForExpectations(timeout: TestConstants.defaultTimeout) + } + + //MARK: - STORE CVN + // Test case for storing CVN with multiple use token (Row 42) + func testStoreCVNWithMultipleUseToken() { + let storeCVNExpectation = expectation(description: XenditIntegrationTestsV3.setDescription(for: .success, description: "Store CVN for multiple use token")) + + // First create a multiple use token + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + shouldAuthenticate: false, + tokenType: .multipleUse, + expectedResult: .success(status: TestConstants.TokenStatus.verified), + completionHandler: { [weak self] token in + guard let self = self, + let tokenId = token?.id else { + XCTFail("Failed to get token ID") + return + } + + // Then store CVN + let storeCVNRequest = StoreCVNRequest( + tokenId: tokenId, + cardCVN: TestConstants.CardData.validCVN, + expectedResult: .success(status: TestConstants.TokenStatus.verified) + ) + + self.runStoreCVNTest(request: storeCVNRequest, expectation: storeCVNExpectation) + } + ) + + runTokenizationTest( + request: tokenRequest, + createTokenExpectation: .xenditExpectation(description: "Create multiple use token for storing CVN") + ) + + + waitForExpectations(timeout: TestConstants.defaultTimeout) + } +} diff --git a/Xendit.xcodeproj/project.pbxproj b/Xendit.xcodeproj/project.pbxproj index 56392b7..cb2f5e8 100644 --- a/Xendit.xcodeproj/project.pbxproj +++ b/Xendit.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ /* Begin PBXBuildFile section */ 210C1C162CEEE13800D5950A /* XenditCardHolderInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 210C1C152CEEE13800D5950A /* XenditCardHolderInformation.swift */; }; + 2122891C2CFEC1720098D6D2 /* XenditIntegrationTestsV3.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2122891B2CFEC1720098D6D2 /* XenditIntegrationTestsV3.swift */; }; 648184A52BF7556800DC5FBF /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 648184A42BF7556800DC5FBF /* Sentry */; }; 648184A82BF7558D00DC5FBF /* OHHTTPStubs in Frameworks */ = {isa = PBXBuildFile; productRef = 648184A72BF7558D00DC5FBF /* OHHTTPStubs */; }; 648184AA2BF7558D00DC5FBF /* OHHTTPStubsSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 648184A92BF7558D00DC5FBF /* OHHTTPStubsSwift */; }; @@ -109,6 +110,7 @@ /* Begin PBXFileReference section */ 210C1C152CEEE13800D5950A /* XenditCardHolderInformation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XenditCardHolderInformation.swift; sourceTree = ""; }; + 2122891B2CFEC1720098D6D2 /* XenditIntegrationTestsV3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XenditIntegrationTestsV3.swift; sourceTree = ""; }; 644E27A82BB2A82E007B0F1D /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; OBJ_10 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; OBJ_12 /* Log.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = ""; }; @@ -166,9 +168,9 @@ OBJ_71 /* XenditIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XenditIntegrationTests.swift; sourceTree = ""; }; OBJ_72 /* XenditTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XenditTests.swift; sourceTree = ""; }; OBJ_9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - "xendit-sdk-ios-src::Xendit::Product" /* Xendit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Xendit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - "xendit-sdk-ios-src::XenditObjC::Product" /* XenditObjC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = XenditObjC.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - "xendit-sdk-ios-src::XenditTests::Product" /* XenditTests.xctest */ = {isa = PBXFileReference; lastKnownFileType = file; path = XenditTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + "xendit-sdk-ios-src::Xendit::Product" /* Xendit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Xendit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + "xendit-sdk-ios-src::XenditObjC::Product" /* XenditObjC.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = XenditObjC.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + "xendit-sdk-ios-src::XenditTests::Product" /* XenditTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; path = XenditTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -353,6 +355,7 @@ OBJ_64 /* Helpers */, OBJ_69 /* JsonEncodeTest.swift */, OBJ_70 /* LogSanitizerTests.swift */, + 2122891B2CFEC1720098D6D2 /* XenditIntegrationTestsV3.swift */, OBJ_71 /* XenditIntegrationTests.swift */, OBJ_72 /* XenditTests.swift */, ); @@ -572,6 +575,7 @@ buildActionMask = 0; files = ( OBJ_799 /* CardDataTest.swift in Sources */, + 2122891C2CFEC1720098D6D2 /* XenditIntegrationTestsV3.swift in Sources */, OBJ_800 /* CreditCardTests.swift in Sources */, OBJ_801 /* FingerprintTests.swift in Sources */, OBJ_802 /* AuthenticationProviderStub.swift in Sources */, @@ -625,15 +629,16 @@ "$(inherited)", "SWIFT_PACKAGE=1", "DEBUG=1", + "INTEGRATION_TEST=1", ); MACOSX_DEPLOYMENT_TARGET = 10.10; ONLY_ACTIVE_ARCH = YES; - OTHER_SWIFT_FLAGS = "$(inherited) -DXcode"; + OTHER_SWIFT_FLAGS = "$(inherited) -DXcode -DBETULKA"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SUPPORTED_PLATFORMS = "$(AVAILABLE_PLATFORMS)"; SUPPORTS_MACCATALYST = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SWIFT_PACKAGE DEBUG"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG INTEGRATION_TEST SWIFT_PACKAGE"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; USE_HEADERMAP = NO; }; @@ -869,8 +874,8 @@ MACOSX_DEPLOYMENT_TARGET = 11.0; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; - OTHER_SWIFT_FLAGS = "$(inherited) -Xcc -fmodule-map-file=$(SRCROOT)/Xendit.xcodeproj/GeneratedModuleMap/OHHTTPStubs/module.modulemap -Xcc -fmodule-map-file=$(SRCROOT)/Sources/XenditObjC/include/module.modulemap"; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; + OTHER_SWIFT_FLAGS = "$(inherited)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) INTEGRATION_TEST"; SWIFT_VERSION = 5.0; TARGET_NAME = XenditTests; TVOS_DEPLOYMENT_TARGET = 14.0; diff --git a/Xendit.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Xendit.xcodeproj/project.xcworkspace/contents.xcworkspacedata index fe1aa71..919434a 100644 --- a/Xendit.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/Xendit.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,4 @@ - \ No newline at end of file + diff --git a/Xendit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Xendit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..b35947f --- /dev/null +++ b/Xendit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,24 @@ +{ + "originHash" : "d7e0e43420e3fa599e3c710c3fe3d9cdbdb543e9a85bc423361644c780765d59", + "pins" : [ + { + "identity" : "ohhttpstubs", + "kind" : "remoteSourceControl", + "location" : "https://github.com/AliSoftware/OHHTTPStubs.git", + "state" : { + "revision" : "12f19662426d0434d6c330c6974d53e2eb10ecd9", + "version" : "9.1.0" + } + }, + { + "identity" : "sentry-cocoa", + "kind" : "remoteSourceControl", + "location" : "https://github.com/getsentry/sentry-cocoa.git", + "state" : { + "revision" : "0ffc3c6287461962cb2a81e57fc0ee0335d8d56a", + "version" : "8.40.1" + } + } + ], + "version" : 3 +} diff --git a/Xendit.xcodeproj/xcshareddata/xcschemes/Xendit-Package.xcscheme b/Xendit.xcodeproj/xcshareddata/xcschemes/Xendit-Package.xcscheme index dc23021..9faf92e 100644 --- a/Xendit.xcodeproj/xcshareddata/xcschemes/Xendit-Package.xcscheme +++ b/Xendit.xcodeproj/xcshareddata/xcschemes/Xendit-Package.xcscheme @@ -51,6 +51,56 @@ BlueprintName = "XenditTests" ReferencedContainer = "container:Xendit.xcodeproj"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Xendit.xcodeproj/xcshareddata/xcschemes/XenditPackageTests.xcscheme b/Xendit.xcodeproj/xcshareddata/xcschemes/XenditPackageTests.xcscheme new file mode 100644 index 0000000..2871789 --- /dev/null +++ b/Xendit.xcodeproj/xcshareddata/xcschemes/XenditPackageTests.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Xendit.xcworkspace/contents.xcworkspacedata b/Xendit.xcworkspace/contents.xcworkspacedata index f16246a..f53a5e5 100755 --- a/Xendit.xcworkspace/contents.xcworkspacedata +++ b/Xendit.xcworkspace/contents.xcworkspacedata @@ -10,4 +10,7 @@ + + diff --git a/XenditExample/XenditExample.xcodeproj/xcshareddata/xcschemes/XenditExample.xcscheme b/XenditExample/XenditExample.xcodeproj/xcshareddata/xcschemes/XenditExample.xcscheme index b969ea3..e47cba9 100644 --- a/XenditExample/XenditExample.xcodeproj/xcshareddata/xcschemes/XenditExample.xcscheme +++ b/XenditExample/XenditExample.xcodeproj/xcshareddata/xcschemes/XenditExample.xcscheme @@ -42,7 +42,7 @@ skipped = "NO"> From 0e19997ba50637648d173651ecd487e30ceb03e5 Mon Sep 17 00:00:00 2001 From: ahmadAlfhajri <82349749+ahmadAlfhajri@users.noreply.github.com> Date: Sat, 21 Dec 2024 23:09:15 +0800 Subject: [PATCH 02/13] Update rule --- .github/workflows/ios-test-workflow.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ios-test-workflow.yml b/.github/workflows/ios-test-workflow.yml index e070e02..5005fd1 100644 --- a/.github/workflows/ios-test-workflow.yml +++ b/.github/workflows/ios-test-workflow.yml @@ -1,10 +1,8 @@ name: iOS Tests on: - push: - branches: [ main, develop ] - pull_request: - branches: [ main, develop ] + pull_request: + jobs: test: From 4eeda55835caa4250721d14da06471d2b1b802f2 Mon Sep 17 00:00:00 2001 From: ahmadAlfhajri <82349749+ahmadAlfhajri@users.noreply.github.com> Date: Sun, 22 Dec 2024 12:54:33 +0800 Subject: [PATCH 03/13] Update github action to auto select the device destination --- .github/workflows/ios-test-workflow.yml | 35 ++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ios-test-workflow.yml b/.github/workflows/ios-test-workflow.yml index 5005fd1..d99e4ca 100644 --- a/.github/workflows/ios-test-workflow.yml +++ b/.github/workflows/ios-test-workflow.yml @@ -16,12 +16,43 @@ jobs: - name: Select Xcode run: sudo xcode-select -s /Applications/Xcode.app + - name: Get Available Destination + id: get-destination + run: | + # Get all available destinations and save to a file + xcodebuild -scheme "Xendit-Package" -showdestinations | tee destinations.txt + + # Extract iPhone simulator with latest OS + DESTINATION=$(grep "platform:iOS Simulator.*name:iPhone" destinations.txt | \ + awk -F'OS:' '{print $2}' | awk -F',' '{print $1 " " NR " " FNR}' | \ + sort -V | tail -n1 | \ + xargs -I {} grep -m{} "platform:iOS Simulator.*name:iPhone" destinations.txt | tail -n1 | \ + sed -e 's/^[[:space:]]*//') + + if [ -z "$DESTINATION" ]; then + echo "No iPhone simulator destination found!" + exit 1 + fi + + # Set the destination as an output + echo "DESTINATION=$DESTINATION" >> "$GITHUB_OUTPUT" + + # Print the selected destination for logging + echo "Selected destination: $DESTINATION" + + # Also print all available destinations for debugging + echo "All available destinations:" + grep "platform:iOS Simulator.*name:iPhone" destinations.txt + - name: Build and Test run: | + DESTINATION="${{ steps.get-destination.outputs.DESTINATION }}" + echo "Using destination: $DESTINATION" + xcodebuild test \ -scheme "Xendit-Package" \ -configuration Debug \ - -destination 'platform=iOS Simulator,name=iPhone 14,OS=latest' \ + -destination "$DESTINATION" \ -enableCodeCoverage YES \ -resultBundlePath TestResults.xcresult \ clean test | xcpretty --report junit && exit ${PIPESTATUS[0]} @@ -46,6 +77,7 @@ jobs: coverage_report.json TestResults.xcresult build/reports/ + destinations.txt - name: Upload test results to Github uses: EnricoMi/publish-unit-test-result-action@v2 @@ -58,6 +90,7 @@ jobs: run: | echo "### Test Results Summary" >> $GITHUB_STEP_SUMMARY echo "---" >> $GITHUB_STEP_SUMMARY + echo "Using destination: ${{ steps.get-destination.outputs.DESTINATION }}" >> $GITHUB_STEP_SUMMARY # Get failed tests specifically echo "### Failed Tests" >> $GITHUB_STEP_SUMMARY From 6f97993d5124fe03a9d28b06258f1a901710f844 Mon Sep 17 00:00:00 2001 From: ahmadAlfhajri <82349749+ahmadAlfhajri@users.noreply.github.com> Date: Sun, 22 Dec 2024 13:02:08 +0800 Subject: [PATCH 04/13] Update the grep pattern to match any iPhone --- .github/workflows/ios-test-workflow.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ios-test-workflow.yml b/.github/workflows/ios-test-workflow.yml index d99e4ca..a0c1901 100644 --- a/.github/workflows/ios-test-workflow.yml +++ b/.github/workflows/ios-test-workflow.yml @@ -3,7 +3,6 @@ name: iOS Tests on: pull_request: - jobs: test: name: Run Xcode Tests @@ -22,15 +21,20 @@ jobs: # Get all available destinations and save to a file xcodebuild -scheme "Xendit-Package" -showdestinations | tee destinations.txt - # Extract iPhone simulator with latest OS - DESTINATION=$(grep "platform:iOS Simulator.*name:iPhone" destinations.txt | \ - awk -F'OS:' '{print $2}' | awk -F',' '{print $1 " " NR " " FNR}' | \ - sort -V | tail -n1 | \ - xargs -I {} grep -m{} "platform:iOS Simulator.*name:iPhone" destinations.txt | tail -n1 | \ + # Print all destinations for debugging + echo "Available destinations:" + cat destinations.txt + + # Extract simulator with latest OS (works for both iPhone and iPhone SE) + DESTINATION=$(grep -E "platform:iOS Simulator.*name:iPhone" destinations.txt | \ + sort -t':' -k4,4V | \ + tail -n1 | \ sed -e 's/^[[:space:]]*//') if [ -z "$DESTINATION" ]; then - echo "No iPhone simulator destination found!" + echo "Error: No iPhone simulator destination found!" + echo "Available destinations were:" + cat destinations.txt exit 1 fi @@ -39,10 +43,6 @@ jobs: # Print the selected destination for logging echo "Selected destination: $DESTINATION" - - # Also print all available destinations for debugging - echo "All available destinations:" - grep "platform:iOS Simulator.*name:iPhone" destinations.txt - name: Build and Test run: | From aba89a5126789d80edcc3b801525b5f028383a25 Mon Sep 17 00:00:00 2001 From: ahmadAlfhajri <82349749+ahmadAlfhajri@users.noreply.github.com> Date: Sun, 22 Dec 2024 15:21:10 +0800 Subject: [PATCH 05/13] Update the command -destination to be in the correct format - Previously it was using the wrong command, `:` instead of `=` for key value --- .github/workflows/ios-test-workflow.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ios-test-workflow.yml b/.github/workflows/ios-test-workflow.yml index a0c1901..33c6389 100644 --- a/.github/workflows/ios-test-workflow.yml +++ b/.github/workflows/ios-test-workflow.yml @@ -29,7 +29,14 @@ jobs: DESTINATION=$(grep -E "platform:iOS Simulator.*name:iPhone" destinations.txt | \ sort -t':' -k4,4V | \ tail -n1 | \ - sed -e 's/^[[:space:]]*//') + sed -e 's/^[[:space:]]*//' | \ + sed -e 's/[[:space:]]*$//' | \ + sed -e 's/[{}]//g' | \ + sed -e 's/platform:/platform=/g' | \ + sed -e 's/id:/id=/g' | \ + sed -e 's/, /,/g' | \ + sed -e 's/,OS.*//g' | \ + xargs) if [ -z "$DESTINATION" ]; then echo "Error: No iPhone simulator destination found!" From b5e2725233c79e0c7ee227bc0a499004b8073eb9 Mon Sep 17 00:00:00 2001 From: ahmadAlfhajri <82349749+ahmadAlfhajri@users.noreply.github.com> Date: Sun, 22 Dec 2024 15:35:00 +0800 Subject: [PATCH 06/13] Create comprehensive test summary in GitHub's UI instead of EnricoMi/publish-unit-test-result-action - It was because EnricoMi/publish-unit-test-result-action only run on Linux --- .github/workflows/ios-test-workflow.yml | 62 ++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ios-test-workflow.yml b/.github/workflows/ios-test-workflow.yml index 33c6389..9eb55d7 100644 --- a/.github/workflows/ios-test-workflow.yml +++ b/.github/workflows/ios-test-workflow.yml @@ -86,13 +86,63 @@ jobs: build/reports/ destinations.txt - - name: Upload test results to Github - uses: EnricoMi/publish-unit-test-result-action@v2 + - name: Create Test Summary if: always() - with: - junit_files: build/reports/junit.xml + run: | + echo "### Test Results Summary" >> $GITHUB_STEP_SUMMARY + echo "---" >> $GITHUB_STEP_SUMMARY + echo "Using destination: ${{ steps.get-destination.outputs.DESTINATION }}" >> $GITHUB_STEP_SUMMARY - - name: Create Test Summary + if [ -f "TestResults.xcresult" ]; then + # Get test results summary + echo "### Test Results" >> $GITHUB_STEP_SUMMARY + xcrun xcresulttool get --format human-readable --path TestResults.xcresult >> $GITHUB_STEP_SUMMARY + + # Get failed tests if any + echo "### Failed Tests" >> $GITHUB_STEP_SUMMARY + xcrun xcresulttool get --format json --path TestResults.xcresult | \ + jq -r '.. | select(.identifier? == "com.apple.xcode.tests.failed")? | .._message?' >> $GITHUB_STEP_SUMMARY + + # Get code coverage if enabled + echo "### Code Coverage" >> $GITHUB_STEP_SUMMARY + xcrun xccov view --report --json TestResults.xcresult | \ + jq -r '.targets[] | "- \(.name): \(.lineCoverage)%"' >> $GITHUB_STEP_SUMMARY + else + echo "❌ No test results found" >> $GITHUB_STEP_SUMMARY + fi + + - name: Comment PR + if: github.event_name == 'pull_request' + uses: actions/github-script@v6 + with: + script: | + const fs = require('fs'); + const testReport = JSON.parse(fs.readFileSync('test_report.json', 'utf8')); + + let message = ''; + const failures = testReport.actions.testsRef.tests + .filter(test => test.status === 'Failure') + .map(test => `- ${test.identifier}: ${test.message}`); + + if (failures.length > 0) { + message = `### ❌ Test Failures\n\n${failures.join('\n')}`; + } else { + const coverage = JSON.parse(fs.readFileSync('coverage_report.json', 'utf8')); + const coverageSummary = coverage.targets + .map(target => `- ${target.name}: ${(target.lineCoverage * 100).toFixed(2)}%`) + .join('\n'); + + message = `### ✅ All Tests Passed!\n\n` + + `**Code Coverage:**\n${coverageSummary}\n\n` + + `Total tests: ${testReport.actions.testsRef.tests.length}`; + } + + github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: message + }); if: always() run: | echo "### Test Results Summary" >> $GITHUB_STEP_SUMMARY @@ -130,4 +180,4 @@ jobs: repo: context.repo.repo, issue_number: context.issue.number, body: failureMessage - }); \ No newline at end of file + }); From f5375c14b1bc63f5eaced01de5cd331acaba910d Mon Sep 17 00:00:00 2001 From: ahmadAlfhajri <82349749+ahmadAlfhajri@users.noreply.github.com> Date: Sun, 22 Dec 2024 15:45:42 +0800 Subject: [PATCH 07/13] Fix duplicate 'if' condition --- .github/workflows/ios-test-workflow.yml | 50 ++----------------------- 1 file changed, 4 insertions(+), 46 deletions(-) diff --git a/.github/workflows/ios-test-workflow.yml b/.github/workflows/ios-test-workflow.yml index 9eb55d7..16f102b 100644 --- a/.github/workflows/ios-test-workflow.yml +++ b/.github/workflows/ios-test-workflow.yml @@ -65,13 +65,9 @@ jobs: clean test | xcpretty --report junit && exit ${PIPESTATUS[0]} - name: Generate Test Report - if: success() || failure() # Run this step even if tests fail - run: | - xcrun xcresulttool get --format json --path TestResults.xcresult > test_report.json - - - name: Parse test coverage if: success() || failure() run: | + xcrun xcresulttool get --format json --path TestResults.xcresult > test_report.json xcrun xccov view --report --json TestResults.xcresult > coverage_report.json - name: Upload test results @@ -110,8 +106,8 @@ jobs: else echo "❌ No test results found" >> $GITHUB_STEP_SUMMARY fi - - - name: Comment PR + + - name: Comment PR with Results if: github.event_name == 'pull_request' uses: actions/github-script@v6 with: @@ -142,42 +138,4 @@ jobs: repo: context.repo.repo, issue_number: context.issue.number, body: message - }); - if: always() - run: | - echo "### Test Results Summary" >> $GITHUB_STEP_SUMMARY - echo "---" >> $GITHUB_STEP_SUMMARY - echo "Using destination: ${{ steps.get-destination.outputs.DESTINATION }}" >> $GITHUB_STEP_SUMMARY - - # Get failed tests specifically - echo "### Failed Tests" >> $GITHUB_STEP_SUMMARY - xcrun xcresulttool get --format json --path TestResults.xcresult | jq -r '.. | select(.identifier? == "com.apple.xcode.tests.failed")? | .._message?' >> $GITHUB_STEP_SUMMARY - - # Get full test results - echo "### Full Test Results" >> $GITHUB_STEP_SUMMARY - xcrun xcresulttool get --format human-readable --path TestResults.xcresult >> $GITHUB_STEP_SUMMARY - - - name: Comment PR with Test Failures - if: failure() && github.event_name == 'pull_request' - uses: actions/github-script@v6 - with: - script: | - const fs = require('fs'); - const testReport = JSON.parse(fs.readFileSync('test_report.json', 'utf8')); - - let failureMessage = '### ❌ Test Failures\n\n'; - // Extract and format test failures from the report - // This will be shown as a PR comment - const failures = testReport.actions.testsRef.tests - .filter(test => test.status === 'Failure') - .map(test => `- ${test.identifier}: ${test.message}`) - .join('\n'); - - failureMessage += failures; - - github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body: failureMessage - }); + }); \ No newline at end of file From 293bde63812de62297666fb35c3453b5540ba7c2 Mon Sep 17 00:00:00 2001 From: ahmadAlfhajri <82349749+ahmadAlfhajri@users.noreply.github.com> Date: Sun, 22 Dec 2024 16:01:14 +0800 Subject: [PATCH 08/13] Fix upload result test not saved correctly --- .github/workflows/ios-test-workflow.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ios-test-workflow.yml b/.github/workflows/ios-test-workflow.yml index 16f102b..63b2e4f 100644 --- a/.github/workflows/ios-test-workflow.yml +++ b/.github/workflows/ios-test-workflow.yml @@ -62,7 +62,7 @@ jobs: -destination "$DESTINATION" \ -enableCodeCoverage YES \ -resultBundlePath TestResults.xcresult \ - clean test | xcpretty --report junit && exit ${PIPESTATUS[0]} + clean test | xcpretty - name: Generate Test Report if: success() || failure() @@ -76,11 +76,10 @@ jobs: with: name: test-results path: | + TestResults.xcresult/**/* test_report.json coverage_report.json - TestResults.xcresult - build/reports/ - destinations.txt + if-no-files-found: error - name: Create Test Summary if: always() From 424586a2e440b3492e1a8d4a6fd4b11f170d0fdf Mon Sep 17 00:00:00 2001 From: ahmadAlfhajri <82349749+ahmadAlfhajri@users.noreply.github.com> Date: Sun, 22 Dec 2024 16:15:28 +0800 Subject: [PATCH 09/13] Replace report with xcresulttool --- .github/workflows/ios-test-workflow.yml | 134 +++++++++++++----------- 1 file changed, 70 insertions(+), 64 deletions(-) diff --git a/.github/workflows/ios-test-workflow.yml b/.github/workflows/ios-test-workflow.yml index 63b2e4f..230c1d2 100644 --- a/.github/workflows/ios-test-workflow.yml +++ b/.github/workflows/ios-test-workflow.yml @@ -63,78 +63,84 @@ jobs: -enableCodeCoverage YES \ -resultBundlePath TestResults.xcresult \ clean test | xcpretty - + - name: Generate Test Report - if: success() || failure() - run: | - xcrun xcresulttool get --format json --path TestResults.xcresult > test_report.json - xcrun xccov view --report --json TestResults.xcresult > coverage_report.json + uses: kishikawakatsumi/xcresulttool@v1 + with: + path: TestResults.xcresult + if: success() || failure() + + # - name: Generate Test Report + # if: success() || failure() + # run: | + # xcrun xcresulttool get --format json --path TestResults.xcresult > test_report.json + # xcrun xccov view --report --json TestResults.xcresult > coverage_report.json - - name: Upload test results - if: always() - uses: actions/upload-artifact@v3 - with: - name: test-results - path: | - TestResults.xcresult/**/* - test_report.json - coverage_report.json - if-no-files-found: error + # - name: Upload test results + # if: always() + # uses: actions/upload-artifact@v3 + # with: + # name: test-results + # path: | + # TestResults.xcresult/**/* + # test_report.json + # coverage_report.json + # if-no-files-found: error - - name: Create Test Summary - if: always() - run: | - echo "### Test Results Summary" >> $GITHUB_STEP_SUMMARY - echo "---" >> $GITHUB_STEP_SUMMARY - echo "Using destination: ${{ steps.get-destination.outputs.DESTINATION }}" >> $GITHUB_STEP_SUMMARY + # - name: Create Test Summary + # if: always() + # run: | + # echo "### Test Results Summary" >> $GITHUB_STEP_SUMMARY + # echo "---" >> $GITHUB_STEP_SUMMARY + # echo "Using destination: ${{ steps.get-destination.outputs.DESTINATION }}" >> $GITHUB_STEP_SUMMARY - if [ -f "TestResults.xcresult" ]; then - # Get test results summary - echo "### Test Results" >> $GITHUB_STEP_SUMMARY - xcrun xcresulttool get --format human-readable --path TestResults.xcresult >> $GITHUB_STEP_SUMMARY + # if [ -f "TestResults.xcresult" ]; then + # # Get test results summary + # echo "### Test Results" >> $GITHUB_STEP_SUMMARY + # xcrun xcresulttool get --format human-readable --path TestResults.xcresult >> $GITHUB_STEP_SUMMARY - # Get failed tests if any - echo "### Failed Tests" >> $GITHUB_STEP_SUMMARY - xcrun xcresulttool get --format json --path TestResults.xcresult | \ - jq -r '.. | select(.identifier? == "com.apple.xcode.tests.failed")? | .._message?' >> $GITHUB_STEP_SUMMARY + # # Get failed tests if any + # echo "### Failed Tests" >> $GITHUB_STEP_SUMMARY + # xcrun xcresulttool get --format json --path TestResults.xcresult | \ + # jq -r '.. | select(.identifier? == "com.apple.xcode.tests.failed")? | .._message?' >> $GITHUB_STEP_SUMMARY - # Get code coverage if enabled - echo "### Code Coverage" >> $GITHUB_STEP_SUMMARY - xcrun xccov view --report --json TestResults.xcresult | \ - jq -r '.targets[] | "- \(.name): \(.lineCoverage)%"' >> $GITHUB_STEP_SUMMARY - else - echo "❌ No test results found" >> $GITHUB_STEP_SUMMARY - fi + # # Get code coverage if enabled + # echo "### Code Coverage" >> $GITHUB_STEP_SUMMARY + # xcrun xccov view --report --json TestResults.xcresult | \ + # jq -r '.targets[] | "- \(.name): \(.lineCoverage)%"' >> $GITHUB_STEP_SUMMARY + # else + # echo "❌ No test results found" >> $GITHUB_STEP_SUMMARY + # fi - - name: Comment PR with Results - if: github.event_name == 'pull_request' - uses: actions/github-script@v6 - with: - script: | - const fs = require('fs'); - const testReport = JSON.parse(fs.readFileSync('test_report.json', 'utf8')); + # - name: Comment PR with Results + # if: github.event_name == 'pull_request' + # uses: actions/github-script@v6 + # with: + # script: | + # const fs = require('fs'); + # const testReport = JSON.parse(fs.readFileSync('test_report.json', 'utf8')); - let message = ''; - const failures = testReport.actions.testsRef.tests - .filter(test => test.status === 'Failure') - .map(test => `- ${test.identifier}: ${test.message}`); + # let message = ''; + # const failures = testReport.actions.testsRef.tests + # .filter(test => test.status === 'Failure') + # .map(test => `- ${test.identifier}: ${test.message}`); - if (failures.length > 0) { - message = `### ❌ Test Failures\n\n${failures.join('\n')}`; - } else { - const coverage = JSON.parse(fs.readFileSync('coverage_report.json', 'utf8')); - const coverageSummary = coverage.targets - .map(target => `- ${target.name}: ${(target.lineCoverage * 100).toFixed(2)}%`) - .join('\n'); + # if (failures.length > 0) { + # message = `### ❌ Test Failures\n\n${failures.join('\n')}`; + # } else { + # const coverage = JSON.parse(fs.readFileSync('coverage_report.json', 'utf8')); + # const coverageSummary = coverage.targets + # .map(target => `- ${target.name}: ${(target.lineCoverage * 100).toFixed(2)}%`) + # .join('\n'); - message = `### ✅ All Tests Passed!\n\n` + - `**Code Coverage:**\n${coverageSummary}\n\n` + - `Total tests: ${testReport.actions.testsRef.tests.length}`; - } + # message = `### ✅ All Tests Passed!\n\n` + + # `**Code Coverage:**\n${coverageSummary}\n\n` + + # `Total tests: ${testReport.actions.testsRef.tests.length}`; + # } - github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body: message - }); \ No newline at end of file + # github.rest.issues.createComment({ + # owner: context.repo.owner, + # repo: context.repo.repo, + # issue_number: context.issue.number, + # body: message + # }); \ No newline at end of file From 951ca33cb12610062850f1cb5c99c21509a3a2d1 Mon Sep 17 00:00:00 2001 From: ahmadAlfhajri <82349749+ahmadAlfhajri@users.noreply.github.com> Date: Sun, 22 Dec 2024 16:17:10 +0800 Subject: [PATCH 10/13] Wrong indendation --- .github/workflows/ios-test-workflow.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ios-test-workflow.yml b/.github/workflows/ios-test-workflow.yml index 230c1d2..bfabcaa 100644 --- a/.github/workflows/ios-test-workflow.yml +++ b/.github/workflows/ios-test-workflow.yml @@ -63,12 +63,12 @@ jobs: -enableCodeCoverage YES \ -resultBundlePath TestResults.xcresult \ clean test | xcpretty - + - name: Generate Test Report uses: kishikawakatsumi/xcresulttool@v1 - with: - path: TestResults.xcresult - if: success() || failure() + with: + path: TestResults.xcresult + if: success() || failure() # - name: Generate Test Report # if: success() || failure() From 042e336eb54fc447a5219c91a4830703c17b24d3 Mon Sep 17 00:00:00 2001 From: ahmadAlfhajri <82349749+ahmadAlfhajri@users.noreply.github.com> Date: Mon, 23 Dec 2024 10:41:22 +0800 Subject: [PATCH 11/13] Add Skipped test for single use token for 3ds disable, without amount, and without CVN --- .../xcshareddata/xcschemes/Xendit-Package.xcscheme | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Xendit.xcodeproj/xcshareddata/xcschemes/Xendit-Package.xcscheme b/Xendit.xcodeproj/xcshareddata/xcschemes/Xendit-Package.xcscheme index 9faf92e..476c470 100644 --- a/Xendit.xcodeproj/xcshareddata/xcschemes/Xendit-Package.xcscheme +++ b/Xendit.xcodeproj/xcshareddata/xcschemes/Xendit-Package.xcscheme @@ -100,6 +100,15 @@ + + + + + + From 859601525d854dfb5027318cdb16e4e1694a10c8 Mon Sep 17 00:00:00 2001 From: ahmadAlfhajri <82349749+ahmadAlfhajri@users.noreply.github.com> Date: Mon, 23 Dec 2024 10:42:11 +0800 Subject: [PATCH 12/13] Remove the unused workflow --- .github/workflows/ios-test-workflow.yml | 77 +------------------ .../XenditIntegrationTestsV3.swift | 1 - 2 files changed, 1 insertion(+), 77 deletions(-) diff --git a/.github/workflows/ios-test-workflow.yml b/.github/workflows/ios-test-workflow.yml index bfabcaa..209ed9e 100644 --- a/.github/workflows/ios-test-workflow.yml +++ b/.github/workflows/ios-test-workflow.yml @@ -68,79 +68,4 @@ jobs: uses: kishikawakatsumi/xcresulttool@v1 with: path: TestResults.xcresult - if: success() || failure() - - # - name: Generate Test Report - # if: success() || failure() - # run: | - # xcrun xcresulttool get --format json --path TestResults.xcresult > test_report.json - # xcrun xccov view --report --json TestResults.xcresult > coverage_report.json - - # - name: Upload test results - # if: always() - # uses: actions/upload-artifact@v3 - # with: - # name: test-results - # path: | - # TestResults.xcresult/**/* - # test_report.json - # coverage_report.json - # if-no-files-found: error - - # - name: Create Test Summary - # if: always() - # run: | - # echo "### Test Results Summary" >> $GITHUB_STEP_SUMMARY - # echo "---" >> $GITHUB_STEP_SUMMARY - # echo "Using destination: ${{ steps.get-destination.outputs.DESTINATION }}" >> $GITHUB_STEP_SUMMARY - - # if [ -f "TestResults.xcresult" ]; then - # # Get test results summary - # echo "### Test Results" >> $GITHUB_STEP_SUMMARY - # xcrun xcresulttool get --format human-readable --path TestResults.xcresult >> $GITHUB_STEP_SUMMARY - - # # Get failed tests if any - # echo "### Failed Tests" >> $GITHUB_STEP_SUMMARY - # xcrun xcresulttool get --format json --path TestResults.xcresult | \ - # jq -r '.. | select(.identifier? == "com.apple.xcode.tests.failed")? | .._message?' >> $GITHUB_STEP_SUMMARY - - # # Get code coverage if enabled - # echo "### Code Coverage" >> $GITHUB_STEP_SUMMARY - # xcrun xccov view --report --json TestResults.xcresult | \ - # jq -r '.targets[] | "- \(.name): \(.lineCoverage)%"' >> $GITHUB_STEP_SUMMARY - # else - # echo "❌ No test results found" >> $GITHUB_STEP_SUMMARY - # fi - - # - name: Comment PR with Results - # if: github.event_name == 'pull_request' - # uses: actions/github-script@v6 - # with: - # script: | - # const fs = require('fs'); - # const testReport = JSON.parse(fs.readFileSync('test_report.json', 'utf8')); - - # let message = ''; - # const failures = testReport.actions.testsRef.tests - # .filter(test => test.status === 'Failure') - # .map(test => `- ${test.identifier}: ${test.message}`); - - # if (failures.length > 0) { - # message = `### ❌ Test Failures\n\n${failures.join('\n')}`; - # } else { - # const coverage = JSON.parse(fs.readFileSync('coverage_report.json', 'utf8')); - # const coverageSummary = coverage.targets - # .map(target => `- ${target.name}: ${(target.lineCoverage * 100).toFixed(2)}%`) - # .join('\n'); - - # message = `### ✅ All Tests Passed!\n\n` + - # `**Code Coverage:**\n${coverageSummary}\n\n` + - # `Total tests: ${testReport.actions.testsRef.tests.length}`; - # } - - # github.rest.issues.createComment({ - # owner: context.repo.owner, - # repo: context.repo.repo, - # issue_number: context.issue.number, - # body: message - # }); \ No newline at end of file + if: success() || failure() \ No newline at end of file diff --git a/Tests/XenditTests/XenditIntegrationTestsV3.swift b/Tests/XenditTests/XenditIntegrationTestsV3.swift index 3e59c03..7e51783 100644 --- a/Tests/XenditTests/XenditIntegrationTestsV3.swift +++ b/Tests/XenditTests/XenditIntegrationTestsV3.swift @@ -396,7 +396,6 @@ private extension XenditIntegrationTestsV3 { tokenizationRequest.midLabel = midLabel } - //TODO: Will add flag for this Xendit.createToken( fromViewController: viewController, tokenizationRequest: tokenizationRequest, From 7f7533ded8319c9fa5cd477ff538848cd99e0428 Mon Sep 17 00:00:00 2001 From: ahmadAlfhajri <82349749+ahmadAlfhajri@users.noreply.github.com> Date: Thu, 2 Jan 2025 14:54:58 +0800 Subject: [PATCH 13/13] Add integration test for authentication single use token --- .../XenditIntegrationTestsV3.swift | 277 ++++++++++++------ 1 file changed, 186 insertions(+), 91 deletions(-) diff --git a/Tests/XenditTests/XenditIntegrationTestsV3.swift b/Tests/XenditTests/XenditIntegrationTestsV3.swift index 7e51783..c97fa56 100644 --- a/Tests/XenditTests/XenditIntegrationTestsV3.swift +++ b/Tests/XenditTests/XenditIntegrationTestsV3.swift @@ -18,102 +18,12 @@ class XenditIntegrationTestsV3: XCTestCase { continueAfterFailure = false Xendit.publishableKey = "xnd_public_development_D8wJuWpOY15JvjJyUNfCdDUTRYKGp8CSM3W0ST4d0N4CsugKyoGEIx6b84j1D7Pg" + Xendit.setLogLevel(.verbose) } //TODO: Need to setup CARD } -//DATA -/* - - cardNumber: 4000000000001091 - - cardExpMonth: 12 - - cardExpYear: 2030 - - cardCvn: 123 - - */ - -//MARK: - Authentication -extension XenditIntegrationTestsV3 { - //MARK: - CREATE SINGLE USE TOKEN - - //MARK: Create token with invalid card expiry month - // func testCreateSingleUseTokenWithInvalidCardExpiryMonth() { - // let expect = expectation(description: XenditIntegrationTestsV3.setDescription(for: .success, description: "Create token with invalid card expiry month")) - // - // let cardData = getCardData( - // cardNumber: TestCard.validCard, - // cardExpMonth: "13", - // cardExpYear: "2030", - // cardCvn: "123" - // ) - // let amount: NSNumber = 0 - // let currency = "IDR" - // - // let tokenizationRequest = XenditTokenizationRequest.init( - // cardData: cardData, - // isSingleUse: true, - // shouldAuthenticate: true, - // amount: amount, - // currency: currency - // ) - // - // Xendit.createToken( - // fromViewController: viewController, - // tokenizationRequest: tokenizationRequest, - // onBehalfOf: nil) { [weak self] (token, error) in - // - // // Check for expected error conditions - // XCTAssertNotNil(error, XenditIntegrationTestsV3.setDescription(for: .failed, description: "error should not be nil")) - // XCTAssertEqual(error?.errorCode, "VALIDATION_ERROR", XenditIntegrationTestsV3.setDescription(for: .failed, description: "error code should be VALIDATION_ERROR")) - // XCTAssertEqual(error?.message, "Card expiration date is invalid", XenditIntegrationTestsV3.setDescription(for: .failed, description: "error message should indicate invalid expiration date")) - // - // expect.fulfill() - // } - // - // waitForExpectations(timeout: 200) { error in - // XCTAssertNil(error, "Oh, we got timeout") - // } - // } - - - func testAuthenticationWithMultipleUseToken() { - let expect = expectation(description: XenditIntegrationTestsV3.setDescription(for: .success, description: "Authentication With Multiple Use Token")) - - let cardData = getCardData() - let amount: NSNumber = 10000 - let currency = "IDR" - - let tokenizationRequest = XenditTokenizationRequest.init( - cardData: cardData, - isSingleUse: false, - shouldAuthenticate: false, //TODO: Ask dhiar on this - amount: amount, - currency: nil - ) - - Xendit.createToken(fromViewController: viewController, tokenizationRequest: tokenizationRequest, onBehalfOf: nil) { [weak self] (token, error) in - guard let self, let tokenId = token?.id else { XCTAssertNotNil(token, XenditIntegrationTestsV3.setDescription(for: .failed, description: "token.id should not be nil")); return } //TODO: Can it be written in this style? - XCTAssertNil(error, XenditIntegrationTestsV3.setDescription(for: .failed, description: "error should be nil")) - - let authenticationRequest = XenditAuthenticationRequest.init(tokenId: tokenId, amount: amount, currency: currency, cardData: nil) - - Xendit.createAuthentication( - fromViewController: viewController, - authenticationRequest: authenticationRequest, - onBehalfOf: nil - ) { (authentication, error) in - XCTAssertNotNil(authentication, XenditIntegrationTestsV3.setDescription(for: .failed, description: "authentication should not be nil")) - XCTAssertNil(error, XenditIntegrationTestsV3.setDescription(for: .failed, description: "error should be nil")) - XCTAssertTrue(authentication?.status == "IN_REVIEW", XenditIntegrationTestsV3.setDescription(for: .failed, description: "Authentication With Multiple Use Token should be IN_REVIEW")) - expect.fulfill() - } - } - - waitForExpectations(timeout: 200) { error in - XCTAssertNil(error, "Oh, we got timeout") - } - } -} private extension XenditIntegrationTestsV3 { func getCardData( @@ -461,6 +371,7 @@ private extension XenditIntegrationTestsV3 { XCTAssertNotNil(response?.authenticationURL) case let .error(code, message): + print("error \(message) errorcode \(error?.message ?? "emtpy")") XCTAssertNotNil(error) XCTAssertEqual(error?.errorCode, code.rawValue) XCTAssertEqual(error?.message?.contains(message), true) @@ -919,6 +830,190 @@ extension XenditIntegrationTestsV3 { ) } + + //MARK: - CREATE AUTHENTICATION : SINGLE USE TOKEN + + // Test case for creating authentication with single use token (Row 27) + func testCreateAuthenticationWithSingleUseToken() { + let createTokenExpectation = expectation(description: "Create single use token") + let createAuthenticationExpectation = expectation(description: "Create authentication") + + // First create single use token + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + shouldAuthenticate: false, // PRE-REQUISITE: shouldAuthenticate: false + tokenType: .singleUse, + expectedResult: .success(status: TestConstants.TokenStatus.verified), + completionHandler: { [weak self] token in + guard let self = self, + let tokenId = token?.id else { + XCTFail("Failed to get token ID") + return + } + + // Then create authentication + let authenticationRequest = AuthenticationRequest( + tokenId: tokenId, + amount: TestConstants.defaultAmount, + expectedResult: .success(status: TestConstants.TokenStatus.inReview) + ) + + self.runAuthenticationTest(request: authenticationRequest, expectation: createAuthenticationExpectation) + } + ) + + runTokenizationTest(request: tokenRequest, createTokenExpectation: createTokenExpectation) + waitForExpectations(timeout: TestConstants.defaultTimeout) + } + + // Test case for creating authentication with CVN (Row 28-29) + func testCreateAuthenticationWithSingleUseTokenAndCVN() { + let createTokenExpectation = expectation(description: "Create single use token") + let createAuthenticationExpectation = expectation(description: "Create authentication with CVN") + + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + shouldAuthenticate: false, + tokenType: .singleUse, + expectedResult: .success(status: TestConstants.TokenStatus.verified), + completionHandler: { [weak self] token in + guard let self = self, + let tokenId = token?.id else { + XCTFail("Failed to get token ID") + return + } + + let authenticationRequest = AuthenticationRequest( + tokenId: tokenId, + amount: TestConstants.defaultAmount, + cardCVN: "123", // Adding CVN to authentication + expectedResult: .success(status: TestConstants.TokenStatus.inReview) + ) + + self.runAuthenticationTest(request: authenticationRequest, expectation: createAuthenticationExpectation) + } + ) + + runTokenizationTest(request: tokenRequest, createTokenExpectation: createTokenExpectation) + waitForExpectations(timeout: TestConstants.defaultTimeout) + } + + // Test case for creating authentication with supported currency (Row 29) + func testCreateAuthenticationWithSingleUseTokenAndCurrency() { + let createTokenExpectation = expectation(description: "Create single use token") + let createAuthenticationExpectation = expectation(description: "Create authentication with currency") + + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + shouldAuthenticate: false, + tokenType: .singleUse, + expectedResult: .success(status: TestConstants.TokenStatus.verified), + completionHandler: { [weak self] token in + guard let self = self, + let tokenId = token?.id else { + XCTFail("Failed to get token ID") + return + } + + let authenticationRequest = AuthenticationRequest( + tokenId: tokenId, + amount: TestConstants.defaultAmount, + currency: TestConstants.defaultCurrency, + expectedResult: .success(status: TestConstants.TokenStatus.inReview) + ) + + self.runAuthenticationTest(request: authenticationRequest, expectation: createAuthenticationExpectation) + } + ) + + runTokenizationTest(request: tokenRequest, createTokenExpectation: createTokenExpectation) + waitForExpectations(timeout: TestConstants.defaultTimeout) + } + + // Test case for creating authentication with unsupported currency (Row 30) + func testCreateAuthenticationWithSingleUseTokenAndUnsupportedCurrency() { + let createTokenExpectation = expectation(description: "Create single use token") + let createAuthenticationExpectation = expectation(description: "Create authentication with unsupported currency") + + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + shouldAuthenticate: false, + tokenType: .singleUse, + expectedResult: .success(status: TestConstants.TokenStatus.verified), + completionHandler: { [weak self] token in + guard let self = self, + let tokenId = token?.id else { + XCTFail("Failed to get token ID") + return + } + + let authenticationRequest = AuthenticationRequest( + tokenId: tokenId, + amount: TestConstants.defaultAmount, + currency: "GBP", + expectedResult: .error( + code: .mismatchCurrency, + message: TestConstants.ErrorMessages.unsupportedCurrency + ) + ) + + self.runAuthenticationTest(request: authenticationRequest, expectation: createAuthenticationExpectation) + } + ) + + runTokenizationTest(request: tokenRequest, createTokenExpectation: createTokenExpectation) + waitForExpectations(timeout: TestConstants.defaultTimeout) + } + + // Test case for creating authentication with invalid currency (Row 31) + func testCreateAuthenticationWithSingleUseTokenAndInvalidCurrency() { + let createTokenExpectation = expectation(description: "Create single use token") + let createAuthenticationExpectation = expectation(description: "Create authentication with invalid currency") + + let tokenRequest = TokenRequest( + cardNumber: TestConstants.CardData.validCardNumber, + cardExpMonth: TestConstants.CardData.validExpMonth, + cardExpYear: TestConstants.CardData.validExpYear, + cardCVN: TestConstants.CardData.validCVN, + shouldAuthenticate: false, + tokenType: .singleUse, + expectedResult: .success(status: TestConstants.TokenStatus.verified), + completionHandler: { [weak self] token in + guard let self = self, + let tokenId = token?.id else { + XCTFail("Failed to get token ID") + return + } + + let authenticationRequest = AuthenticationRequest( + tokenId: tokenId, + amount: TestConstants.defaultAmount, + currency: "ZZZ", + expectedResult: .error( + code: .invalidCurrency, + message: TestConstants.ErrorMessages.invalidCurrency + ) + ) + + self.runAuthenticationTest(request: authenticationRequest, expectation: createAuthenticationExpectation) + } + ) + + runTokenizationTest(request: tokenRequest, createTokenExpectation: createTokenExpectation) + waitForExpectations(timeout: TestConstants.defaultTimeout) + } //MARK: - CREATE MULTIPLE USE TOKEN // Test case for creating multiple use token without CVN (Row 24)