Add analytics to IdentityVerificationSheet (#155)

This change adds analytics to the IDV sheet. Specifically, we're logging the following events:

- `stripeios.idprod.verification_sheet.presented` - the sheet is presented to the end-user
- `stripeios.idprod.verification_sheet.closed` - the sheet is closed by the end-user. Also contains a `session_result` to indicate whether the user successfully completed the IDV flow or closed the sheet to cancel out of the flow.
- `stripeios.idprod.verification_sheet.failed` - the sheet failed to open. Also contains an `error_dictionary` containing the error code, domain, and userInfo dictionary for the error that caused the sheet to fail to open.
This commit is contained in:
Mel 2021-03-19 19:41:17 -07:00
parent 16c2d2c28b
commit 336be4a0e3
9 changed files with 274 additions and 13 deletions

View File

@ -674,6 +674,8 @@
E60437F225D34316006E2E03 /* STPGenericInputPickerFieldSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E60437F125D34316006E2E03 /* STPGenericInputPickerFieldSnapshotTests.swift */; };
E60437F625D37DDC006E2E03 /* STPGenericInputPickerFieldValidatorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E60437F525D37DDC006E2E03 /* STPGenericInputPickerFieldValidatorTest.swift */; };
E62F3A7425F80F9A00B2C0AC /* UIActivityIndicatorView+Stripe.swift in Sources */ = {isa = PBXBuildFile; fileRef = E62F3A7325F80F9A00B2C0AC /* UIActivityIndicatorView+Stripe.swift */; };
E633417D25FC2376006F084B /* STPAnalyticEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = E633417C25FC2376006F084B /* STPAnalyticEvent.swift */; };
E633418425FC2FEA006F084B /* VerificationSheetAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = E633418325FC2FEA006F084B /* VerificationSheetAnalytics.swift */; };
E66D20A925F02D9B00ED6CA0 /* IdentityVerificationSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = E66D20A825F02D9B00ED6CA0 /* IdentityVerificationSheet.swift */; };
E66D20AD25F0303500ED6CA0 /* VerificationFlowWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E66D20AC25F0303500ED6CA0 /* VerificationFlowWebViewController.swift */; };
E66D20B025F031FB00ED6CA0 /* VerificationFlowWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E66D20AF25F031FB00ED6CA0 /* VerificationFlowWebView.swift */; };
@ -695,10 +697,12 @@
E6A598EB25F70729003B4987 /* VerificationFlowWebViewTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A598E725F706A7003B4987 /* VerificationFlowWebViewTest.swift */; };
E6A598F025F715B7003B4987 /* VerificationFlowWebViewControllerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A598ED25F71599003B4987 /* VerificationFlowWebViewControllerTest.swift */; };
E6A598F725F72272003B4987 /* VerificationClientSecretTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A598F325F721D3003B4987 /* VerificationClientSecretTest.swift */; };
E6A598FC25F72B96003B4987 /* IdentityVerificationSheetTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A598F925F72B7E003B4987 /* IdentityVerificationSheetTest.swift */; };
E6B0F5592686C0BA00055EAF /* StripeCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E6B0F5582686C0BA00055EAF /* StripeCore.framework */; };
E6B8971F25FC4FD6002428E1 /* MockAnalyticsClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6B8971E25FC4FD6002428E1 /* MockAnalyticsClient.swift */; };
E6A598FC25F72B96003B4987 /* IdentityVerificationSheetTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A598F925F72B7E003B4987 /* IdentityVerificationSheetTest.swift */; };
E6B8972525FC532B002428E1 /* Analytic.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6B8972125FC5311002428E1 /* Analytic.swift */; };
E6CA4B3225F1C98300D8D9E8 /* mock.html in Resources */ = {isa = PBXBuildFile; fileRef = E6CA4B3125F1C98300D8D9E8 /* mock.html */; };
E6E7A27C25FC65110002F914 /* VerificationSheetAnalyticsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6E7A27B25FC65110002F914 /* VerificationSheetAnalyticsTest.swift */; };
E6B8972525FC532B002428E1 /* Analytic.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6B8972125FC5311002428E1 /* Analytic.swift */; };
F1122A7E1DFB84E000A8B1AF /* UINavigationBar+StripeTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F1122A7D1DFB84E000A8B1AF /* UINavigationBar+StripeTest.m */; };
F116E94C1D83405E0026A52A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 11C74B9B164043050071C2CA /* Foundation.framework */; };
@ -1460,6 +1464,8 @@
E60437F125D34316006E2E03 /* STPGenericInputPickerFieldSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPGenericInputPickerFieldSnapshotTests.swift; sourceTree = "<group>"; };
E60437F525D37DDC006E2E03 /* STPGenericInputPickerFieldValidatorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPGenericInputPickerFieldValidatorTest.swift; sourceTree = "<group>"; };
E62F3A7325F80F9A00B2C0AC /* UIActivityIndicatorView+Stripe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIActivityIndicatorView+Stripe.swift"; sourceTree = "<group>"; };
E633417C25FC2376006F084B /* STPAnalyticEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPAnalyticEvent.swift; sourceTree = "<group>"; };
E633418325FC2FEA006F084B /* VerificationSheetAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationSheetAnalytics.swift; sourceTree = "<group>"; };
E66D20A825F02D9B00ED6CA0 /* IdentityVerificationSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityVerificationSheet.swift; sourceTree = "<group>"; };
E66D20AC25F0303500ED6CA0 /* VerificationFlowWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationFlowWebViewController.swift; sourceTree = "<group>"; };
E66D20AF25F031FB00ED6CA0 /* VerificationFlowWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationFlowWebView.swift; sourceTree = "<group>"; };
@ -1487,6 +1493,7 @@
E6B8971E25FC4FD6002428E1 /* MockAnalyticsClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAnalyticsClient.swift; sourceTree = "<group>"; };
E6B8972125FC5311002428E1 /* Analytic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Analytic.swift; sourceTree = "<group>"; };
E6CA4B3125F1C98300D8D9E8 /* mock.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = mock.html; path = MockFiles/mock.html; sourceTree = "<group>"; };
E6E7A27B25FC65110002F914 /* VerificationSheetAnalyticsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationSheetAnalyticsTest.swift; sourceTree = "<group>"; };
ED627D57207EA348007EFC56 /* nb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = nb; path = Localizations/nb.lproj/Localizable.strings; sourceTree = "<group>"; };
F1122A7D1DFB84E000A8B1AF /* UINavigationBar+StripeTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UINavigationBar+StripeTest.m"; sourceTree = "<group>"; };
F148ABE71D5E805A0014FD92 /* en */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = en; path = Localizations/en.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -2848,6 +2855,7 @@
E66D20BC25F05C7C00ED6CA0 /* IdentityVerificationSheetError.swift */,
E66D20B725F057FD00ED6CA0 /* VerificationClientSecret.swift */,
E66D20C125F0643200ED6CA0 /* VerifyWebURLHelper.swift */,
E633418325FC2FEA006F084B /* VerificationSheetAnalytics.swift */,
);
name = Identity;
sourceTree = "<group>";
@ -2892,6 +2900,7 @@
E6A598ED25F71599003B4987 /* VerificationFlowWebViewControllerTest.swift */,
E6A598F325F721D3003B4987 /* VerificationClientSecretTest.swift */,
E6A598F925F72B7E003B4987 /* IdentityVerificationSheetTest.swift */,
E6E7A27B25FC65110002F914 /* VerificationSheetAnalyticsTest.swift */,
);
name = Identity;
sourceTree = "<group>";
@ -3307,6 +3316,7 @@
3111C60F252D270D00207E32 /* STPPaymentMethodSofortTests.swift in Sources */,
8BD87B951EFB1CB100269C2B /* STPSourceVerificationTest.m in Sources */,
36E283F8254A35210028C186 /* STPCardCVCInputTextFieldValidatorTests.swift in Sources */,
E6E7A27C25FC65110002F914 /* VerificationSheetAnalyticsTest.swift in Sources */,
36D7A91E253111E7009F2978 /* STPFloatingPlaceholderTextFieldSnapshotTests.swift in Sources */,
B66FA0BE267EE204008D7F1D /* FormElementTest.swift in Sources */,
B646C25526827F1200F4EAE5 /* TextFieldElement+IBANTest.swift in Sources */,
@ -3586,6 +3596,7 @@
B6E40EC62542541100A5BABD /* Events.swift in Sources */,
B6D9CEDB251AA99100AAD424 /* STPPinManagementService.swift in Sources */,
3111BE9F251316E600288D28 /* STPAddCardViewController.swift in Sources */,
E633418425FC2FEA006F084B /* VerificationSheetAnalytics.swift in Sources */,
3194907425140C9700AD8F0B /* STPPaymentOptionsViewController.swift in Sources */,
B6E40EBC2542541100A5BABD /* PaymentSheet.swift in Sources */,
366ECD38254B4AFA0082868E /* STPCardExpiryInputTextFieldFormatter.swift in Sources */,

