Skip to content

Commit 5366717

Browse files
authored
[Vertex AI] Add integration tests for App Check failure (#14008)
1 parent 33a44a9 commit 5366717

File tree

5 files changed

+133
-5
lines changed

5 files changed

+133
-5
lines changed

FirebaseVertexAI/Tests/TestApp/Sources/TestApp.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import SwiftUI
1919
@main
2020
struct TestApp: App {
2121
init() {
22-
AppCheck.setAppCheckProviderFactory(AppCheckDebugProviderFactory())
22+
AppCheck.setAppCheckProviderFactory(TestAppCheckProviderFactory())
2323
FirebaseApp.configure()
2424
}
2525

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import FirebaseAppCheck
16+
import FirebaseCore
17+
import Foundation
18+
19+
/// An `AppCheckProviderFactory` for the Test App.
20+
///
21+
/// Defaults to the `AppCheckDebugProvider` unless the `FirebaseApp` `name` contains
22+
/// ``notConfiguredName``, in which case App Check is not configured; this facilitates integration
23+
/// testing of App Check failure cases.
24+
public class TestAppCheckProviderFactory: NSObject, AppCheckProviderFactory {
25+
/// The name, or a substring of the name, of Firebase apps where App Check is not configured.
26+
public static let notConfiguredName = "app-check-not-configured"
27+
28+
/// Returns the `AppCheckDebugProvider` unless `app.name` contains ``notConfiguredName``.
29+
public func createProvider(with app: FirebaseApp) -> (any AppCheckProvider)? {
30+
if app.name.contains(TestAppCheckProviderFactory.notConfiguredName) {
31+
return nil
32+
}
33+
34+
return AppCheckDebugProvider(app: app)
35+
}
36+
}

FirebaseVertexAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ import FirebaseAuth
1616
import FirebaseCore
1717
import FirebaseStorage
1818
import FirebaseVertexAI
19+
import VertexAITestApp
1920
import XCTest
2021

21-
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
2222
final class IntegrationTests: XCTestCase {
2323
// Set temperature, topP and topK to lowest allowed values to make responses more deterministic.
2424
let generationConfig = GenerationConfig(
@@ -75,6 +75,21 @@ final class IntegrationTests: XCTestCase {
7575
XCTAssertEqual(text, "Mountain View")
7676
}
7777

78+
func testGenerateContent_appCheckNotConfigured_shouldFail() async throws {
79+
let app = try FirebaseApp.defaultNamedCopy(name: TestAppCheckProviderFactory.notConfiguredName)
80+
addTeardownBlock { await app.delete() }
81+
let vertex = VertexAI.vertexAI(app: app)
82+
let model = vertex.generativeModel(modelName: "gemini-1.5-flash")
83+
let prompt = "Where is Google headquarters located? Answer with the city name only."
84+
85+
do {
86+
_ = try await model.generateContent(prompt)
87+
XCTFail("Expected a Firebase App Check error; none thrown.")
88+
} catch let GenerateContentError.internalError(error) {
89+
XCTAssertTrue(String(describing: error).contains("Firebase App Check token is invalid"))
90+
}
91+
}
92+
7893
// MARK: - Count Tokens
7994

8095
func testCountTokens_text() async throws {
@@ -205,6 +220,21 @@ final class IntegrationTests: XCTestCase {
205220
XCTAssertEqual(response.totalTokens, 34)
206221
XCTAssertEqual(response.totalBillableCharacters, 59)
207222
}
223+
224+
func testCountTokens_appCheckNotConfigured_shouldFail() async throws {
225+
let app = try FirebaseApp.defaultNamedCopy(name: TestAppCheckProviderFactory.notConfiguredName)
226+
addTeardownBlock { await app.delete() }
227+
let vertex = VertexAI.vertexAI(app: app)
228+
let model = vertex.generativeModel(modelName: "gemini-1.5-flash")
229+
let prompt = "Why is the sky blue?"
230+
231+
do {
232+
_ = try await model.countTokens(prompt)
233+
XCTFail("Expected a Firebase App Check error; none thrown.")
234+
} catch {
235+
XCTAssertTrue(String(describing: error).contains("Firebase App Check token is invalid"))
236+
}
237+
}
208238
}
209239

210240
extension StorageReference {
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import FirebaseCore
16+
17+
extension FirebaseApp {
18+
/// Configures another `FirebaseApp` with the specified `name` and the same `FirebaseOptions`.
19+
func namedCopy(name: String) throws -> FirebaseApp {
20+
FirebaseApp.configure(name: name, options: options)
21+
guard let app = FirebaseApp.app(name: name) else {
22+
throw AppNotFound(name: name)
23+
}
24+
return app
25+
}
26+
27+
/// Configures an app with the specified `name` and the same `FirebaseOptions` as the default app.
28+
static func defaultNamedCopy(name: String) throws -> FirebaseApp {
29+
guard FirebaseApp.isDefaultAppConfigured(), let defaultApp = FirebaseApp.app() else {
30+
throw DefaultAppNotConfigured()
31+
}
32+
return try defaultApp.namedCopy(name: name)
33+
}
34+
35+
struct AppNotFound: Error {
36+
let name: String
37+
}
38+
39+
struct DefaultAppNotConfigured: Error {}
40+
}

FirebaseVertexAI/Tests/TestApp/VertexAITestApp.xcodeproj/project.pbxproj

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
8692F29A2CC9477800539E8F /* FirebaseAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 8692F2992CC9477800539E8F /* FirebaseAuth */; };
1919
8692F29C2CC9477800539E8F /* FirebaseStorage in Frameworks */ = {isa = PBXBuildFile; productRef = 8692F29B2CC9477800539E8F /* FirebaseStorage */; };
2020
8692F29E2CC9477800539E8F /* FirebaseVertexAI in Frameworks */ = {isa = PBXBuildFile; productRef = 8692F29D2CC9477800539E8F /* FirebaseVertexAI */; };
21+
8698D7462CD3CF3600ABA833 /* FirebaseAppTestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8698D7452CD3CF2F00ABA833 /* FirebaseAppTestUtils.swift */; };
22+
8698D7482CD4332B00ABA833 /* TestAppCheckProviderFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8698D7472CD4332B00ABA833 /* TestAppCheckProviderFactory.swift */; };
2123
/* End PBXBuildFile section */
2224

2325
/* Begin PBXContainerItemProxy section */
@@ -41,6 +43,8 @@
4143
868A7C502CCC263300E449DD /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
4244
868A7C532CCC26B500E449DD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
4345
868A7C552CCC271300E449DD /* TestApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TestApp.entitlements; sourceTree = "<group>"; };
46+
8698D7452CD3CF2F00ABA833 /* FirebaseAppTestUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseAppTestUtils.swift; sourceTree = "<group>"; };
47+
8698D7472CD4332B00ABA833 /* TestAppCheckProviderFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestAppCheckProviderFactory.swift; sourceTree = "<group>"; };
4448
/* End PBXFileReference section */
4549

4650
/* Begin PBXFrameworksBuildPhase section */
@@ -107,6 +111,7 @@
107111
isa = PBXGroup;
108112
children = (
109113
8661385B2CC943DD00F4B78E /* TestApp.swift */,
114+
8698D7472CD4332B00ABA833 /* TestAppCheckProviderFactory.swift */,
110115
8661385D2CC943DD00F4B78E /* ContentView.swift */,
111116
);
112117
path = Sources;
@@ -125,10 +130,19 @@
125130
isa = PBXGroup;
126131
children = (
127132
868A7C572CCC27AF00E449DD /* Integration */,
133+
8698D7442CD3CEF700ABA833 /* Utilities */,
128134
);
129135
path = Tests;
130136
sourceTree = "<group>";
131137
};
138+
8698D7442CD3CEF700ABA833 /* Utilities */ = {
139+
isa = PBXGroup;
140+
children = (
141+
8698D7452CD3CF2F00ABA833 /* FirebaseAppTestUtils.swift */,
142+
);
143+
path = Utilities;
144+
sourceTree = "<group>";
145+
};
132146
/* End PBXGroup section */
133147

134148
/* Begin PBXNativeTarget section */
@@ -181,7 +195,7 @@
181195
attributes = {
182196
BuildIndependentTargetsInParallel = 1;
183197
LastSwiftUpdateCheck = 1520;
184-
LastUpgradeCheck = 1520;
198+
LastUpgradeCheck = 1600;
185199
TargetAttributes = {
186200
866138572CC943DD00F4B78E = {
187201
CreatedOnToolsVersion = 15.2;
@@ -241,13 +255,15 @@
241255
files = (
242256
8661385E2CC943DD00F4B78E /* ContentView.swift in Sources */,
243257
8661385C2CC943DD00F4B78E /* TestApp.swift in Sources */,
258+
8698D7482CD4332B00ABA833 /* TestAppCheckProviderFactory.swift in Sources */,
244259
);
245260
runOnlyForDeploymentPostprocessing = 0;
246261
};
247262
866138652CC943DE00F4B78E /* Sources */ = {
248263
isa = PBXSourcesBuildPhase;
249264
buildActionMask = 2147483647;
250265
files = (
266+
8698D7462CD3CF3600ABA833 /* FirebaseAppTestUtils.swift in Sources */,
251267
868A7C4F2CCC229F00E449DD /* Credentials.swift in Sources */,
252268
8661386E2CC943DE00F4B78E /* IntegrationTests.swift in Sources */,
253269
);
@@ -298,6 +314,7 @@
298314
CLANG_WARN_UNREACHABLE_CODE = YES;
299315
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
300316
COPY_PHASE_STRIP = NO;
317+
DEAD_CODE_STRIPPING = YES;
301318
DEBUG_INFORMATION_FORMAT = dwarf;
302319
ENABLE_STRICT_OBJC_MSGSEND = YES;
303320
ENABLE_TESTABILITY = YES;
@@ -359,6 +376,7 @@
359376
CLANG_WARN_UNREACHABLE_CODE = YES;
360377
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
361378
COPY_PHASE_STRIP = NO;
379+
DEAD_CODE_STRIPPING = YES;
362380
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
363381
ENABLE_NS_ASSERTIONS = NO;
364382
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -386,6 +404,7 @@
386404
CODE_SIGN_ENTITLEMENTS = Resources/TestApp.entitlements;
387405
CODE_SIGN_STYLE = Automatic;
388406
CURRENT_PROJECT_VERSION = 1;
407+
DEAD_CODE_STRIPPING = YES;
389408
DEVELOPMENT_ASSET_PATHS = "\"Resources/Preview Content\"";
390409
ENABLE_PREVIEWS = YES;
391410
GENERATE_INFOPLIST_FILE = YES;
@@ -405,6 +424,7 @@
405424
MACOSX_DEPLOYMENT_TARGET = 12.0;
406425
MARKETING_VERSION = 1.0;
407426
PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.VertexAITestApp;
427+
PRODUCT_MODULE_NAME = VertexAITestApp;
408428
PRODUCT_NAME = "$(TARGET_NAME)";
409429
SDKROOT = auto;
410430
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
@@ -422,6 +442,7 @@
422442
CODE_SIGN_ENTITLEMENTS = Resources/TestApp.entitlements;
423443
CODE_SIGN_STYLE = Automatic;
424444
CURRENT_PROJECT_VERSION = 1;
445+
DEAD_CODE_STRIPPING = YES;
425446
DEVELOPMENT_ASSET_PATHS = "\"Resources/Preview Content\"";
426447
ENABLE_PREVIEWS = YES;
427448
GENERATE_INFOPLIST_FILE = YES;
@@ -441,6 +462,7 @@
441462
MACOSX_DEPLOYMENT_TARGET = 12.0;
442463
MARKETING_VERSION = 1.0;
443464
PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.VertexAITestApp;
465+
PRODUCT_MODULE_NAME = VertexAITestApp;
444466
PRODUCT_NAME = "$(TARGET_NAME)";
445467
SDKROOT = auto;
446468
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
@@ -453,10 +475,10 @@
453475
866138812CC943DE00F4B78E /* Debug */ = {
454476
isa = XCBuildConfiguration;
455477
buildSettings = {
456-
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
457478
BUNDLE_LOADER = "$(TEST_HOST)";
458479
CODE_SIGN_STYLE = Automatic;
459480
CURRENT_PROJECT_VERSION = 1;
481+
DEAD_CODE_STRIPPING = YES;
460482
GENERATE_INFOPLIST_FILE = YES;
461483
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
462484
MACOSX_DEPLOYMENT_TARGET = 12.0;
@@ -475,10 +497,10 @@
475497
866138822CC943DE00F4B78E /* Release */ = {
476498
isa = XCBuildConfiguration;
477499
buildSettings = {
478-
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
479500
BUNDLE_LOADER = "$(TEST_HOST)";
480501
CODE_SIGN_STYLE = Automatic;
481502
CURRENT_PROJECT_VERSION = 1;
503+
DEAD_CODE_STRIPPING = YES;
482504
GENERATE_INFOPLIST_FILE = YES;
483505
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
484506
MACOSX_DEPLOYMENT_TARGET = 12.0;

0 commit comments

Comments
 (0)