// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import Foundation import FirebaseCore @testable import FirebaseFunctions import FirebaseAppCheckInterop import FirebaseAuthInterop import FirebaseMessagingInterop import SharedTestUtilities import XCTest class ContextProviderTests: XCTestCase { let appCheckFake = FIRAppCheckFake() let appCheckTokenError = FIRAppCheckTokenResultFake(token: "dummy token", error: NSError( domain: "testAppCheckError", code: -1, userInfo: nil )) let appCheckLimitedUseTokenError = FIRAppCheckTokenResultFake(token: "limited use token", error: NSError( domain: "testAppCheckError", code: -1, userInfo: nil )) let appCheckTokenSuccess = FIRAppCheckTokenResultFake(token: "valid_token", error: nil) let messagingFake = FIRMessagingInteropFake() func testAsyncContextWithAuth() async throws { let auth = FIRAuthInteropFake(token: "token", userID: "userID", error: nil) let provider = FunctionsContextProvider(auth: auth, messaging: messagingFake, appCheck: nil) let context = try await provider.context(options: nil) XCTAssertNotNil(context) XCTAssertEqual(context.authToken, "token") XCTAssertEqual(context.fcmToken, messagingFake.fcmToken) } func testContextWithAuth() { let auth = FIRAuthInteropFake(token: "token", userID: "userID", error: nil) let provider = FunctionsContextProvider(auth: auth, messaging: messagingFake, appCheck: nil) let expectation = expectation(description: "Context should have auth keys.") provider.getContext { context, error in XCTAssertNotNil(context) XCTAssertEqual(context.authToken, "token") XCTAssertEqual(context.fcmToken, self.messagingFake.fcmToken) XCTAssertNil(error) expectation.fulfill() } waitForExpectations(timeout: 0.1) } func testAsyncContextWithAuthError() async { let authError = NSError(domain: "com.functions.tests", code: 4, userInfo: nil) let auth = FIRAuthInteropFake(token: nil, userID: "userID", error: authError) let provider = FunctionsContextProvider(auth: auth, messaging: messagingFake, appCheck: nil) do { _ = try await provider.context(options: nil) XCTFail("Expected an error") } catch { XCTAssertEqual(error as NSError, authError) } } func testContextWithAuthError() { let authError = NSError(domain: "com.functions.tests", code: 4, userInfo: nil) let auth = FIRAuthInteropFake(token: nil, userID: "userID", error: authError) let provider = FunctionsContextProvider(auth: auth, messaging: messagingFake, appCheck: nil) let expectation = expectation(description: "Completion handler should fail with Auth error.") provider.getContext { context, error in XCTAssertNotNil(context) XCTAssertNil(context.authToken) XCTAssertEqual(error as NSError?, authError) expectation.fulfill() } waitForExpectations(timeout: 0.1) } func testAsyncContextWithoutAuth() async throws { let provider = FunctionsContextProvider(auth: nil, messaging: nil, appCheck: nil) let context = try await provider.context(options: nil) XCTAssertNil(context.authToken) XCTAssertNil(context.fcmToken) } func testContextWithoutAuth() { let provider = FunctionsContextProvider(auth: nil, messaging: nil, appCheck: nil) let expectation = expectation(description: "Completion handler should succeed without Auth.") provider.getContext { context, error in XCTAssertNotNil(context) XCTAssertNil(error) XCTAssertNil(context.authToken) XCTAssertNil(context.fcmToken) expectation.fulfill() } waitForExpectations(timeout: 0.1) } func testAsyncContextWithAppCheckOnlySuccess() async throws { appCheckFake.tokenResult = appCheckTokenSuccess let provider = FunctionsContextProvider(auth: nil, messaging: nil, appCheck: appCheckFake) let context = try await provider.context(options: nil) XCTAssertNil(context.authToken) XCTAssertNil(context.fcmToken) XCTAssertEqual(context.appCheckToken, appCheckTokenSuccess.token) } func testContextWithAppCheckOnlySuccess() { appCheckFake.tokenResult = appCheckTokenSuccess let provider = FunctionsContextProvider(auth: nil, messaging: nil, appCheck: appCheckFake) let expectation = expectation(description: "Verify app check.") provider.getContext { context, error in XCTAssertNotNil(context) XCTAssertNil(error) XCTAssertNil(context.authToken) XCTAssertNil(context.fcmToken) XCTAssertEqual(context.appCheckToken, self.appCheckTokenSuccess.token) expectation.fulfill() } waitForExpectations(timeout: 0.1) } func testAsyncContextWithAppCheckOnlyError() async throws { appCheckFake.tokenResult = appCheckTokenError let provider = FunctionsContextProvider(auth: nil, messaging: nil, appCheck: appCheckFake) let context = try await provider.context(options: nil) XCTAssertNil(context.authToken) XCTAssertNil(context.fcmToken) // Expect placeholder token in the case of App Check error. XCTAssertEqual(context.appCheckToken, appCheckFake.tokenResult.token) } func testAsyncContextWithAppCheckOnlyError_LimitedUseToken() async throws { appCheckFake.limitedUseTokenResult = appCheckLimitedUseTokenError let provider = FunctionsContextProvider(auth: nil, messaging: nil, appCheck: appCheckFake) let context = try await provider.context(options: .init(requireLimitedUseAppCheckTokens: true)) XCTAssertNil(context.authToken) XCTAssertNil(context.fcmToken) // Expect placeholder token in the case of App Check error. XCTAssertEqual(context.limitedUseAppCheckToken, appCheckFake.limitedUseTokenResult.token) } func testContextWithAppCheckOnlyError() { appCheckFake.tokenResult = appCheckTokenError let provider = FunctionsContextProvider(auth: nil, messaging: nil, appCheck: appCheckFake) let expectation = expectation(description: "Verify bad app check token") provider.getContext { context, error in XCTAssertNotNil(context) XCTAssertNil(error) XCTAssertNil(context.authToken) XCTAssertNil(context.fcmToken) // Expect placeholder token in the case of App Check error. XCTAssertEqual(context.appCheckToken, self.appCheckFake.tokenResult.token) expectation.fulfill() } waitForExpectations(timeout: 0.1) } func testContextWithAppCheckOnlyError_LimitedUseToken() { appCheckFake.limitedUseTokenResult = appCheckLimitedUseTokenError let provider = FunctionsContextProvider(auth: nil, messaging: nil, appCheck: appCheckFake) let expectation = expectation(description: "Verify bad app check token") provider.getContext(options: .init(requireLimitedUseAppCheckTokens: true)) { context, error in XCTAssertNotNil(context) XCTAssertNil(error) XCTAssertNil(context.authToken) XCTAssertNil(context.fcmToken) // Expect placeholder token in the case of App Check error. XCTAssertEqual(context.limitedUseAppCheckToken, self.appCheckFake.limitedUseTokenResult.token) expectation.fulfill() } waitForExpectations(timeout: 0.1) } func testAsyncContextWithAppCheckWithoutOptionalMethods() async throws { let appCheck = AppCheckFakeWithoutOptionalMethods(tokenResult: appCheckTokenSuccess) let provider = FunctionsContextProvider(auth: nil, messaging: nil, appCheck: appCheck) let context = try await provider.context(options: .init(requireLimitedUseAppCheckTokens: true)) XCTAssertNil(context.authToken) XCTAssertNil(context.fcmToken) XCTAssertNil(context.appCheckToken) // If the method for limited-use tokens is not implemented, the value should be `nil`: XCTAssertNil(context.limitedUseAppCheckToken) } func testContextWithAppCheckWithoutOptionalMethods() { let appCheck = AppCheckFakeWithoutOptionalMethods(tokenResult: appCheckTokenSuccess) let provider = FunctionsContextProvider(auth: nil, messaging: nil, appCheck: appCheck) let expectation = expectation(description: "Verify non-implemented method for limited-use tokens") provider.getContext(options: .init(requireLimitedUseAppCheckTokens: true)) { context, error in XCTAssertNotNil(context) XCTAssertNil(error) XCTAssertNil(context.authToken) XCTAssertNil(context.fcmToken) XCTAssertNil(context.appCheckToken) // If the method for limited-use tokens is not implemented, the value should be `nil`: XCTAssertNil(context.limitedUseAppCheckToken) expectation.fulfill() } // Importantly, `getContext(options:_:)` must still finish in a timely manner: waitForExpectations(timeout: 0.1) } func testAsyncAllContextsAvailableSuccess() async throws { appCheckFake.tokenResult = appCheckTokenSuccess let auth = FIRAuthInteropFake(token: "token", userID: "userID", error: nil) let provider = FunctionsContextProvider( auth: auth, messaging: messagingFake, appCheck: appCheckFake ) let context = try await provider.context(options: nil) XCTAssertEqual(context.authToken, "token") XCTAssertEqual(context.fcmToken, messagingFake.fcmToken) XCTAssertEqual(context.appCheckToken, appCheckTokenSuccess.token) } func testAllContextsAvailableSuccess() { appCheckFake.tokenResult = appCheckTokenSuccess let auth = FIRAuthInteropFake(token: "token", userID: "userID", error: nil) let provider = FunctionsContextProvider( auth: auth, messaging: messagingFake, appCheck: appCheckFake ) let expectation = expectation(description: "All contexts available") provider.getContext { context, error in XCTAssertNotNil(context) XCTAssertNil(error) XCTAssertEqual(context.authToken, "token") XCTAssertEqual(context.fcmToken, self.messagingFake.fcmToken) XCTAssertEqual(context.appCheckToken, self.appCheckTokenSuccess.token) expectation.fulfill() } waitForExpectations(timeout: 0.1) } func testAsyncAllContextsAuthAndAppCheckError() async { appCheckFake.tokenResult = appCheckTokenError let authError = NSError(domain: "com.functions.tests", code: 4, userInfo: nil) let auth = FIRAuthInteropFake(token: nil, userID: "userID", error: authError) let provider = FunctionsContextProvider( auth: auth, messaging: messagingFake, appCheck: appCheckFake ) do { _ = try await provider.context(options: nil) XCTFail("Expected an error") } catch { XCTAssertEqual(error as NSError, authError) } } func testAllContextsAuthAndAppCheckError() { appCheckFake.tokenResult = appCheckTokenError let authError = NSError(domain: "com.functions.tests", code: 4, userInfo: nil) let auth = FIRAuthInteropFake(token: nil, userID: "userID", error: authError) let provider = FunctionsContextProvider( auth: auth, messaging: messagingFake, appCheck: appCheckFake ) let expectation = expectation(description: "All contexts with errors") provider.getContext { context, error in XCTAssertNotNil(context) XCTAssertEqual(error as NSError?, authError) XCTAssertNil(context.authToken) XCTAssertEqual(context.fcmToken, self.messagingFake.fcmToken) // Expect placeholder token in the case of App Check error. XCTAssertEqual(context.appCheckToken, self.appCheckFake.tokenResult.token) expectation.fulfill() } waitForExpectations(timeout: 0.1) } func testAllContextsAuthAndAppCheckError_LimitedUseToken() { appCheckFake.limitedUseTokenResult = appCheckLimitedUseTokenError let authError = NSError(domain: "com.functions.tests", code: 4, userInfo: nil) let auth = FIRAuthInteropFake(token: nil, userID: "userID", error: authError) let provider = FunctionsContextProvider( auth: auth, messaging: messagingFake, appCheck: appCheckFake ) let expectation = expectation(description: "All contexts with errors") provider.getContext(options: .init(requireLimitedUseAppCheckTokens: true)) { context, error in XCTAssertNotNil(context) XCTAssertEqual(error as NSError?, authError) XCTAssertNil(context.authToken) XCTAssertEqual(context.fcmToken, self.messagingFake.fcmToken) // Expect placeholder token in the case of App Check error. XCTAssertEqual(context.limitedUseAppCheckToken, self.appCheckFake.limitedUseTokenResult.token) expectation.fulfill() } waitForExpectations(timeout: 0.1) } } // MARK: - Utilities private class AppCheckFakeWithoutOptionalMethods: NSObject, AppCheckInterop { let tokenResult: FIRAppCheckTokenResultInterop init(tokenResult: FIRAppCheckTokenResultInterop) { self.tokenResult = tokenResult } func getToken(forcingRefresh: Bool, completion handler: @escaping AppCheckTokenHandlerInterop) { handler(tokenResult) } func tokenDidChangeNotificationName() -> String { "AppCheckFakeTokenDidChangeNotification" } func notificationTokenKey() -> String { "AppCheckFakeTokenNotificationKey" } func notificationAppNameKey() -> String { "AppCheckFakeAppNameNotificationKey" } }