diff --git a/FirebaseVertexAI/Tests/Unit/ChatTests.swift b/FirebaseVertexAI/Tests/Unit/ChatTests.swift index 95ce8e7e43d..1c4988faf7c 100644 --- a/FirebaseVertexAI/Tests/Unit/ChatTests.swift +++ b/FirebaseVertexAI/Tests/Unit/ChatTests.swift @@ -32,11 +32,7 @@ final class ChatTests: XCTestCase { } func testMergingText() async throws { - #if SWIFT_PACKAGE - let bundle = Bundle.module - #else // SWIFT_PACKAGE - let bundle = Bundle(for: Self.self) - #endif // SWIFT_PACKAGE + let bundle = BundleTestUtil.bundle() let fileURL = try XCTUnwrap(bundle.url( forResource: "streaming-success-basic-reply-parts", withExtension: "txt" diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift index f9035949f1a..dc9acd02d55 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift @@ -1446,11 +1446,7 @@ final class GenerativeModelTests: XCTestCase { #if os(watchOS) throw XCTSkip("Custom URL protocols are unsupported in watchOS 2 and later.") #endif // os(watchOS) - #if SWIFT_PACKAGE - let bundle = Bundle.module - #else // SWIFT_PACKAGE - let bundle = Bundle(for: Self.self) - #endif // SWIFT_PACKAGE + let bundle = BundleTestUtil.bundle() let fileURL = try XCTUnwrap(bundle.url(forResource: name, withExtension: ext)) return { request in let requestURL = try XCTUnwrap(request.url) diff --git a/FirebaseVertexAI/Tests/Unit/Snippets/FirebaseAppSnippetsUtil.swift b/FirebaseVertexAI/Tests/Unit/Snippets/FirebaseAppSnippetsUtil.swift index 013b0dcab6d..f463fbda188 100644 --- a/FirebaseVertexAI/Tests/Unit/Snippets/FirebaseAppSnippetsUtil.swift +++ b/FirebaseVertexAI/Tests/Unit/Snippets/FirebaseAppSnippetsUtil.swift @@ -17,30 +17,41 @@ import Foundation import XCTest extension FirebaseApp { - static let projectIDEnvVar = "PROJECT_ID" - static let appIDEnvVar = "APP_ID" - static let apiKeyEnvVar = "API_KEY" - - static func configureForSnippets() throws { - let environment = ProcessInfo.processInfo.environment - guard let projectID = environment[projectIDEnvVar] else { - throw XCTSkip("No Firebase Project ID specified in environment variable \(projectIDEnvVar).") - } - guard let appID = environment[appIDEnvVar] else { - throw XCTSkip("No Google App ID specified in environment variable \(appIDEnvVar).") + /// Configures the default `FirebaseApp` for use in snippets tests. + /// + /// Uses a `GoogleService-Info.plist` file from the + /// [`Resources`](https://github.com/firebase/firebase-ios-sdk/tree/main/FirebaseVertexAI/Tests/Unit/Resources) + /// directory. + /// + /// > Note: This is typically called in a snippet test's set up; overriding + /// > `setUpWithError() throws` works well since it supports throwing errors. + static func configureDefaultAppForSnippets() throws { + guard let plistPath = BundleTestUtil.bundle().path( + forResource: "GoogleService-Info", + ofType: "plist" + ) else { + throw XCTSkip("No GoogleService-Info.plist found in FirebaseVertexAI/Tests/Unit/Resources.") } - guard let apiKey = environment[apiKeyEnvVar] else { - throw XCTSkip("No API key specified in environment variable \(apiKeyEnvVar).") - } - - let options = FirebaseOptions(googleAppID: appID, gcmSenderID: "") - options.projectID = projectID - options.apiKey = apiKey + let options = try XCTUnwrap(FirebaseOptions(contentsOfFile: plistPath)) FirebaseApp.configure(options: options) + guard FirebaseApp.isDefaultAppConfigured() else { XCTFail("Default Firebase app not configured.") return } } + + /// Deletes the default `FirebaseApp` if configured. + /// + /// > Note: This is typically called in a snippet test's tear down; overriding + /// > `tearDown() async throws` works well since deletion is asynchronous. + static func deleteDefaultAppForSnippets() async { + // Checking if `isDefaultAppConfigured()` before calling `FirebaseApp.app()` suppresses a log + // message that "The default Firebase app has not yet been configured." during `tearDown` when + // the tests are skipped. This reduces extraneous noise in the test logs. + if FirebaseApp.isDefaultAppConfigured(), let app = FirebaseApp.app() { + await app.delete() + } + } } diff --git a/FirebaseVertexAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift b/FirebaseVertexAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift index 60d4438cad5..492574dc11d 100644 --- a/FirebaseVertexAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift +++ b/FirebaseVertexAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift @@ -16,16 +16,17 @@ import FirebaseCore import FirebaseVertexAI import XCTest +// These snippet tests are intentionally skipped in CI jobs; see the README file in this directory +// for instructions on running them manually. + @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class FunctionCallingSnippets: XCTestCase { override func setUpWithError() throws { - try FirebaseApp.configureForSnippets() + try FirebaseApp.configureDefaultAppForSnippets() } override func tearDown() async throws { - if let app = FirebaseApp.app() { - await app.delete() - } + await FirebaseApp.deleteDefaultAppForSnippets() } func testFunctionCalling() async throws { diff --git a/FirebaseVertexAI/Tests/Unit/Snippets/README.md b/FirebaseVertexAI/Tests/Unit/Snippets/README.md new file mode 100644 index 00000000000..8d03458c456 --- /dev/null +++ b/FirebaseVertexAI/Tests/Unit/Snippets/README.md @@ -0,0 +1,10 @@ +# Vertex AI in Firebase Code Snippet Tests + +These "tests" are for verifying that the code snippets provided in our +documentation continue to compile. They are intentionally skipped in CI but can +be manually run to verify expected behavior / outputs. + +To run the tests, place a valid `GoogleService-Info.plist` file in the +[`FirebaseVertexAI/Tests/Unit/Resources`](https://github.com/firebase/firebase-ios-sdk/tree/main/FirebaseVertexAI/Tests/Unit/Resources) +folder. They may then be invoked individually or alongside the rest of the unit +tests in Xcode. diff --git a/FirebaseVertexAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift b/FirebaseVertexAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift index 4a1046083a2..1ad137188c5 100644 --- a/FirebaseVertexAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift +++ b/FirebaseVertexAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift @@ -16,16 +16,17 @@ import FirebaseCore import FirebaseVertexAI import XCTest +// These snippet tests are intentionally skipped in CI jobs; see the README file in this directory +// for instructions on running them manually. + @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class StructuredOutputSnippets: XCTestCase { override func setUpWithError() throws { - try FirebaseApp.configureForSnippets() + try FirebaseApp.configureDefaultAppForSnippets() } override func tearDown() async throws { - if let app = FirebaseApp.app() { - await app.delete() - } + await FirebaseApp.deleteDefaultAppForSnippets() } func testStructuredOutputJSONBasic() async throws { diff --git a/FirebaseVertexAI/Tests/Unit/TestUtilities/BundleTestUtil.swift b/FirebaseVertexAI/Tests/Unit/TestUtilities/BundleTestUtil.swift new file mode 100644 index 00000000000..272be41c1e4 --- /dev/null +++ b/FirebaseVertexAI/Tests/Unit/TestUtilities/BundleTestUtil.swift @@ -0,0 +1,31 @@ +// Copyright 2024 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 + +/// `Bundle` test utilities. +final class BundleTestUtil { + /// Returns the `Bundle` for the test module or target containing the file. + /// + /// This abstracts away the `Bundle` differences between SPM and CocoaPods tests. + static func bundle() -> Bundle { + #if SWIFT_PACKAGE + return Bundle.module + #else // SWIFT_PACKAGE + return Bundle(for: Self.self) + #endif // SWIFT_PACKAGE + } + + private init() {} +}