Skip to content

Commit 83ee78a

Browse files
authored
[Firebase AI] Add Candidate decoding tests for urlMetadata (#15348)
1 parent 7df2377 commit 83ee78a

File tree

4 files changed

+72
-77
lines changed

4 files changed

+72
-77
lines changed

FirebaseAI/Tests/Unit/GenerativeModelGoogleAITests.swift

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -384,27 +384,6 @@ final class GenerativeModelGoogleAITests: XCTestCase {
384384
XCTAssertEqual(errorURLMetadata.retrievalStatus, .error)
385385
}
386386

387-
func testGenerateContent_success_urlContext_emptyURLMetadata() async throws {
388-
let json = """
389-
{
390-
"candidates": [
391-
{
392-
"content": { "role": "model", "parts": [ { "text": "Some text." } ] },
393-
"finishReason": "STOP",
394-
"urlContextMetadata": { "urlMetadata": [] }
395-
}
396-
]
397-
}
398-
"""
399-
MockURLProtocol.requestHandler = nil
400-
MockURLProtocol.dataRequestHandler = try GenerativeModelTestUtil.httpRequestHandler(json: json)
401-
402-
let response = try await model.generateContent(testPrompt)
403-
404-
let candidate = try XCTUnwrap(response.candidates.first)
405-
XCTAssertNil(candidate.urlContextMetadata)
406-
}
407-
408387
func testGenerateContent_failure_invalidAPIKey() async throws {
409388
let expectedStatusCode = 400
410389
MockURLProtocol.requestHandler = try GenerativeModelTestUtil.httpRequestHandler(

FirebaseAI/Tests/Unit/GenerativeModelVertexAITests.swift

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -556,27 +556,6 @@ final class GenerativeModelVertexAITests: XCTestCase {
556556
XCTAssertEqual(urlMetadata.retrievalStatus, .error)
557557
}
558558

559-
func testGenerateContent_success_urlContext_emptyURLMetadata() async throws {
560-
let json = """
561-
{
562-
"candidates": [
563-
{
564-
"content": { "role": "model", "parts": [ { "text": "Some text." } ] },
565-
"finishReason": "STOP",
566-
"urlContextMetadata": { "urlMetadata": [] }
567-
}
568-
]
569-
}
570-
"""
571-
MockURLProtocol.requestHandler = nil
572-
MockURLProtocol.dataRequestHandler = try GenerativeModelTestUtil.httpRequestHandler(json: json)
573-
574-
let response = try await model.generateContent(testPrompt)
575-
576-
let candidate = try XCTUnwrap(response.candidates.first)
577-
XCTAssertNil(candidate.urlContextMetadata)
578-
}
579-
580559
func testGenerateContent_success_image_invalidSafetyRatingsIgnored() async throws {
581560
MockURLProtocol.requestHandler = try GenerativeModelTestUtil.httpRequestHandler(
582561
forResource: "unary-success-image-invalid-safety-ratings",

FirebaseAI/Tests/Unit/MockURLProtocol.swift

Lines changed: 21 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,6 @@ class MockURLProtocol: URLProtocol, @unchecked Sendable {
2222
AsyncLineSequence<URL.AsyncBytes>?
2323
))?
2424

25-
nonisolated(unsafe) static var dataRequestHandler: ((URLRequest) throws -> (
26-
URLResponse,
27-
Data?
28-
))?
29-
3025
override class func canInit(with request: URLRequest) -> Bool {
3126
#if os(watchOS)
3227
print("MockURLProtocol cannot be used on watchOS.")
@@ -43,40 +38,31 @@ class MockURLProtocol: URLProtocol, @unchecked Sendable {
4338
fatalError("`client` is nil.")
4439
}
4540

46-
if let requestHandler = MockURLProtocol.requestHandler {
47-
Task {
48-
let (response, stream) = try requestHandler(self.request)
49-
client.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
50-
if let stream = stream {
51-
do {
52-
for try await line in stream {
53-
guard let data = line.data(using: .utf8) else {
54-
fatalError("Failed to convert \"\(line)\" to UTF8 data.")
55-
}
56-
client.urlProtocol(self, didLoad: data)
57-
// Add a newline character since AsyncLineSequence strips them when reading line by
58-
// line;
59-
// without the following, the whole file is delivered as a single line.
60-
client.urlProtocol(self, didLoad: "\n".data(using: .utf8)!)
41+
guard let requestHandler = MockURLProtocol.requestHandler else {
42+
fatalError("No request handler set.")
43+
}
44+
45+
Task {
46+
let (response, stream) = try requestHandler(self.request)
47+
client.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
48+
if let stream = stream {
49+
do {
50+
for try await line in stream {
51+
guard let data = line.data(using: .utf8) else {
52+
fatalError("Failed to convert \"\(line)\" to UTF8 data.")
6153
}
62-
} catch {
63-
client.urlProtocol(self, didFailWithError: error)
64-
XCTFail("Unexpected failure reading lines from stream: \(error.localizedDescription)")
54+
client.urlProtocol(self, didLoad: data)
55+
// Add a newline character since AsyncLineSequence strips them when reading line by
56+
// line;
57+
// without the following, the whole file is delivered as a single line.
58+
client.urlProtocol(self, didLoad: "\n".data(using: .utf8)!)
6559
}
60+
} catch {
61+
client.urlProtocol(self, didFailWithError: error)
62+
XCTFail("Unexpected failure reading lines from stream: \(error.localizedDescription)")
6663
}
67-
client.urlProtocolDidFinishLoading(self)
68-
}
69-
} else if let dataRequestHandler = MockURLProtocol.dataRequestHandler {
70-
Task {
71-
let (response, data) = try dataRequestHandler(self.request)
72-
client.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
73-
if let data = data {
74-
client.urlProtocol(self, didLoad: data)
75-
}
76-
client.urlProtocolDidFinishLoading(self)
7764
}
78-
} else {
79-
fatalError("No request handler set.")
65+
client.urlProtocolDidFinishLoading(self)
8066
}
8167
}
8268

FirebaseAI/Tests/Unit/Types/GenerateContentResponseTests.swift

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import XCTest
1717

1818
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
1919
final class GenerateContentResponseTests: XCTestCase {
20+
let jsonDecoder = JSONDecoder()
21+
2022
// MARK: - GenerateContentResponse Computed Properties
2123

2224
func testGenerateContentResponse_inlineDataParts_success() throws {
@@ -106,4 +108,53 @@ final class GenerateContentResponseTests: XCTestCase {
106108
"functionCalls should be empty when there are no candidates."
107109
)
108110
}
111+
112+
// MARK: - Decoding Tests
113+
114+
func testDecodeCandidate_emptyURLMetadata_urlContextMetadataIsNil() throws {
115+
let json = """
116+
{
117+
"content": { "role": "model", "parts": [ { "text": "Some text." } ] },
118+
"finishReason": "STOP",
119+
"urlContextMetadata": { "urlMetadata": [] }
120+
}
121+
"""
122+
let jsonData = try XCTUnwrap(json.data(using: .utf8))
123+
124+
let candidate = try jsonDecoder.decode(Candidate.self, from: jsonData)
125+
126+
XCTAssertNil(
127+
candidate.urlContextMetadata,
128+
"urlContextMetadata should be nil if the `urlMetadata` array is empty in the candidate."
129+
)
130+
XCTAssertEqual(candidate.content.role, "model")
131+
let part = try XCTUnwrap(candidate.content.parts.first)
132+
let textPart = try XCTUnwrap(part as? TextPart)
133+
XCTAssertEqual(textPart.text, "Some text.")
134+
XCTAssertFalse(textPart.isThought)
135+
XCTAssertEqual(candidate.finishReason, .stop)
136+
}
137+
138+
func testDecodeCandidate_missingURLMetadata_urlContextMetadataIsNil() throws {
139+
let json = """
140+
{
141+
"content": { "role": "model", "parts": [ { "text": "Some text." } ] },
142+
"finishReason": "STOP"
143+
}
144+
"""
145+
let jsonData = try XCTUnwrap(json.data(using: .utf8))
146+
147+
let candidate = try jsonDecoder.decode(Candidate.self, from: jsonData)
148+
149+
XCTAssertNil(
150+
candidate.urlContextMetadata,
151+
"urlContextMetadata should be nil if `urlMetadata` is not provided in the candidate."
152+
)
153+
XCTAssertEqual(candidate.content.role, "model")
154+
let part = try XCTUnwrap(candidate.content.parts.first)
155+
let textPart = try XCTUnwrap(part as? TextPart)
156+
XCTAssertEqual(textPart.text, "Some text.")
157+
XCTAssertFalse(textPart.isThought)
158+
XCTAssertEqual(candidate.finishReason, .stop)
159+
}
109160
}

0 commit comments

Comments
 (0)