From 17796b52a10af361e553d1d9768e74af9d753294 Mon Sep 17 00:00:00 2001 From: Pragati Date: Tue, 27 Aug 2024 09:17:09 -0700 Subject: [PATCH 1/6] parent bfaaf1d8c699f43052def03d529e96834b0bc901 author Pragati 1724775429 -0700 committer Pragati 1725423960 -0700 Unit Tests drafted tests adding mock rce enforce test remove wip test add error cases lint changes line fix CI failures modifying objc methods to use swift implementations remove ios only enablement enum address PR feedback adding mock rce enforce test lint changes modifying objc methods to use swift implementations remove ios only enablement enum add/remove rebasing changes --- .../AuthProvider/PhoneAuthProvider.swift | 14 +-- .../RPC/SendVerificationTokenRequest.swift | 2 +- .../Swift/Utilities/AuthErrorUtils.swift | 8 ++ .../Utilities/AuthRecaptchaVerifier.swift | 10 +- .../Tests/Unit/PhoneAuthProviderTests.swift | 98 ++++++++++++++++++- 5 files changed, 119 insertions(+), 13 deletions(-) diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift b/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift index 050f267fd8a..a6f0aa9fa7e 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift @@ -228,10 +228,10 @@ import Foundation } } - private func verifyClAndSendVerificationCodeWithRecaptcha(toPhoneNumber phoneNumber: String, - retryOnInvalidAppCredential: Bool, - uiDelegate: AuthUIDelegate?, - recaptchaVerifier: AuthRecaptchaVerifier) async throws + func verifyClAndSendVerificationCodeWithRecaptcha(toPhoneNumber phoneNumber: String, + retryOnInvalidAppCredential: Bool, + uiDelegate: AuthUIDelegate?, + recaptchaVerifier: AuthRecaptchaVerifier) async throws -> String? { let request = SendVerificationCodeRequest(phoneNumber: phoneNumber, codeIdentity: CodeIdentity.empty, @@ -260,9 +260,9 @@ import Foundation /// - Parameter phoneNumber: The phone number to be verified. /// - Parameter callback: The callback to be invoked on the global work queue when the flow is /// finished. - private func verifyClAndSendVerificationCode(toPhoneNumber phoneNumber: String, - retryOnInvalidAppCredential: Bool, - uiDelegate: AuthUIDelegate?) async throws + func verifyClAndSendVerificationCode(toPhoneNumber phoneNumber: String, + retryOnInvalidAppCredential: Bool, + uiDelegate: AuthUIDelegate?) async throws -> String? { let codeIdentity = try await verifyClient(withUIDelegate: uiDelegate) let request = SendVerificationCodeRequest(phoneNumber: phoneNumber, diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SendVerificationTokenRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SendVerificationTokenRequest.swift index c599c6955c1..2a968aa5029 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SendVerificationTokenRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SendVerificationTokenRequest.swift @@ -42,7 +42,7 @@ private let kRecaptchaVersion = "recaptchaVersion" private let kTenantIDKey = "tenantId" /// A verification code can be an appCredential or a reCaptcha Token -enum CodeIdentity { +enum CodeIdentity: Equatable { case credential(AuthAppCredential) case recaptcha(String) case empty diff --git a/FirebaseAuth/Sources/Swift/Utilities/AuthErrorUtils.swift b/FirebaseAuth/Sources/Swift/Utilities/AuthErrorUtils.swift index e4997d07c2d..3b5e599230c 100644 --- a/FirebaseAuth/Sources/Swift/Utilities/AuthErrorUtils.swift +++ b/FirebaseAuth/Sources/Swift/Utilities/AuthErrorUtils.swift @@ -203,6 +203,10 @@ class AuthErrorUtils: NSObject { error(code: .missingAndroidPackageName, message: message) } + static func invalidRecaptchaTokenError(message: String?) -> Error { + error(code: .invalidRecaptchaToken, message: message) + } + static func unauthorizedDomainError(message: String?) -> Error { error(code: .unauthorizedDomain, message: message) } @@ -239,6 +243,10 @@ class AuthErrorUtils: NSObject { error(code: .missingVerificationID, message: message) } + static func missingRecaptchaTokenError(message: String?) -> Error { + error(code: .missingRecaptchaToken, message: message) + } + static func invalidVerificationIDError(message: String?) -> Error { error(code: .invalidVerificationID, message: message) } diff --git a/FirebaseAuth/Sources/Swift/Utilities/AuthRecaptchaVerifier.swift b/FirebaseAuth/Sources/Swift/Utilities/AuthRecaptchaVerifier.swift index c9c5775102d..6047a87cf4e 100644 --- a/FirebaseAuth/Sources/Swift/Utilities/AuthRecaptchaVerifier.swift +++ b/FirebaseAuth/Sources/Swift/Utilities/AuthRecaptchaVerifier.swift @@ -72,10 +72,9 @@ private(set) var agentConfig: AuthRecaptchaConfig? private(set) var tenantConfigs: [String: AuthRecaptchaConfig] = [:] private(set) var recaptchaClient: RCARecaptchaClientProtocol? - - private static let _shared = AuthRecaptchaVerifier() + private static var _shared = AuthRecaptchaVerifier() private let kRecaptchaVersion = "RECAPTCHA_ENTERPRISE" - private init() {} + init() {} class func shared(auth: Auth?) -> AuthRecaptchaVerifier { if _shared.auth != auth { @@ -86,6 +85,11 @@ return _shared } + class func setShared(_ instance: AuthRecaptchaVerifier, auth: Auth?) { + _shared = instance + _ = shared(auth: auth) + } + func siteKey() -> String? { if let tenantID = auth?.tenantID { if let config = tenantConfigs[tenantID] { diff --git a/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift b/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift index 4475e736cf5..b432779545e 100644 --- a/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift +++ b/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift @@ -98,6 +98,95 @@ try await internalTestVerify(function: #function) } + /** + @fn testVerifyPhoneNumberWithRceEnforce + @brief Tests a successful invocation of @c verifyPhoneNumber:completion: with recaptcha enterprise enforced + */ + func testVerifyPhoneNumberWithRceEnforceSuccess() async throws { + initApp(#function) + let auth = try XCTUnwrap(PhoneAuthProviderTests.auth) + // TODO: Figure out how to mock objective C's FIRRecaptchaGetToken response + let provider = PhoneAuthProvider.provider(auth: auth) + let mockVerifier = FakeAuthRecaptchaVerifier(captchaResponse: kCaptchaResponse) + AuthRecaptchaVerifier.setShared(mockVerifier, auth: auth) + rpcIssuer.rceMode = "ENFORCE" + rpcIssuer?.verifyRequester = { request in + XCTAssertEqual(request.phoneNumber, self.kTestPhoneNumber) + XCTAssertEqual(request.captchaResponse, self.kCaptchaResponse) + XCTAssertEqual(request.recaptchaVersion, "RECAPTCHA_ENTERPRISE") + XCTAssertEqual(request.codeIdentity, CodeIdentity.empty) + do { + try self.rpcIssuer? + .respond(withJSON: [self.kVerificationIDKey: self.kTestVerificationID]) + } catch { + XCTFail("Failure sending response: \(error)") + } + } + do { + let result = try await provider.verifyClAndSendVerificationCodeWithRecaptcha( + toPhoneNumber: kTestPhoneNumber, + retryOnInvalidAppCredential: false, + uiDelegate: nil, + recaptchaVerifier: mockVerifier + ) + XCTAssertEqual(result, kTestVerificationID) + } catch { + XCTFail("Unexpected error") + } + } + + /** + @fn testVerifyPhoneNumberWithRceEnforceInvalidRecaptcha + @brief Tests a successful invocation of @c verifyPhoneNumber:completion: with recaptcha enterprise enforced + */ + func testVerifyPhoneNumberWithRceEnforceInvalidRecaptcha() async throws { + initApp(#function) + let auth = try XCTUnwrap(PhoneAuthProviderTests.auth) + // TODO: Figure out how to mock objective C's FIRRecaptchaGetToken response + let provider = PhoneAuthProvider.provider(auth: auth) + let invalidRecaptchaTokenError = AuthErrorUtils + .invalidRecaptchaTokenError(message: "INVALID_RECAPTCHA_TOKEN") + let mockVerifier = FakeAuthRecaptchaVerifier(error: invalidRecaptchaTokenError) + AuthRecaptchaVerifier.setShared(mockVerifier, auth: auth) + rpcIssuer.rceMode = "ENFORCE" + do { + let _ = try await provider.verifyClAndSendVerificationCodeWithRecaptcha( + toPhoneNumber: kTestPhoneNumber, + retryOnInvalidAppCredential: false, + uiDelegate: nil, + recaptchaVerifier: mockVerifier + ) + } catch { + XCTAssertEqual((error as NSError).code, AuthErrorCode.invalidRecaptchaToken.rawValue) + } + } + + /** + @fn testVerifyPhoneNumberWithRceEnforceMissingRecaptcha + @brief Tests an invocation of @c verifyPhoneNumber:completion: with recaptcha enterprise enforced returning missing recaptcha token error + */ + func testVerifyPhoneNumberWithRceEnforceMissingRecaptcha() async throws { + initApp(#function) + let auth = try XCTUnwrap(PhoneAuthProviderTests.auth) + // TODO: Figure out how to mock objective C's FIRRecaptchaGetToken response + let provider = PhoneAuthProvider.provider(auth: auth) + let missingRecaptchaTokenError = AuthErrorUtils + .missingRecaptchaTokenError(message: "MISSING_RECAPTCHA_TOKEN") + let mockVerifier = FakeAuthRecaptchaVerifier(error: missingRecaptchaTokenError) + AuthRecaptchaVerifier.setShared(mockVerifier, auth: auth) + rpcIssuer.rceMode = "ENFORCE" + do { + let _ = try await provider.verifyClAndSendVerificationCodeWithRecaptcha( + toPhoneNumber: kTestPhoneNumber, + retryOnInvalidAppCredential: false, + uiDelegate: nil, + recaptchaVerifier: mockVerifier + ) + } catch { + XCTAssertEqual((error as NSError).code, AuthErrorCode.missingRecaptchaToken.rawValue) + } + } + /** @fn testVerifyPhoneNumberInTestMode @brief Tests a successful invocation of @c verifyPhoneNumber:completion: when app verification is disabled. @@ -681,10 +770,15 @@ class FakeAuthRecaptchaVerifier: AuthRecaptchaVerifier { var captchaResponse: String = "captchaResponse" - var fakeError: Error? + var error: Error? + init(captchaResponse: String? = nil, error: Error? = nil) { + super.init() + self.captchaResponse = captchaResponse ?? "NO_RECAPTCHA" + self.error = error + } override func verify(forceRefresh: Bool, action: AuthRecaptchaAction) async throws -> String { - if let error = fakeError { + if let error = error { throw error } return captchaResponse From 215a52581b1daa997b3aabaf082eaca78c580f65 Mon Sep 17 00:00:00 2001 From: Pragati Date: Wed, 4 Sep 2024 14:27:38 -0700 Subject: [PATCH 2/6] adding callback fn expectations --- FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift b/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift index b432779545e..79df07d3a2a 100644 --- a/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift +++ b/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift @@ -640,6 +640,7 @@ forwardingNotification: forwardingNotification) let auth = try XCTUnwrap(PhoneAuthProviderTests.auth) let provider = PhoneAuthProvider.provider(auth: auth) + var expectations: [XCTestExpectation] = [] if !reCAPTCHAfallback { // Fake out appCredentialManager flow. @@ -647,8 +648,11 @@ secret: kTestSecret) } else { // 1. Intercept, handle, and test the projectConfiguration RPC calls. + let projectConfigExpectation = self.expectation(description: "projectConfiguration") + expectations.append(projectConfigExpectation) rpcIssuer?.projectConfigRequester = { request in XCTAssertEqual(request.apiKey, PhoneAuthProviderTests.kFakeAPIKey) + projectConfigExpectation.fulfill() do { // Response for the underlying VerifyClientRequest RPC call. try self.rpcIssuer?.respond( @@ -675,6 +679,8 @@ ) } if errorURLString == nil, presenterError == nil { + let requestExpectation = self.expectation(description: "verifyRequester") + expectations.append(requestExpectation) rpcIssuer?.verifyRequester = { request in XCTAssertEqual(request.phoneNumber, self.kTestPhoneNumber) switch request.codeIdentity { @@ -688,6 +694,7 @@ case .empty: XCTAssertTrue(testMode) } + requestExpectation.fulfill() do { // Response for the underlying SendVerificationCode RPC call. if let errorString { @@ -716,6 +723,7 @@ // expected value XCTAssertEqual((error as NSError).code, errorCode) } + await fulfillment(of: expectations, timeout: 5.0) } private func initApp(_ functionName: String, From ac7c2f77430be06b70e2305bb0c77a2d0a987dd7 Mon Sep 17 00:00:00 2001 From: Pragati Date: Wed, 4 Sep 2024 14:47:35 -0700 Subject: [PATCH 3/6] lint --- .../Tests/Unit/PhoneAuthProviderTests.swift | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift b/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift index 79df07d3a2a..d3915a86aea 100644 --- a/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift +++ b/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift @@ -146,7 +146,10 @@ let provider = PhoneAuthProvider.provider(auth: auth) let invalidRecaptchaTokenError = AuthErrorUtils .invalidRecaptchaTokenError(message: "INVALID_RECAPTCHA_TOKEN") - let mockVerifier = FakeAuthRecaptchaVerifier(error: invalidRecaptchaTokenError) + let mockVerifier = FakeAuthRecaptchaVerifier( + captchaResponse: "invalidToken", + error: invalidRecaptchaTokenError + ) AuthRecaptchaVerifier.setShared(mockVerifier, auth: auth) rpcIssuer.rceMode = "ENFORCE" do { @@ -172,7 +175,10 @@ let provider = PhoneAuthProvider.provider(auth: auth) let missingRecaptchaTokenError = AuthErrorUtils .missingRecaptchaTokenError(message: "MISSING_RECAPTCHA_TOKEN") - let mockVerifier = FakeAuthRecaptchaVerifier(error: missingRecaptchaTokenError) + let mockVerifier = FakeAuthRecaptchaVerifier( + captchaResponse: "", + error: missingRecaptchaTokenError + ) AuthRecaptchaVerifier.setShared(mockVerifier, auth: auth) rpcIssuer.rceMode = "ENFORCE" do { @@ -648,7 +654,7 @@ secret: kTestSecret) } else { // 1. Intercept, handle, and test the projectConfiguration RPC calls. - let projectConfigExpectation = self.expectation(description: "projectConfiguration") + let projectConfigExpectation = expectation(description: "projectConfiguration") expectations.append(projectConfigExpectation) rpcIssuer?.projectConfigRequester = { request in XCTAssertEqual(request.apiKey, PhoneAuthProviderTests.kFakeAPIKey) @@ -679,7 +685,7 @@ ) } if errorURLString == nil, presenterError == nil { - let requestExpectation = self.expectation(description: "verifyRequester") + let requestExpectation = expectation(description: "verifyRequester") expectations.append(requestExpectation) rpcIssuer?.verifyRequester = { request in XCTAssertEqual(request.phoneNumber, self.kTestPhoneNumber) From c2cc153ab2f3011f11d0a2ddfc54e209b45c0818 Mon Sep 17 00:00:00 2001 From: Pragati Date: Thu, 5 Sep 2024 09:29:41 -0700 Subject: [PATCH 4/6] refactor --- .../AuthProvider/PhoneAuthProvider.swift | 2 +- .../Swift/Utilities/AuthErrorUtils.swift | 8 +- .../Tests/Unit/PhoneAuthProviderTests.swift | 100 +++++++++++------- 3 files changed, 64 insertions(+), 46 deletions(-) diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift b/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift index a6f0aa9fa7e..412396aa015 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift @@ -199,7 +199,7 @@ import Foundation } let recaptchaVerifier = AuthRecaptchaVerifier.shared(auth: auth) - try await recaptchaVerifier.retrieveRecaptchaConfig(forceRefresh: true) + try await recaptchaVerifier.retrieveRecaptchaConfig(forceRefresh: false) switch recaptchaVerifier.enablementStatus(forProvider: .phone) { case .off: diff --git a/FirebaseAuth/Sources/Swift/Utilities/AuthErrorUtils.swift b/FirebaseAuth/Sources/Swift/Utilities/AuthErrorUtils.swift index 3b5e599230c..9bf83a59d9f 100644 --- a/FirebaseAuth/Sources/Swift/Utilities/AuthErrorUtils.swift +++ b/FirebaseAuth/Sources/Swift/Utilities/AuthErrorUtils.swift @@ -203,8 +203,8 @@ class AuthErrorUtils: NSObject { error(code: .missingAndroidPackageName, message: message) } - static func invalidRecaptchaTokenError(message: String?) -> Error { - error(code: .invalidRecaptchaToken, message: message) + static func invalidRecaptchaTokenError() -> Error { + error(code: .invalidRecaptchaToken) } static func unauthorizedDomainError(message: String?) -> Error { @@ -243,10 +243,6 @@ class AuthErrorUtils: NSObject { error(code: .missingVerificationID, message: message) } - static func missingRecaptchaTokenError(message: String?) -> Error { - error(code: .missingRecaptchaToken, message: message) - } - static func invalidVerificationIDError(message: String?) -> Error { error(code: .invalidVerificationID, message: message) } diff --git a/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift b/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift index d3915a86aea..3cff373b24d 100644 --- a/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift +++ b/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift @@ -100,7 +100,7 @@ /** @fn testVerifyPhoneNumberWithRceEnforce - @brief Tests a successful invocation of @c verifyPhoneNumber:completion: with recaptcha enterprise enforced + @brief Tests a successful invocation of @c verifyPhoneNumber with recaptcha enterprise enforced */ func testVerifyPhoneNumberWithRceEnforceSuccess() async throws { initApp(#function) @@ -110,6 +110,7 @@ let mockVerifier = FakeAuthRecaptchaVerifier(captchaResponse: kCaptchaResponse) AuthRecaptchaVerifier.setShared(mockVerifier, auth: auth) rpcIssuer.rceMode = "ENFORCE" + let requestExpectation = expectation(description: "verifyRequester") rpcIssuer?.verifyRequester = { request in XCTAssertEqual(request.phoneNumber, self.kTestPhoneNumber) XCTAssertEqual(request.captchaResponse, self.kCaptchaResponse) @@ -122,6 +123,7 @@ XCTFail("Failure sending response: \(error)") } } + requestExpectation.fulfill() do { let result = try await provider.verifyClAndSendVerificationCodeWithRecaptcha( toPhoneNumber: kTestPhoneNumber, @@ -133,64 +135,63 @@ } catch { XCTFail("Unexpected error") } + await fulfillment(of: [requestExpectation], timeout: 5.0) } /** @fn testVerifyPhoneNumberWithRceEnforceInvalidRecaptcha - @brief Tests a successful invocation of @c verifyPhoneNumber:completion: with recaptcha enterprise enforced + @brief Tests a successful invocation of @c verifyPhoneNumber with recaptcha enterprise enforced */ func testVerifyPhoneNumberWithRceEnforceInvalidRecaptcha() async throws { initApp(#function) let auth = try XCTUnwrap(PhoneAuthProviderTests.auth) - // TODO: Figure out how to mock objective C's FIRRecaptchaGetToken response + // TODO: Figure out how to mock objective C's FIRRecaptchaGetToken response let provider = PhoneAuthProvider.provider(auth: auth) - let invalidRecaptchaTokenError = AuthErrorUtils - .invalidRecaptchaTokenError(message: "INVALID_RECAPTCHA_TOKEN") - let mockVerifier = FakeAuthRecaptchaVerifier( - captchaResponse: "invalidToken", - error: invalidRecaptchaTokenError - ) + let mockVerifier = FakeAuthRecaptchaVerifier() AuthRecaptchaVerifier.setShared(mockVerifier, auth: auth) rpcIssuer.rceMode = "ENFORCE" + let requestExpectation = expectation(description: "verifyRequester") + rpcIssuer?.verifyRequester = { request in + XCTAssertEqual(request.phoneNumber, self.kTestPhoneNumber) + XCTAssertEqual(request.captchaResponse, "NO_RECAPTCHA") + XCTAssertEqual(request.recaptchaVersion, "RECAPTCHA_ENTERPRISE") + XCTAssertEqual(request.codeIdentity, CodeIdentity.empty) + do { + try self.rpcIssuer? + .respond(serverErrorMessage: "INVALID_RECAPTCHA_TOKEN") + } catch { + XCTFail("Failure sending response: \(error)") + } + } + requestExpectation.fulfill() do { - let _ = try await provider.verifyClAndSendVerificationCodeWithRecaptcha( + _ = try await provider.verifyClAndSendVerificationCodeWithRecaptcha( toPhoneNumber: kTestPhoneNumber, retryOnInvalidAppCredential: false, uiDelegate: nil, recaptchaVerifier: mockVerifier ) + //XCTAssertEqual(result, kTestVerificationID) } catch { - XCTAssertEqual((error as NSError).code, AuthErrorCode.invalidRecaptchaToken.rawValue) + XCTAssertEqual((error as NSError).code, AuthErrorCode.invalidRecaptchaToken.code.rawValue) } + await fulfillment(of: [requestExpectation], timeout: 5.0) } - + /** - @fn testVerifyPhoneNumberWithRceEnforceMissingRecaptcha - @brief Tests an invocation of @c verifyPhoneNumber:completion: with recaptcha enterprise enforced returning missing recaptcha token error + @fn testVerifyPhoneNumberWithRceEnforceSDKNotLinked + @brief Tests a successful invocation of @c verifyPhoneNumber with recaptcha enterprise enforced */ - func testVerifyPhoneNumberWithRceEnforceMissingRecaptcha() async throws { - initApp(#function) - let auth = try XCTUnwrap(PhoneAuthProviderTests.auth) - // TODO: Figure out how to mock objective C's FIRRecaptchaGetToken response - let provider = PhoneAuthProvider.provider(auth: auth) - let missingRecaptchaTokenError = AuthErrorUtils - .missingRecaptchaTokenError(message: "MISSING_RECAPTCHA_TOKEN") - let mockVerifier = FakeAuthRecaptchaVerifier( - captchaResponse: "", - error: missingRecaptchaTokenError - ) - AuthRecaptchaVerifier.setShared(mockVerifier, auth: auth) - rpcIssuer.rceMode = "ENFORCE" - do { - let _ = try await provider.verifyClAndSendVerificationCodeWithRecaptcha( - toPhoneNumber: kTestPhoneNumber, - retryOnInvalidAppCredential: false, - uiDelegate: nil, - recaptchaVerifier: mockVerifier - ) - } catch { - XCTAssertEqual((error as NSError).code, AuthErrorCode.missingRecaptchaToken.rawValue) - } + func testVerifyPhoneNumberWithRceEnforceRecaptchaSDKNotLinked() async throws { + return try await testRecaptchaFlowError(function: #function, rceError: AuthErrorUtils.recaptchaSDKNotLinkedError()) + } + + /** + @fn testVerifyPhoneNumberWithRceEnforceSDKNotLinked + @brief Tests a successful invocation of @c verifyPhoneNumber with recaptcha enterprise enforced + */ + func testVerifyPhoneNumberWithRceEnforceRecaptchaActionCreationFailed() async throws { + return try await testRecaptchaFlowError(function: #function, rceError: AuthErrorUtils.recaptchaActionCreationFailed()) } /** @fn testVerifyPhoneNumberInTestMode @@ -434,6 +435,27 @@ } XCTAssertEqual(unarchivedCredential.provider, PhoneAuthProvider.id) } + + private func testRecaptchaFlowError(function: String, rceError: Error) async throws{ + initApp(function) + let auth = try XCTUnwrap(PhoneAuthProviderTests.auth) + // TODO: Figure out how to mock objective C's FIRRecaptchaGetToken response + // Mocking the output of verify() method + let provider = PhoneAuthProvider.provider(auth: auth) + let mockVerifier = FakeAuthRecaptchaVerifier(error: rceError) + AuthRecaptchaVerifier.setShared(mockVerifier, auth: auth) + rpcIssuer.rceMode = "ENFORCE" + do { + let _ = try await provider.verifyClAndSendVerificationCodeWithRecaptcha( + toPhoneNumber: kTestPhoneNumber, + retryOnInvalidAppCredential: false, + uiDelegate: nil, + recaptchaVerifier: mockVerifier + ) + } catch { + XCTAssertEqual((error as NSError).code, (rceError as NSError).code) + } + } private func internalFlowRetry(function: String, goodRetry: Bool = false) throws { let function = function @@ -783,12 +805,12 @@ } class FakeAuthRecaptchaVerifier: AuthRecaptchaVerifier { - var captchaResponse: String = "captchaResponse" + var captchaResponse: String var error: Error? init(captchaResponse: String? = nil, error: Error? = nil) { - super.init() self.captchaResponse = captchaResponse ?? "NO_RECAPTCHA" self.error = error + super.init() } override func verify(forceRefresh: Bool, action: AuthRecaptchaAction) async throws -> String { From dabe2d612fd735c2e2a9d4a0b195bdce6cf06bfa Mon Sep 17 00:00:00 2001 From: Pragati Date: Thu, 5 Sep 2024 10:55:11 -0700 Subject: [PATCH 5/6] add test attestation --- .../AuthProvider/PhoneAuthProvider.swift | 1 + .../Tests/Unit/PhoneAuthProviderTests.swift | 36 +++++++++++++------ 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift b/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift index 412396aa015..bc2f8d8dc10 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift @@ -420,6 +420,7 @@ import Foundation } throw AuthErrorUtils.unexpectedResponse(deserializedResponse: nil, underlyingError: error) } + print(error) throw error } diff --git a/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift b/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift index 3cff373b24d..ca84255253c 100644 --- a/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift +++ b/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift @@ -145,7 +145,7 @@ func testVerifyPhoneNumberWithRceEnforceInvalidRecaptcha() async throws { initApp(#function) let auth = try XCTUnwrap(PhoneAuthProviderTests.auth) - // TODO: Figure out how to mock objective C's FIRRecaptchaGetToken response + // TODO: Figure out how to mock objective C's FIRRecaptchaGetToken response let provider = PhoneAuthProvider.provider(auth: auth) let mockVerifier = FakeAuthRecaptchaVerifier() AuthRecaptchaVerifier.setShared(mockVerifier, auth: auth) @@ -158,7 +158,10 @@ XCTAssertEqual(request.codeIdentity, CodeIdentity.empty) do { try self.rpcIssuer? - .respond(serverErrorMessage: "INVALID_RECAPTCHA_TOKEN") + .respond( + serverErrorMessage: "INVALID_RECAPTCHA_TOKEN", + error: AuthErrorUtils.invalidRecaptchaTokenError() as NSError + ) } catch { XCTFail("Failure sending response: \(error)") } @@ -171,27 +174,38 @@ uiDelegate: nil, recaptchaVerifier: mockVerifier ) - //XCTAssertEqual(result, kTestVerificationID) + // XCTAssertEqual(result, kTestVerificationID) } catch { - XCTAssertEqual((error as NSError).code, AuthErrorCode.invalidRecaptchaToken.code.rawValue) + // Traverse the nested error to find the root cause + let underlyingError = (error as NSError).userInfo[NSUnderlyingErrorKey] as? NSError + let rootError = underlyingError?.userInfo[NSUnderlyingErrorKey] as? NSError + + // Compare the root error code to the expected error code + XCTAssertEqual(rootError?.code, AuthErrorCode.invalidRecaptchaToken.code.rawValue) } await fulfillment(of: [requestExpectation], timeout: 5.0) } - + /** @fn testVerifyPhoneNumberWithRceEnforceSDKNotLinked @brief Tests a successful invocation of @c verifyPhoneNumber with recaptcha enterprise enforced */ func testVerifyPhoneNumberWithRceEnforceRecaptchaSDKNotLinked() async throws { - return try await testRecaptchaFlowError(function: #function, rceError: AuthErrorUtils.recaptchaSDKNotLinkedError()) + return try await testRecaptchaFlowError( + function: #function, + rceError: AuthErrorUtils.recaptchaSDKNotLinkedError() + ) } - + /** @fn testVerifyPhoneNumberWithRceEnforceSDKNotLinked @brief Tests a successful invocation of @c verifyPhoneNumber with recaptcha enterprise enforced */ func testVerifyPhoneNumberWithRceEnforceRecaptchaActionCreationFailed() async throws { - return try await testRecaptchaFlowError(function: #function, rceError: AuthErrorUtils.recaptchaActionCreationFailed()) + return try await testRecaptchaFlowError( + function: #function, + rceError: AuthErrorUtils.recaptchaActionCreationFailed() + ) } /** @fn testVerifyPhoneNumberInTestMode @@ -435,11 +449,11 @@ } XCTAssertEqual(unarchivedCredential.provider, PhoneAuthProvider.id) } - - private func testRecaptchaFlowError(function: String, rceError: Error) async throws{ + + private func testRecaptchaFlowError(function: String, rceError: Error) async throws { initApp(function) let auth = try XCTUnwrap(PhoneAuthProviderTests.auth) - // TODO: Figure out how to mock objective C's FIRRecaptchaGetToken response + // TODO: Figure out how to mock objective C's FIRRecaptchaGetToken response // Mocking the output of verify() method let provider = PhoneAuthProvider.provider(auth: auth) let mockVerifier = FakeAuthRecaptchaVerifier(error: rceError) From 981939daf031b8b39a8a19eac34c3333d54fe8bf Mon Sep 17 00:00:00 2001 From: Pragati Date: Thu, 5 Sep 2024 17:07:56 -0700 Subject: [PATCH 6/6] fixes --- .../Sources/Swift/AuthProvider/PhoneAuthProvider.swift | 1 - FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift b/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift index bc2f8d8dc10..412396aa015 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift @@ -420,7 +420,6 @@ import Foundation } throw AuthErrorUtils.unexpectedResponse(deserializedResponse: nil, underlyingError: error) } - print(error) throw error } diff --git a/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift b/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift index ca84255253c..ae2bf574e3c 100644 --- a/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift +++ b/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift @@ -116,6 +116,7 @@ XCTAssertEqual(request.captchaResponse, self.kCaptchaResponse) XCTAssertEqual(request.recaptchaVersion, "RECAPTCHA_ENTERPRISE") XCTAssertEqual(request.codeIdentity, CodeIdentity.empty) + requestExpectation.fulfill() do { try self.rpcIssuer? .respond(withJSON: [self.kVerificationIDKey: self.kTestVerificationID]) @@ -123,7 +124,6 @@ XCTFail("Failure sending response: \(error)") } } - requestExpectation.fulfill() do { let result = try await provider.verifyClAndSendVerificationCodeWithRecaptcha( toPhoneNumber: kTestPhoneNumber, @@ -156,6 +156,7 @@ XCTAssertEqual(request.captchaResponse, "NO_RECAPTCHA") XCTAssertEqual(request.recaptchaVersion, "RECAPTCHA_ENTERPRISE") XCTAssertEqual(request.codeIdentity, CodeIdentity.empty) + requestExpectation.fulfill() do { try self.rpcIssuer? .respond( @@ -166,7 +167,6 @@ XCTFail("Failure sending response: \(error)") } } - requestExpectation.fulfill() do { _ = try await provider.verifyClAndSendVerificationCodeWithRecaptcha( toPhoneNumber: kTestPhoneNumber,