mirror of
https://github.com/google/app-check
synced 2026-11-09 22:05:52 +05:45
The debug provider stores a per-service "registered" flag so it stops logging the local debug token's registration instructions once that token has been exchanged successfully. Exchanges using the AppCheckDebugToken or FIRAAppCheckDebugToken environment variable also updated the flag, even though the local token wasn't used: - On success, the local token was marked as registered, so after the variable was removed the SDK fell back to an unregistered token without logging how to register it. - On failure, the flag was cleared, so an already registered local token logged its registration instructions again. Only set or clear the flag when the local token was the one exchanged; serverUnreachable failures still leave it untouched. There are no public API changes. Also document all parameters of the public initializer, and fix the test tearDown, which only removed the registered key's prefix and let the flag leak between tests.
627 lines
23 KiB
Swift
627 lines
23 KiB
Swift
// Copyright 2026 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 XCTest
|
|
#if canImport(GoogleUtilities)
|
|
import GoogleUtilities
|
|
#endif
|
|
@testable import AppCheckCore
|
|
|
|
class MockAppCheckDebugProviderAPIService: NSObject, AppCheckCoreDebugProviderAPIServiceProtocol {
|
|
var passedDebugToken: String?
|
|
var passedLimitedUse: Bool?
|
|
|
|
var tokenResult: Result<AppCheckCoreToken, Error>?
|
|
var limitedUseTokenResult: Result<AppCheckCoreToken, Error>?
|
|
|
|
func appCheckToken(debugToken: String, limitedUse: Bool) async throws -> AppCheckCoreToken {
|
|
passedDebugToken = debugToken
|
|
passedLimitedUse = limitedUse
|
|
|
|
let result = limitedUse ? limitedUseTokenResult : tokenResult
|
|
|
|
guard let result = result else {
|
|
throw NSError(domain: "MockError", code: -1, userInfo: nil)
|
|
}
|
|
|
|
switch result {
|
|
case let .success(token):
|
|
return token
|
|
case let .failure(error):
|
|
throw error
|
|
}
|
|
}
|
|
}
|
|
|
|
class AppCheckCoreDebugProviderTests: XCTestCase {
|
|
let kDebugTokenEnvKey = "AppCheckDebugToken"
|
|
let kFirebaseDebugTokenEnvKey = "FIRAAppCheckDebugToken"
|
|
let kDebugTokenUserDefaultsKey = AppCheckCoreDebugProvider.debugTokenUserDefaultsKey
|
|
let kDebugTokenRegisteredUserDefaultsKey = AppCheckCoreDebugProvider
|
|
.debugTokenRegisteredUserDefaultsKeyPrefix
|
|
|
|
var provider: AppCheckCoreDebugProvider!
|
|
var fakeAPIService: MockAppCheckDebugProviderAPIService!
|
|
|
|
override func setUp() {
|
|
super.setUp()
|
|
fakeAPIService = MockAppCheckDebugProviderAPIService()
|
|
provider = AppCheckCoreDebugProvider(apiService: fakeAPIService,
|
|
serviceName: "test-service",
|
|
resourceName: "projects/test-project/apps/test-app",
|
|
environment: [:])
|
|
}
|
|
|
|
override func tearDown() {
|
|
provider = nil
|
|
UserDefaults.standard.removeObject(forKey: kDebugTokenUserDefaultsKey)
|
|
UserDefaults.standard.removeObject(forKey: kDebugTokenRegisteredUserDefaultsKey)
|
|
// The prefix alone is not a real key; also clear the flag for the suite's default provider.
|
|
UserDefaults.standard.removeObject(
|
|
forKey: AppCheckCoreDebugProvider.registeredUserDefaultsKey(
|
|
forServiceName: "test-service",
|
|
resourceName: "projects/test-project/apps/test-app"
|
|
)
|
|
)
|
|
super.tearDown()
|
|
}
|
|
|
|
// MARK: - Debug token generating/storing
|
|
|
|
// IMMUTABLE TEST: Do not edit, update, or remove under any circumstance.
|
|
// This test locks in an immutable backwards compatibility contract with App Check 11.
|
|
// If this test fails, revert changes to the implementation instead of modifying this test.
|
|
func testDebugTokenUserDefaultsKeys_MatchContract() {
|
|
XCTAssertEqual(
|
|
AppCheckCoreDebugProvider.debugTokenUserDefaultsKey,
|
|
"GACAppCheckDebugToken",
|
|
"Debug token key mismatch breaks App Check 11 migration compatibility."
|
|
)
|
|
XCTAssertEqual(
|
|
AppCheckCoreDebugProvider.debugTokenRegisteredUserDefaultsKeyPrefix,
|
|
"GACAppCheckDebugTokenRegistered",
|
|
"Registered token key prefix mismatch breaks App Check 11 migration compatibility."
|
|
)
|
|
}
|
|
|
|
func testLegacyDebugTokenCompatibility() {
|
|
let legacyKey = kDebugTokenUserDefaultsKey
|
|
let legacyToken = "legacy_stored_debug_token"
|
|
UserDefaults.standard.set(legacyToken, forKey: legacyKey)
|
|
defer {
|
|
UserDefaults.standard.removeObject(forKey: legacyKey)
|
|
}
|
|
|
|
let debugProvider = AppCheckCoreDebugProvider(
|
|
apiService: fakeAPIService,
|
|
serviceName: "test-service",
|
|
resourceName: "projects/test-project/apps/test-app",
|
|
environment: [:]
|
|
)
|
|
|
|
XCTAssertEqual(
|
|
debugProvider.currentDebugToken(),
|
|
legacyToken,
|
|
"Debug token mismatch breaks App Check 11 migration compatibility."
|
|
)
|
|
}
|
|
|
|
func testSetDebugToken_CompatibilityWithLegacyGACStorage() {
|
|
let legacyKey = kDebugTokenUserDefaultsKey
|
|
UserDefaults.standard.removeObject(forKey: legacyKey)
|
|
defer {
|
|
UserDefaults.standard.removeObject(forKey: legacyKey)
|
|
}
|
|
|
|
let debugProvider = AppCheckCoreDebugProvider(
|
|
apiService: fakeAPIService,
|
|
serviceName: "test-service",
|
|
resourceName: "projects/test-project/apps/test-app",
|
|
environment: [:]
|
|
)
|
|
|
|
let generatedToken = debugProvider.currentDebugToken()
|
|
let storedLegacyValue = UserDefaults.standard.string(forKey: legacyKey)
|
|
XCTAssertEqual(
|
|
storedLegacyValue,
|
|
generatedToken,
|
|
"Generated debug token was not stored under legacy key."
|
|
)
|
|
}
|
|
|
|
func testLegacyRegisteredDebugTokenCompatibility() async throws {
|
|
let serviceName = "test-service"
|
|
let resourceName = "projects/test-project/apps/test-app"
|
|
let expectedLegacyRegisteredKey =
|
|
"GACAppCheckDebugTokenRegistered_test-service_projects_test-project_apps_test-app"
|
|
|
|
// 1. Verify key resolution matches legacy v11 format.
|
|
let resolvedKey = AppCheckCoreDebugProvider.registeredUserDefaultsKey(
|
|
forServiceName: serviceName,
|
|
resourceName: resourceName
|
|
)
|
|
XCTAssertEqual(
|
|
resolvedKey,
|
|
expectedLegacyRegisteredKey,
|
|
"Resolved registered token key does not match legacy v11 format."
|
|
)
|
|
|
|
// 2. Verify write-back persists to UserDefaults using legacy key format.
|
|
UserDefaults.standard.removeObject(forKey: expectedLegacyRegisteredKey)
|
|
defer {
|
|
UserDefaults.standard.removeObject(forKey: expectedLegacyRegisteredKey)
|
|
}
|
|
|
|
let validToken = AppCheckCoreToken(
|
|
token: "valid_token",
|
|
expirationDate: Date().addingTimeInterval(3600)
|
|
)
|
|
fakeAPIService.tokenResult = .success(validToken)
|
|
|
|
let debugProvider = AppCheckCoreDebugProvider(
|
|
apiService: fakeAPIService,
|
|
serviceName: serviceName,
|
|
resourceName: resourceName,
|
|
environment: [:]
|
|
)
|
|
|
|
XCTAssertFalse(
|
|
UserDefaults.standard.bool(forKey: expectedLegacyRegisteredKey),
|
|
"Precondition: Key should not be registered before getToken()."
|
|
)
|
|
|
|
_ = try await debugProvider.getToken()
|
|
|
|
XCTAssertTrue(
|
|
UserDefaults.standard.bool(forKey: expectedLegacyRegisteredKey),
|
|
"Registered flag was not stored under legacy key format GACAppCheckDebugTokenRegistered_<service>_<resource>."
|
|
)
|
|
}
|
|
|
|
func testCurrentTokenWhenEnvironmentVariableSetAndTokenStored() {
|
|
UserDefaults.standard.set("stored token", forKey: kDebugTokenUserDefaultsKey)
|
|
let envToken = "env token"
|
|
provider = AppCheckCoreDebugProvider(apiService: fakeAPIService,
|
|
serviceName: "test-service",
|
|
resourceName: "projects/test-project/apps/test-app",
|
|
environment: [kDebugTokenEnvKey: envToken])
|
|
|
|
XCTAssertEqual(provider.currentDebugToken(), envToken)
|
|
}
|
|
|
|
func testCurrentTokenWhenFirebaseAndCoreEnvironmentVariablesSetAndTokenStored() {
|
|
UserDefaults.standard.set("stored token", forKey: kDebugTokenUserDefaultsKey)
|
|
let envToken = "env token"
|
|
provider = AppCheckCoreDebugProvider(apiService: fakeAPIService,
|
|
serviceName: "test-service",
|
|
resourceName: "projects/test-project/apps/test-app",
|
|
environment: [
|
|
kDebugTokenEnvKey: envToken,
|
|
kFirebaseDebugTokenEnvKey: "firebase env token",
|
|
])
|
|
|
|
XCTAssertEqual(provider.currentDebugToken(), envToken)
|
|
}
|
|
|
|
func testCurrentTokenWhenFirebaseEnvironmentVariableSetAndTokenStored() {
|
|
UserDefaults.standard.set("stored token", forKey: kDebugTokenUserDefaultsKey)
|
|
let envToken = "env token"
|
|
provider = AppCheckCoreDebugProvider(apiService: fakeAPIService,
|
|
serviceName: "test-service",
|
|
resourceName: "projects/test-project/apps/test-app",
|
|
environment: [kFirebaseDebugTokenEnvKey: envToken])
|
|
|
|
XCTAssertEqual(provider.currentDebugToken(), envToken)
|
|
}
|
|
|
|
func testCurrentTokenWhenFirebaseAndCoreEnvironmentVariablesSet() {
|
|
let envToken = "env token"
|
|
provider = AppCheckCoreDebugProvider(apiService: fakeAPIService,
|
|
serviceName: "test-service",
|
|
resourceName: "projects/test-project/apps/test-app",
|
|
environment: [
|
|
kDebugTokenEnvKey: envToken,
|
|
kFirebaseDebugTokenEnvKey: "firebase env token",
|
|
])
|
|
|
|
XCTAssertEqual(provider.currentDebugToken(), envToken)
|
|
}
|
|
|
|
func testCurrentTokenWhenNoEnvironmentVariableAndTokenStored() {
|
|
let storedToken = "stored token"
|
|
UserDefaults.standard.set(storedToken, forKey: kDebugTokenUserDefaultsKey)
|
|
|
|
XCTAssertEqual(provider.currentDebugToken(), storedToken)
|
|
XCTAssertEqual(provider.currentDebugToken(), storedToken)
|
|
}
|
|
|
|
func testCurrentTokenWhenNoEnvironmentVariableAndNoTokenStored() {
|
|
UserDefaults.standard.removeObject(forKey: kDebugTokenUserDefaultsKey)
|
|
XCTAssertNil(UserDefaults.standard.string(forKey: kDebugTokenUserDefaultsKey))
|
|
|
|
let generatedToken = provider.currentDebugToken()
|
|
XCTAssertNotNil(generatedToken)
|
|
|
|
// Check if the generated token is stored to the user defaults.
|
|
XCTAssertEqual(UserDefaults.standard.string(forKey: kDebugTokenUserDefaultsKey), generatedToken)
|
|
|
|
// Check if the same token is used once generated.
|
|
XCTAssertEqual(provider.currentDebugToken(), generatedToken)
|
|
}
|
|
|
|
func testGetTokenSuccessWithEnvironmentDebugTokenDoesNotSetRegisteredFlag() async throws {
|
|
let registeredKey = AppCheckCoreDebugProvider.registeredUserDefaultsKey(
|
|
forServiceName: "test-service",
|
|
resourceName: "projects/test-project/apps/test-app"
|
|
)
|
|
UserDefaults.standard.removeObject(forKey: registeredKey)
|
|
provider = AppCheckCoreDebugProvider(apiService: fakeAPIService,
|
|
serviceName: "test-service",
|
|
resourceName: "projects/test-project/apps/test-app",
|
|
environment: [kDebugTokenEnvKey: "env token"])
|
|
fakeAPIService.tokenResult = .success(AppCheckCoreToken(
|
|
token: "valid_token",
|
|
expirationDate: Date().addingTimeInterval(3600)
|
|
))
|
|
|
|
_ = try await provider.getToken()
|
|
|
|
XCTAssertNil(UserDefaults.standard.object(forKey: registeredKey))
|
|
}
|
|
|
|
func testGetTokenFailureWithEnvironmentDebugTokenPreservesRegisteredFlag() async throws {
|
|
let registeredKey = AppCheckCoreDebugProvider.registeredUserDefaultsKey(
|
|
forServiceName: "test-service",
|
|
resourceName: "projects/test-project/apps/test-app"
|
|
)
|
|
UserDefaults.standard.set(true, forKey: registeredKey)
|
|
provider = AppCheckCoreDebugProvider(apiService: fakeAPIService,
|
|
serviceName: "test-service",
|
|
resourceName: "projects/test-project/apps/test-app",
|
|
environment: [kDebugTokenEnvKey: "env token"])
|
|
fakeAPIService.tokenResult = .failure(NSError(domain: "testDomain", code: -1, userInfo: nil))
|
|
|
|
await XCTAssertThrowsErrorAsync(try await provider.getToken())
|
|
|
|
XCTAssertTrue(UserDefaults.standard.bool(forKey: registeredKey))
|
|
}
|
|
|
|
/// Asserts that `expression` throws, since `XCTAssertThrowsError` does not accept async
|
|
/// expressions.
|
|
private func XCTAssertThrowsErrorAsync<T>(_ expression: @autoclosure () async throws -> T,
|
|
file: StaticString = #filePath,
|
|
line: UInt = #line) async {
|
|
do {
|
|
_ = try await expression()
|
|
XCTFail("Expected error to be thrown", file: file, line: line)
|
|
} catch {}
|
|
}
|
|
|
|
// MARK: - Debug token to FAC token exchange
|
|
|
|
func testGetTokenSuccess() async throws {
|
|
// 1. Stub API service.
|
|
let expectedDebugToken = provider.currentDebugToken()
|
|
let validToken = AppCheckCoreToken(
|
|
token: "valid_token",
|
|
expirationDate: Date(),
|
|
receivedAt: Date()
|
|
)
|
|
fakeAPIService.tokenResult = .success(validToken)
|
|
|
|
// 2. Validate get token.
|
|
let token = try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<
|
|
AppCheckCoreToken,
|
|
Error
|
|
>) in
|
|
provider.getToken { token, error in
|
|
if let error = error {
|
|
continuation.resume(throwing: error)
|
|
} else if let token = token {
|
|
continuation.resume(returning: token)
|
|
} else {
|
|
continuation.resume(throwing: NSError(domain: "TestError", code: -1, userInfo: nil))
|
|
}
|
|
}
|
|
}
|
|
|
|
XCTAssertEqual(token.token, validToken.token)
|
|
XCTAssertEqual(token.expirationDate, validToken.expirationDate)
|
|
XCTAssertEqual(token.receivedAtDate, validToken.receivedAtDate)
|
|
|
|
// 3. Verify fakes.
|
|
XCTAssertEqual(fakeAPIService.passedDebugToken, expectedDebugToken)
|
|
XCTAssertEqual(fakeAPIService.passedLimitedUse, false)
|
|
}
|
|
|
|
func testGetTokenAPIError() async throws {
|
|
// 1. Stub API service.
|
|
let expectedDebugToken = provider.currentDebugToken()
|
|
let apiError = NSError(domain: "testGetTokenAPIError", code: -1, userInfo: nil)
|
|
fakeAPIService.tokenResult = .failure(apiError)
|
|
|
|
// 2. Validate get token.
|
|
do {
|
|
_ = try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<
|
|
AppCheckCoreToken,
|
|
Error
|
|
>) in
|
|
provider.getToken { token, error in
|
|
if let error = error {
|
|
continuation.resume(throwing: error)
|
|
} else if let token = token {
|
|
continuation.resume(returning: token)
|
|
} else {
|
|
continuation.resume(throwing: NSError(domain: "TestError", code: -1, userInfo: nil))
|
|
}
|
|
}
|
|
}
|
|
XCTFail("Expected error to be thrown")
|
|
} catch let error as NSError {
|
|
XCTAssertEqual(error, apiError)
|
|
}
|
|
|
|
// 3. Verify fakes.
|
|
XCTAssertEqual(fakeAPIService.passedDebugToken, expectedDebugToken)
|
|
XCTAssertEqual(fakeAPIService.passedLimitedUse, false)
|
|
}
|
|
|
|
func testGetLimitedUseTokenSuccess() async throws {
|
|
// 1. Stub API service.
|
|
let expectedDebugToken = provider.currentDebugToken()
|
|
let validToken = AppCheckCoreToken(
|
|
token: "valid_token",
|
|
expirationDate: Date(),
|
|
receivedAt: Date()
|
|
)
|
|
fakeAPIService.limitedUseTokenResult = .success(validToken)
|
|
|
|
// 2. Validate get limited-use token.
|
|
let token = try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<
|
|
AppCheckCoreToken,
|
|
Error
|
|
>) in
|
|
provider.getLimitedUseToken { token, error in
|
|
if let error = error {
|
|
continuation.resume(throwing: error)
|
|
} else if let token = token {
|
|
continuation.resume(returning: token)
|
|
} else {
|
|
continuation.resume(throwing: NSError(domain: "TestError", code: -1, userInfo: nil))
|
|
}
|
|
}
|
|
}
|
|
|
|
XCTAssertEqual(token.token, validToken.token)
|
|
XCTAssertEqual(token.expirationDate, validToken.expirationDate)
|
|
XCTAssertEqual(token.receivedAtDate, validToken.receivedAtDate)
|
|
|
|
// 3. Verify fakes.
|
|
XCTAssertEqual(fakeAPIService.passedDebugToken, expectedDebugToken)
|
|
XCTAssertEqual(fakeAPIService.passedLimitedUse, true)
|
|
}
|
|
|
|
func testGetLimitedUseTokenAPIError() async throws {
|
|
// 1. Stub API service.
|
|
let expectedDebugToken = provider.currentDebugToken()
|
|
let apiError = NSError(domain: "testGetLimitedUseTokenAPIError", code: -1, userInfo: nil)
|
|
fakeAPIService.limitedUseTokenResult = .failure(apiError)
|
|
|
|
// 2. Validate get limited-use token.
|
|
do {
|
|
_ = try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<
|
|
AppCheckCoreToken,
|
|
Error
|
|
>) in
|
|
provider.getLimitedUseToken { token, error in
|
|
if let error = error {
|
|
continuation.resume(throwing: error)
|
|
} else if let token = token {
|
|
continuation.resume(returning: token)
|
|
} else {
|
|
continuation.resume(throwing: NSError(domain: "TestError", code: -1, userInfo: nil))
|
|
}
|
|
}
|
|
}
|
|
XCTFail("Expected error to be thrown")
|
|
} catch let error as NSError {
|
|
XCTAssertEqual(error, apiError)
|
|
}
|
|
|
|
// 3. Verify fakes.
|
|
XCTAssertEqual(fakeAPIService.passedDebugToken, expectedDebugToken)
|
|
XCTAssertEqual(fakeAPIService.passedLimitedUse, true)
|
|
}
|
|
|
|
func testGetTokenSuccessSetsRegisteredFlag() async throws {
|
|
// 1. Stub API service.
|
|
let expectedDebugToken = provider.currentDebugToken()
|
|
let validToken = AppCheckCoreToken(
|
|
token: "valid_token",
|
|
expirationDate: Date(),
|
|
receivedAt: Date()
|
|
)
|
|
fakeAPIService.tokenResult = .success(validToken)
|
|
|
|
// The mirror way to get registeredUserDefaultsKey, or since we know it, we can just hardcode or
|
|
// access it
|
|
let registeredKey =
|
|
"\(kDebugTokenRegisteredUserDefaultsKey)_test-service_projects_test-project_apps_test-app"
|
|
UserDefaults.standard.removeObject(forKey: registeredKey)
|
|
|
|
// 2. Validate get token.
|
|
_ = try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<
|
|
AppCheckCoreToken,
|
|
Error
|
|
>) in
|
|
provider.getToken { token, error in
|
|
if let error = error {
|
|
continuation.resume(throwing: error)
|
|
} else if let token = token {
|
|
continuation.resume(returning: token)
|
|
} else {
|
|
continuation.resume(throwing: NSError(domain: "TestError", code: -1, userInfo: nil))
|
|
}
|
|
}
|
|
}
|
|
|
|
// 3. Verify flag is now YES.
|
|
XCTAssertTrue(UserDefaults.standard.bool(forKey: registeredKey))
|
|
|
|
// 4. Verify fakes.
|
|
XCTAssertEqual(fakeAPIService.passedDebugToken, expectedDebugToken)
|
|
XCTAssertEqual(fakeAPIService.passedLimitedUse, false)
|
|
}
|
|
|
|
func testGetTokenPermanentFailureClearsRegisteredFlag() async throws {
|
|
// 1. Stub API service.
|
|
let expectedDebugToken = provider.currentDebugToken()
|
|
let apiError = NSError(
|
|
domain: "testGetTokenPermanentFailureClearsRegisteredFlag",
|
|
code: -1,
|
|
userInfo: nil
|
|
)
|
|
fakeAPIService.tokenResult = .failure(apiError)
|
|
|
|
let registeredKey =
|
|
"\(kDebugTokenRegisteredUserDefaultsKey)_test-service_projects_test-project_apps_test-app"
|
|
UserDefaults.standard.set(true, forKey: registeredKey)
|
|
|
|
// 2. Validate get token.
|
|
do {
|
|
_ = try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<
|
|
AppCheckCoreToken,
|
|
Error
|
|
>) in
|
|
provider.getToken { token, error in
|
|
if let error = error {
|
|
continuation.resume(throwing: error)
|
|
} else if let token = token {
|
|
continuation.resume(returning: token)
|
|
} else {
|
|
continuation.resume(throwing: NSError(domain: "TestError", code: -1, userInfo: nil))
|
|
}
|
|
}
|
|
}
|
|
XCTFail("Expected error to be thrown")
|
|
} catch _ {
|
|
// expected
|
|
}
|
|
|
|
// 3. Verify flag is cleared.
|
|
XCTAssertNil(UserDefaults.standard.object(forKey: registeredKey))
|
|
|
|
// 4. Verify fakes.
|
|
XCTAssertEqual(fakeAPIService.passedDebugToken, expectedDebugToken)
|
|
XCTAssertEqual(fakeAPIService.passedLimitedUse, false)
|
|
}
|
|
|
|
func testGetTokenNetworkFailureDoesNotClearRegisteredFlag() async throws {
|
|
// 1. Stub API service.
|
|
let expectedDebugToken = provider.currentDebugToken()
|
|
let networkError = NSError(
|
|
domain: AppCheckCoreErrorDomain,
|
|
code: AppCheckCoreErrorCode.serverUnreachable.rawValue,
|
|
userInfo: nil
|
|
)
|
|
fakeAPIService.tokenResult = .failure(networkError)
|
|
|
|
let registeredKey =
|
|
"\(kDebugTokenRegisteredUserDefaultsKey)_test-service_projects_test-project_apps_test-app"
|
|
UserDefaults.standard.set(true, forKey: registeredKey)
|
|
|
|
// 2. Validate get token.
|
|
do {
|
|
_ = try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<
|
|
AppCheckCoreToken,
|
|
Error
|
|
>) in
|
|
provider.getToken { token, error in
|
|
if let error = error {
|
|
continuation.resume(throwing: error)
|
|
} else if let token = token {
|
|
continuation.resume(returning: token)
|
|
} else {
|
|
continuation.resume(throwing: NSError(domain: "TestError", code: -1, userInfo: nil))
|
|
}
|
|
}
|
|
}
|
|
XCTFail("Expected error to be thrown")
|
|
} catch let error as NSError {
|
|
XCTAssertEqual(error, networkError)
|
|
}
|
|
|
|
// 3. Verify flag is still YES.
|
|
XCTAssertTrue(UserDefaults.standard.bool(forKey: registeredKey))
|
|
|
|
// 4. Verify fakes.
|
|
XCTAssertEqual(fakeAPIService.passedDebugToken, expectedDebugToken)
|
|
XCTAssertEqual(fakeAPIService.passedLimitedUse, false)
|
|
}
|
|
|
|
func testGetTokenWithEnvironmentDebugTokenDoesNotCreateLocalToken() async throws {
|
|
// `setUp` creates a provider without an environment variable, which stores a local token.
|
|
UserDefaults.standard.removeObject(forKey: kDebugTokenUserDefaultsKey)
|
|
provider = AppCheckCoreDebugProvider(apiService: fakeAPIService,
|
|
serviceName: "test-service",
|
|
resourceName: "projects/test-project/apps/test-app",
|
|
environment: [kDebugTokenEnvKey: "env token"])
|
|
fakeAPIService.tokenResult = .success(AppCheckCoreToken(
|
|
token: "valid_token",
|
|
expirationDate: Date().addingTimeInterval(3600)
|
|
))
|
|
|
|
_ = try await provider.getToken()
|
|
|
|
XCTAssertEqual(fakeAPIService.passedDebugToken, "env token")
|
|
// The local token is only generated, stored, and logged when it is the token in use.
|
|
XCTAssertNil(UserDefaults.standard.string(forKey: kDebugTokenUserDefaultsKey))
|
|
}
|
|
|
|
// MARK: - Registered UserDefaults key construction
|
|
|
|
/// Backfilled from the v11 suite
|
|
/// (`testRegisteredUserDefaultsKeyForServiceName_resourceName`). This key
|
|
/// namespaces the "debug token already registered" flag, so a change to its
|
|
/// construction would silently orphan the flag for every existing install and
|
|
/// cause a spurious re-registration.
|
|
func testRegisteredUserDefaultsKeyForServiceNameResourceName() {
|
|
// Slashes in the resource name must be sanitized to underscores.
|
|
XCTAssertEqual(
|
|
AppCheckCoreDebugProvider.registeredUserDefaultsKey(
|
|
forServiceName: "app1", resourceName: "projects/p1/apps/a1"
|
|
),
|
|
"GACAppCheckDebugTokenRegistered_app1_projects_p1_apps_a1"
|
|
)
|
|
XCTAssertEqual(
|
|
AppCheckCoreDebugProvider.registeredUserDefaultsKey(
|
|
forServiceName: "app2", resourceName: "projects/p2/apps/a2"
|
|
),
|
|
"GACAppCheckDebugTokenRegistered_app2_projects_p2_apps_a2"
|
|
)
|
|
|
|
// Empty components fall back to "default".
|
|
//
|
|
// Note: v11 also covered `nil` for both parameters. Swift's type system
|
|
// makes that unrepresentable here (both are non-optional `String`), so the
|
|
// empty-string case is the only reachable fallback.
|
|
XCTAssertEqual(
|
|
AppCheckCoreDebugProvider.registeredUserDefaultsKey(
|
|
forServiceName: "", resourceName: ""
|
|
),
|
|
"GACAppCheckDebugTokenRegistered_default_default"
|
|
)
|
|
}
|
|
}
|