// // Copyright 2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // import XCTest @testable import Signal final class DonationPaymentDetailsViewControllerTest: XCTestCase { // MARK: - Formatting tests func testFormatCardNumber() { // We test this by "round-tripping". let testCases: [String] = [ "", // American Express "37", "3782 ", "3782 822463 ", "3782 822463 10005", "3782 822463 100051234", // UnionPay "6200 ", "6200 0", "6200 0000 00", "6200 0000 0000 000", "6200 0000 0000 0000", "6200 0000 0000 0000000", // Other types "42", "4242 4242 ", "4242 4242 4242 4242", "4242 4242 4242 4242424", ] for testCase in testCases { let reformatted = DonationPaymentDetailsViewController.formatCardNumber( unformatted: testCase.asciiDigitsOnly, ) XCTAssertEqual(reformatted, testCase) } } func testFormatExpirationDate() { // We test this by "round-tripping". let testCases: [String] = [ "", "0", "1", "2/", "9/", "01/", "02/", "10/", "11/", "12/", "1/3", "1/9", "2/3", "2/00", "2/34", "01/00", "01/23", "02/00", "02/34", "10/98", "12/34", // These cases aren't that important because they're invalid. // If these tests fail, that's probably fine. // We mostly want to ensure they don't crash. "1/345", "0/0", "0/000", ] for testCase in testCases { let reformatted = DonationPaymentDetailsViewController.formatExpirationDate( unformatted: testCase.asciiDigitsOnly, ) XCTAssertEqual(reformatted, testCase) } } // MARK: - Form state tests // Field validity is largely tested elsewhere // (for example, the specifics of CVV validation). private static func nextYearTwoDigits() -> String { let calendar = Calendar(identifier: .iso8601) let currentYear = calendar.component(.year, from: Date()) let nextYear = currentYear + 1 return String(String(nextYear).suffix(2)) } private static func validRawExpiration() -> String { " 09 / \(nextYearTwoDigits()) " } private func assertFullyValid( _ formState: DonationPaymentDetailsViewController.FormState, _ message: String = "Form state not fully valid", ) { switch formState { case .fullyValid: break case .invalid, .potentiallyValid: XCTFail(message) } } private func fs( cardNumber: String = "4242424242424242", isCardNumberFieldFocused: Bool = false, expirationDate: String = DonationPaymentDetailsViewControllerTest.validRawExpiration(), cvv: String = "123", ) -> DonationPaymentDetailsViewController.FormState { DonationPaymentDetailsViewController.formState( cardNumber: cardNumber, isCardNumberFieldFocused: isCardNumberFieldFocused, expirationDate: expirationDate, cvv: cvv, ) } func testFormStateFullForm() { XCTAssertEqual(fs(cardNumber: "x", cvv: "x"), .invalid(invalidFields: [.cardNumber, .cvv])) XCTAssertEqual(fs(cvv: ""), .potentiallyValid) XCTAssertEqual(fs(), .fullyValid(.card(.init( cardNumber: "4242424242424242", expirationMonth: 9, expirationTwoDigitYear: UInt8(Self.nextYearTwoDigits())!, cvv: "123", )))) } func testFormStateCardNumber() { XCTAssertEqual(fs(cardNumber: "x"), .invalid(invalidFields: [.cardNumber])) XCTAssertEqual(fs(cardNumber: ""), .potentiallyValid) XCTAssertEqual(fs(cardNumber: "123"), .potentiallyValid) assertFullyValid(fs(cardNumber: " 4111 1111 1111 1111 ")) } func testFormStateExpirationDate() { let year = Self.nextYearTwoDigits() let invalids: [String] = [ "x", "13\(year)", "09\(year)0", "009\(year)", "13 / \(year)", "09 / \(year)0", "009 / \(year)", "09 / \(year)x", "09 / \(year) /", "00 / \(year)", ] for expirationDate in invalids { XCTAssertEqual( fs(expirationDate: expirationDate), .invalid(invalidFields: [.expirationDate]), expirationDate, ) } let potentiallyValids: [String] = [ "", "0", "1", "09", "13", "09 /", "1 / 3", "0 / \(year)", ] for expirationDate in potentiallyValids { XCTAssertEqual( fs(expirationDate: expirationDate), .potentiallyValid, expirationDate, ) } let fullyValids: [String] = [ "1\(year)", "9\(year)", "09\(year)", " 12 \(year) ", "1 / \(year)", "9 / \(year)", "09 / \(year)", " 12 / \(year) ", ] for expirationDate in fullyValids { assertFullyValid(fs(expirationDate: expirationDate), expirationDate) } } func testFormStateCvv() { XCTAssertEqual(fs(cvv: "x"), .invalid(invalidFields: [.cvv])) XCTAssertEqual(fs(cvv: ""), .potentiallyValid) XCTAssertEqual(fs(cvv: "12"), .potentiallyValid) assertFullyValid(fs(cvv: " 123 ")) let amexCard = "378282246310005" XCTAssertEqual(fs(cardNumber: amexCard, cvv: "123"), .potentiallyValid) assertFullyValid(fs(cardNumber: amexCard, cvv: " 1234 ")) } }