View File

@ -35,8 +35,18 @@ final public class IdentityVerificationSheet {
- Parameters:
- verificationSessionClientSecret: The client secret of the Stripe VerificationSession object.
*/
public init(verificationSessionClientSecret: String) {
public convenience init(verificationSessionClientSecret: String) {
self.init(verificationSessionClientSecret: verificationSessionClientSecret,
analyticsClient: STPAnalyticsClient.sharedClient)
}
init(verificationSessionClientSecret: String,
analyticsClient: STPAnalyticsClientProtocol) {
self.verificationSessionClientSecret = verificationSessionClientSecret
self.clientSecret = VerificationClientSecret(string: verificationSessionClientSecret)
self.analyticsClient = analyticsClient
analyticsClient.addClass(toProductUsageIfNecessary: IdentityVerificationSheet.self)
}
/**
@ -68,7 +78,10 @@ final public class IdentityVerificationSheet {
) {
// Overwrite completion closure to retain self until called
let completion: (VerificationFlowResult) -> Void = { result in
// TODO(mludowise|IDPROD-1438): Add analytics to log completion or error
self.analyticsClient.log(analytic: VerificationSheetCompletionAnalytic.make(
verificationSessionId: self.clientSecret?.verificationSessionId,
sessionResult: result
))
completion(result)
self.completion = nil
}
@ -85,7 +98,7 @@ final public class IdentityVerificationSheet {
}
// Validate client secret
guard let clientSecret = VerificationClientSecret(string: verificationSessionClientSecret) else {
guard let clientSecret = clientSecret else {
completion(.flowFailed(error: IdentityVerificationSheetError.invalidClientSecret))
return
}
@ -94,13 +107,20 @@ final public class IdentityVerificationSheet {
clientSecret: clientSecret,
delegate: self
)
// TODO(mludowise|IDPROD-1438): Add analytics for starting flow
analyticsClient.log(analytic: VerificationSheetPresentedAnalytic(verificationSessionId: clientSecret.verificationSessionId))
presentingViewController.present(navigationController, animated: true)
}
// MARK: - Private
/// Analytics client to use for logging analytics
private let analyticsClient: STPAnalyticsClientProtocol
/// Completion block called when the sheet is closed or fails to open
private var completion: ((VerificationFlowResult) -> Void)?
/// Parsed client secret string
private let clientSecret: VerificationClientSecret?
}
// MARK: - VerificationFlowWebViewControllerDelegate
@ -112,3 +132,9 @@ extension IdentityVerificationSheet: VerificationFlowWebViewControllerDelegate {
completion?(result)
}
}
// MARK: - STPAnalyticsProtocol
extension IdentityVerificationSheet: STPAnalyticsProtocol {
static var stp_analyticsIdentifier = "IdentityVerificationSheet"
}

View File

@ -16,9 +16,44 @@ public enum IdentityVerificationSheetError: Error {
case invalidClientSecret
/// An unknown error.
case unknown(debugDescription: String)
}
extension IdentityVerificationSheetError: LocalizedError {
/// Localized description of the error
public var localizedDescription: String {
return NSError.stp_unexpectedErrorMessage()
}
}
extension IdentityVerificationSheetError: CustomDebugStringConvertible {
public var debugDescription: String {
switch self {
case .invalidClientSecret:
return "Invalid client secret"
case .unknown(debugDescription: let debugDescription):
return debugDescription
}
}
}
// TODO(mludowise|MOBILESDK-193): Added `CustomNSError` conformance so our
// analytics will be able to log useful information until we find a better solution.
extension IdentityVerificationSheetError: CustomNSError {
public static let errorDomain = "Stripe.\(IdentityVerificationSheetError.self)"
public var errorCode: Int {
switch self {
case .invalidClientSecret:
return 0
case .unknown:
return 1
}
}
public var errorUserInfo: [String : Any] {
return [
NSDebugDescriptionErrorKey: debugDescription,
NSLocalizedDescriptionKey: localizedDescription
]
}
}

View File

@ -53,8 +53,8 @@ final class VerificationFlowWebViewController: UIViewController {
- clientSecret: The VerificationSession client secret.
- delegate: Optional delegate for the `VerificationFlowWebViewController`
*/
private init(clientSecret: VerificationClientSecret,
delegate: VerificationFlowWebViewControllerDelegate?) {
init(clientSecret: VerificationClientSecret,
delegate: VerificationFlowWebViewControllerDelegate?) {
self.verificationWebView = VerificationFlowWebView(initialURL: VerifyWebURLHelper.startURL(fromToken: clientSecret.urlToken))
super.init(nibName: nil, bundle: nil)
self.delegate = delegate

View File

@ -0,0 +1,85 @@
//
// VerificationSheetAnalytics.swift
// StripeiOS
//
// Created by Mel Ludowise on 3/12/21.
// Copyright © 2021 Stripe, Inc. All rights reserved.
//
import Foundation
/// Analytic that contains a `verification_session` payload param
protocol VerificationSheetAnalytic: Analytic {
var verificationSessionId: String? { get }
var additionalParams: [String: Any] { get }
}
extension VerificationSheetAnalytic {
var params: [String : Any] {
var params = additionalParams
params["verification_session"] = verificationSessionId
return params
}
}
/// Logged when the sheet is presented
struct VerificationSheetPresentedAnalytic: VerificationSheetAnalytic {
let event = STPAnalyticEvent.verificationSheetPresented
let verificationSessionId: String?
let additionalParams: [String : Any] = [:]
}
/// Logged when the sheet is closed by the end-user
struct VerificationSheetClosedAnalytic: VerificationSheetAnalytic {
let event = STPAnalyticEvent.verificationSheetClosed
let verificationSessionId: String?
let sessionResult: String
var additionalParams: [String : Any] {
return [
"session_result": sessionResult,
]
}
}
/// Logged if there's an error presenting the sheet
struct VerificationSheetFailedAnalytic: VerificationSheetAnalytic {
let event = STPAnalyticEvent.verificationSheetFailed
let verificationSessionId: String?
let error: Error
var additionalParams: [String : Any] {
return [
"error_dictionary": STPAnalyticsClient.serializeError(error as NSError)
]
}
}
/// Helper to determine if we should log a failed analytic or closed analytic from the sheet's completion block
struct VerificationSheetCompletionAnalytic {
/// Returns either a `VerificationSheetClosedAnalytic` or `VerificationSheetFailedAnalytic` depending on the result
static func make(
verificationSessionId: String?,
sessionResult result: IdentityVerificationSheet.VerificationFlowResult
) -> VerificationSheetAnalytic {
switch result {
case .flowCompleted:
assert(verificationSessionId != nil, "Verification Session ID is nil with completed result.")
return VerificationSheetClosedAnalytic(
verificationSessionId: verificationSessionId,
sessionResult: "flow_completed"
)
case .flowCanceled:
assert(verificationSessionId != nil, "Verification Session ID is nil with canceled result.")
return VerificationSheetClosedAnalytic(
verificationSessionId: verificationSessionId,
sessionResult: "flow_canceled"
)
case .flowFailed(let error):
return VerificationSheetFailedAnalytic(
verificationSessionId: verificationSessionId,
error: error
)
}
}
}

View File

@ -35,4 +35,9 @@ import Foundation
// MARK: - Card Scanning
case cardScanSucceeded = "stripeios.cardscan_success"
case cardScanCancelled = "stripeios.cardscan_cancel"
// MARK: - Identity Verification Flow
case verificationSheetPresented = "stripeios.idprod.verification_sheet.presented"
case verificationSheetClosed = "stripeios.idprod.verification_sheet.closed"
case verificationSheetFailed = "stripeios.idprod.verification_sheet.failed"
}

View File

@ -13,17 +13,20 @@ import XCTest
final class IdentityVerificationSheetTest: XCTestCase {
private let mockViewController = UIViewController()
private let mockSecret = "vi_123_secret_456"
private let mockAnalyticsClient = MockAnalyticsClient()
private var sheet: IdentityVerificationSheet!
override func setUp() {
super.setUp()
sheet = IdentityVerificationSheet(verificationSessionClientSecret: mockSecret)
mockAnalyticsClient.reset()
sheet = IdentityVerificationSheet(verificationSessionClientSecret: mockSecret, analyticsClient: mockAnalyticsClient)
}
func testInvalidSecret() {
var result: IdentityVerificationSheet.VerificationFlowResult?
sheet = IdentityVerificationSheet(verificationSessionClientSecret: "bad secret")
sheet = IdentityVerificationSheet(verificationSessionClientSecret: "bad secret", analyticsClient: mockAnalyticsClient)
// TODO(mludowise|RUN_MOBILESDK-120): Using `presentInternal` instead of
// `present` so we can run tests on our CI until it's updated to iOS 14.
sheet.presentInternal(from: mockViewController) { (r) in
@ -36,9 +39,45 @@ final class IdentityVerificationSheetTest: XCTestCase {
case .invalidClientSecret = sheetError else {
return XCTFail("Expected `IdentityVerificationSheetError.invalidClientSecret`")
}
// Verify failed analytic is logged
XCTAssertEqual(mockAnalyticsClient.loggedAnalytics.count, 1)
guard let failedAnalytic = mockAnalyticsClient.loggedAnalytics.first as? VerificationSheetFailedAnalytic else {
return XCTFail("Expected `VerificationSheetFailedAnalytic`")
}
XCTAssertNil(failedAnalytic.verificationSessionId)
guard let analyticError = failedAnalytic.error as? IdentityVerificationSheetError,
case .invalidClientSecret = analyticError else {
return XCTFail("Expected `IdentityVerificationSheetError.invalidClientSecret`")
}
}
func testAnalytics() {
// TODO(mludowise|IDPROD-1438)
// TODO(mludowise|RUN_MOBILESDK-120): Using `presentInternal` instead of
// `present` so we can run tests on our CI until it's updated to iOS 14.
sheet.presentInternal(from: mockViewController) { _ in }
// Verify presented analytic is logged
XCTAssertEqual(mockAnalyticsClient.loggedAnalytics.count, 1)
guard let presentedAnalytic = mockAnalyticsClient.loggedAnalytics.first as? VerificationSheetPresentedAnalytic else {
return XCTFail("Expected `VerificationSheetPresentedAnalytic`")
}
XCTAssertEqual(presentedAnalytic.verificationSessionId, "vi_123")
// Mock that flow is completed
let mockVC = VerificationFlowWebViewController(clientSecret: VerificationClientSecret(string: mockSecret)!, delegate: nil)
sheet.verificationFlowWebViewController(mockVC, didFinish: .flowCanceled)
// Verify closed analytic is logged
XCTAssertEqual(mockAnalyticsClient.loggedAnalytics.count, 2)
guard let closedAnalytic = mockAnalyticsClient.loggedAnalytics.last as? VerificationSheetClosedAnalytic else {
return XCTFail("Expected `VerificationSheetClosedAnalytic`")
}
XCTAssertEqual(closedAnalytic.verificationSessionId, "vi_123")
XCTAssertEqual(closedAnalytic.sessionResult, "flow_canceled")
}
func testAnalyticsProductUsage() {
XCTAssertEqual(mockAnalyticsClient.productUsage, ["IdentityVerificationSheet"])
}
}

View File

@ -0,0 +1,59 @@
//
// VerificationSheetAnalyticsTest.swift
// StripeiOS Tests
//
// Created by Mel Ludowise on 3/12/21.
// Copyright © 2021 Stripe, Inc. All rights reserved.
//
import XCTest
@testable import Stripe
final class VerificationSheetAnalyticsTest: XCTestCase {
func testVerificationSheetFailedAnalyticEncoding() {
let analytic = VerificationSheetFailedAnalytic(verificationSessionId: nil, error: IdentityVerificationSheetError.unknown(debugDescription: "some description"))
XCTAssertEqual(analytic.params.count, 1)
guard let errorDict = analytic.params["error_dictionary"] as? [String: Any] else {
return XCTFail("Expected `error_dictionary`")
}
XCTAssertEqual(errorDict["user_info"] as? [String: String], [
NSDebugDescriptionErrorKey: "some description",
NSLocalizedDescriptionKey: NSError.stp_unexpectedErrorMessage()
])
XCTAssertEqual(errorDict["code"] as? Int, 1)
XCTAssertEqual(errorDict["domain"] as? String, "Stripe.IdentityVerificationSheetError")
}
func testVerificationSheetCompletionAnalyticCompleted() {
let analytic = VerificationSheetCompletionAnalytic.make(verificationSessionId: "session_id", sessionResult: .flowCompleted)
guard let closedAnalytic = analytic as? VerificationSheetClosedAnalytic else {
return XCTFail("Expected `VerificationSheetClosedAnalytic`")
}
XCTAssertEqual(closedAnalytic.verificationSessionId, "session_id")
XCTAssertEqual(closedAnalytic.sessionResult, "flow_completed")
}
func testVerificationSheetCompletionAnalyticCanceled() {
let analytic = VerificationSheetCompletionAnalytic.make(verificationSessionId: "session_id", sessionResult: .flowCanceled)
guard let closedAnalytic = analytic as? VerificationSheetClosedAnalytic else {
return XCTFail("Expected `VerificationSheetClosedAnalytic`")
}
XCTAssertEqual(closedAnalytic.verificationSessionId, "session_id")
XCTAssertEqual(closedAnalytic.sessionResult, "flow_canceled")
}
func testVerificationSheetCompletionAnalyticFailed() {
let analytic = VerificationSheetCompletionAnalytic.make(verificationSessionId: "session_id", sessionResult: .flowFailed(error: IdentityVerificationSheetError.unknown(debugDescription: "some description")))
guard let failedAnalytic = analytic as? VerificationSheetFailedAnalytic else {
return XCTFail("Expected `VerificationSheetFailedAnalytic`")
}
XCTAssertEqual(failedAnalytic.verificationSessionId, "session_id")
XCTAssert(failedAnalytic.error is IdentityVerificationSheetError)
}
}

View File

@ -82,6 +82,7 @@ if skip_snapshot_tests
"StripeiOS Tests/STPGenericInputPickerFieldSnapshotTests",
"StripeiOS Tests/STPiDEALBankPickerInputFieldSnapshotTests",
"StripeiOS Tests/STPiDEALFormViewSnapshotTests",
"StripeiOS Tests/VerificationFlowWebViewSnapshotTests"
"StripeiOS Tests/AfterpayPriceBreakdownViewSnapshotTests"
]
end
@ -89,7 +90,7 @@ end
destination_string = 'generic/platform=iOS Simulator'
build_action = 'clean test'
if build_only
if build_only
# We'll want to clean outside this script.
# If we clean here, we may unintentionally throw out the cache we built for other targets!
build_action = 'build-for-testing'
@ -103,7 +104,7 @@ else
destination_string = 'platform=iOS Simulator'
destination_string += ',name=' + device
destination_string += ',OS=' + version
end
end
end
skip_tests_command = ""
@ -148,4 +149,4 @@ Dir.chdir(__dir__ + '/..') do
# If the build succeeded, create a placeholder cache key for the target.
FileUtils.touch(__dir__ + '/../build-ci-tests/' + build_scheme + '.finished')
end
end
end