mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-10 22:45:59 +05:45
207 lines
7.0 KiB
Swift
207 lines
7.0 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.
|
|
|
|
#if canImport(Testing)
|
|
import Foundation
|
|
package import GeminiAPIClient
|
|
package import Testing
|
|
|
|
/// Represents an integration testing backend for Gemini API requests.
|
|
package enum IntegrationTestingBackend:
|
|
Sendable,
|
|
Hashable,
|
|
CaseIterable,
|
|
CustomStringConvertible
|
|
{
|
|
/// Gemini Developer API (via `generativelanguage.googleapis.com`).
|
|
case developerAPI
|
|
|
|
/// Firebase AI Logic proxying to the Gemini Developer API (via
|
|
/// `firebasevertexai.googleapis.com`).
|
|
case firebaseAILogicDeveloperAPI
|
|
|
|
/// Firebase AI Logic proxying to the Gemini Enterprise API (via
|
|
/// `firebasevertexai.googleapis.com`).
|
|
case firebaseAILogicEnterprise(location: String)
|
|
|
|
/// Convenience instance targeting the global Gemini Enterprise API.
|
|
package static var firebaseAILogicEnterprise: IntegrationTestingBackend {
|
|
.firebaseAILogicEnterprise(location: "global")
|
|
}
|
|
|
|
/// All canonical backend configurations for parameterized testing.
|
|
package static let allCases: [IntegrationTestingBackend] = [
|
|
.developerAPI,
|
|
.firebaseAILogicDeveloperAPI,
|
|
.firebaseAILogicEnterprise(location: "global"),
|
|
]
|
|
|
|
package var description: String {
|
|
switch self {
|
|
case .developerAPI:
|
|
return "Developer API"
|
|
case .firebaseAILogicDeveloperAPI:
|
|
return "Firebase AI Logic (Developer API)"
|
|
case .firebaseAILogicEnterprise(let location):
|
|
return "Firebase AI Logic (Enterprise, \(location))"
|
|
}
|
|
}
|
|
|
|
/// Indicates whether the required credentials are available for this backend in the
|
|
/// specified test environment.
|
|
///
|
|
/// - Parameter environment: The test environment to evaluate. Defaults to `.process`.
|
|
/// - Returns: `true` if this backend is available to execute tests; otherwise, `false`.
|
|
package func isAvailable(in environment: IntegrationTestEnvironment = .process) -> Bool {
|
|
switch self {
|
|
case .developerAPI:
|
|
return environment.hasGeminiAPIKey
|
|
|
|
case .firebaseAILogicDeveloperAPI, .firebaseAILogicEnterprise:
|
|
return environment.hasFirebaseAILogicCredentials
|
|
}
|
|
}
|
|
|
|
/// Indicates whether the required credentials are available for this backend.
|
|
package var isAvailable: Bool {
|
|
isAvailable(in: .process)
|
|
}
|
|
|
|
/// The list of backends that are currently available to execute tests against.
|
|
package static var availableBackends: [IntegrationTestingBackend] {
|
|
allCases.filter(\.isAvailable)
|
|
}
|
|
|
|
/// The network endpoint configuration for this backend.
|
|
package var endpointConfiguration: EndpointConfiguration {
|
|
switch self {
|
|
case .developerAPI:
|
|
return .geminiDeveloperAPI
|
|
|
|
case .firebaseAILogicDeveloperAPI, .firebaseAILogicEnterprise:
|
|
return .firebaseAILogic
|
|
}
|
|
}
|
|
|
|
/// The model resource configuration for this backend given a model identifier.
|
|
package func modelResource(
|
|
modelID: String = ModelResource.gemini35FlashLiteID
|
|
) throws -> ModelResource {
|
|
switch self {
|
|
case .developerAPI:
|
|
return ModelResource(
|
|
modelID: modelID,
|
|
urlResourceName: "models/\(modelID)",
|
|
payloadResourceName: "models/\(modelID)"
|
|
)
|
|
|
|
case .firebaseAILogicDeveloperAPI:
|
|
let projectID = try resolveProjectID()
|
|
return ModelResource(
|
|
modelID: modelID,
|
|
urlResourceName: "projects/\(projectID)/models/\(modelID)",
|
|
payloadResourceName: "models/\(modelID)"
|
|
)
|
|
|
|
case .firebaseAILogicEnterprise(let location):
|
|
let projectID = try resolveProjectID()
|
|
let resourcePath =
|
|
"projects/\(projectID)/locations/\(location)/publishers/google/models/\(modelID)"
|
|
return ModelResource(
|
|
modelID: modelID,
|
|
urlResourceName: resourcePath,
|
|
payloadResourceName: "models/\(modelID)"
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Creates the authentication header provider for this backend.
|
|
@available(macOS 15.0, iOS 18.0, tvOS 18.0, watchOS 11.0, visionOS 2.0, *)
|
|
package func makeHeaderProvider() throws -> (
|
|
@Sendable () async throws -> [String: String]
|
|
)? {
|
|
switch self {
|
|
case .developerAPI:
|
|
if let apiKey = geminiAPIKey {
|
|
return { ["x-goog-api-key": apiKey] }
|
|
}
|
|
return nil
|
|
|
|
case .firebaseAILogicDeveloperAPI, .firebaseAILogicEnterprise:
|
|
let projectID = try resolveProjectID()
|
|
guard let appID = firebaseAppID else {
|
|
throw IntegrationBackendError.missingCredential("firebaseAppID")
|
|
}
|
|
guard let apiKey = firebaseAPIKey else {
|
|
throw IntegrationBackendError.missingCredential("firebaseAPIKey")
|
|
}
|
|
guard let debugToken = appCheckDebugToken else {
|
|
throw IntegrationBackendError.missingCredential("appCheckDebugToken")
|
|
}
|
|
return {
|
|
let appCheckToken = try await AppCheckTokenCache.shared.token(
|
|
projectID: projectID,
|
|
appID: appID,
|
|
apiKey: apiKey,
|
|
debugToken: debugToken
|
|
)
|
|
return [
|
|
"x-goog-api-key": apiKey,
|
|
"x-firebase-appcheck": appCheckToken,
|
|
]
|
|
}
|
|
}
|
|
}
|
|
|
|
private func resolveProjectID() throws -> String {
|
|
guard let projectID = firebaseProjectID else {
|
|
throw IntegrationBackendError.missingCredential("firebaseProjectID")
|
|
}
|
|
return projectID
|
|
}
|
|
}
|
|
|
|
// MARK: - Backend Errors
|
|
|
|
/// Errors thrown when configuring integration testing backends.
|
|
package enum IntegrationBackendError: Error, LocalizedError, Sendable {
|
|
case missingCredential(String)
|
|
|
|
package var errorDescription: String? {
|
|
switch self {
|
|
case .missingCredential(let name):
|
|
return "Missing required integration test credential: \(name)"
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Testing Traits
|
|
|
|
/// Indicates whether any integration backend (Developer API or Firebase AI Logic) is available.
|
|
package var hasIntegrationTestingBackend: Bool {
|
|
!IntegrationTestingBackend.availableBackends.isEmpty
|
|
}
|
|
|
|
@available(macOS 15.0, iOS 18.0, tvOS 18.0, watchOS 11.0, visionOS 2.0, *)
|
|
extension Trait where Self == Testing.ConditionTrait {
|
|
/// Requires at least one integration backend to be available.
|
|
package static var requireIntegrationTestingBackend: Self {
|
|
.enabled(
|
|
if: hasIntegrationTestingBackend,
|
|
"Requires credentials for Developer API or Firebase AI Logic"
|
|
)
|
|
}
|
|
}
|
|
#endif // canImport(Testing)
|