Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Streamable HTTPCallable functions (Alt. to #14290) #14376

Closed
wants to merge 47 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
231d602
[Infra] Update functions workflow to use macOS 15 for Xcode 16 jobs (…
eBlender Nov 8, 2024
a14d964
Stremable Functions.
eBlender Dec 20, 2024
a92d7c2
Changed return type.
eBlender Dec 20, 2024
10bec1d
Lint test
eBlender Dec 20, 2024
53a2aab
Remove test function
eBlender Dec 20, 2024
758fbed
Remove old test.
eBlender Dec 20, 2024
93b6c8b
Updated function, add full test.
eBlender Dec 27, 2024
a7e8fe8
Update functions
eBlender Jan 2, 2025
6d59fcd
Update FunctionsTests.swift
eBlender Jan 2, 2025
51f02b8
Cleanup HTTPCallable
eBlender Jan 2, 2025
7b61076
Add documentation for processResponseDataForStreamableContent
eBlender Jan 2, 2025
a95449e
Update Functions.swift
eBlender Jan 2, 2025
cdc49ee
Update Functions.swift
eBlender Jan 2, 2025
426b6bc
Update FunctionsTests.swift
eBlender Jan 2, 2025
9cb0a5e
Update and Cleanup
eBlender Jan 3, 2025
ad31052
Update IntegrationTests.swift
eBlender Jan 3, 2025
6ee9000
Clean up
eBlender Jan 3, 2025
1ffe73d
Update check.sh
eBlender Jan 3, 2025
9fcd91e
Bump to Main.
eBlender Jan 6, 2025
177aa8e
Merge branch 'main' into iOS-Stremable-Functions
eBlender Jan 6, 2025
f4d678b
Cleanup
eBlender Jan 6, 2025
74557e7
Merge branch 'iOS-Stremable-Functions' of https://github.com/eBlender…
eBlender Jan 6, 2025
18f748b
Update Functions.swift
eBlender Jan 7, 2025
4f956fb
Lint check
eBlender Jan 7, 2025
4edc0ad
Function concurrency error
eBlender Jan 7, 2025
e50f69c
Update .github/workflows/functions.yml
eBlender Jan 15, 2025
7356cf9
Update FirebaseFunctions/Tests/Unit/FunctionsTests.swift
eBlender Jan 15, 2025
aed47d6
Delete firebase-database-emulator.log
eBlender Jan 15, 2025
f6c6cff
Delete firebase-database-emulator.pid
eBlender Jan 15, 2025
75a7574
Update function error handling.
eBlender Jan 15, 2025
adf7366
Merge branch 'iOS-Stremable-Functions' of https://github.com/eBlender…
eBlender Jan 15, 2025
9ef7411
Update FirebaseFunctions/Tests/Unit/FunctionsTests.swift
eBlender Jan 16, 2025
4ee820e
Update FunctionsTests.swift
eBlender Jan 16, 2025
fd68f01
Merge branch 'iOS-Stremable-Functions' of https://github.com/eBlender…
eBlender Jan 16, 2025
f27bf07
Update FunctionsTests.swift
eBlender Jan 16, 2025
1ffa4f0
Format and refactoring.
eBlender Jan 16, 2025
80f0991
Update FirebaseFunctions/Tests/Unit/FunctionsTests.swift
eBlender Jan 16, 2025
756dc26
Update FirebaseFunctions/Tests/Unit/FunctionsTests.swift
eBlender Jan 16, 2025
f031c1f
Update FirebaseFunctions/Tests/Unit/FunctionsTests.swift
eBlender Jan 16, 2025
0df7f8d
Update FirebaseFunctions/Tests/Unit/FunctionsTests.swift
eBlender Jan 16, 2025
3e325aa
[WIP] Add generic and basic streaming implementation
ncooke3 Jan 23, 2025
95fc340
Merge branch 'main' into iOS-Stremable-Functions
ncooke3 Jan 23, 2025
a712525
Merge branch 'main' into iOS-Stremable-Functions
ncooke3 Jan 23, 2025
7b42c35
Move unit tests
ncooke3 Jan 23, 2025
6dc3959
Merge remote-tracking branch 'origin/main' into iOS-Stremable-Functions
ncooke3 Jan 27, 2025
ca53153
Post main sync checkpoint
ncooke3 Jan 27, 2025
d3e476c
Copy over more structure from vertex
ncooke3 Jan 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions FirebaseFunctions/Sources/Callable+Codable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -159,4 +159,22 @@ public struct Callable<Request: Encodable, Response: Decodable> {
public func callAsFunction(_ data: Request) async throws -> Response {
return try await call(data)
}

// TODO: Look into handling parameter-less functions.
@available(iOS 15, *)
public func stream(_ data: Request) async throws -> AsyncThrowingStream<Response, Error> {
let encoded = try encoder.encode(data)
return AsyncThrowingStream { continuation in
Task {
do {
for try await result in callable.stream(encoded) {
let response = try decoder.decode(Response.self, from: result.data)
continuation.yield(response)
}
} catch {
continuation.finish(throwing: error)
}
}
}
}
}
221 changes: 219 additions & 2 deletions FirebaseFunctions/Sources/Functions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,169 @@
}
}

@available(iOS 15, *)
func stream(at url: URL,
withObject data: Any?,
options: HTTPSCallableOptions?,
timeout: TimeInterval)
-> AsyncThrowingStream<HTTPSCallableResult, Error> {
AsyncThrowingStream { continuation in
// TODO: Vertex prints the curl command. Should this?

Task {
// TODO: This API does not throw. Should the throwing request
// setup be in the stream or one level up?
let urlRequest: URLRequest
do {
let context = try await contextProvider.context(options: options)
urlRequest = try makeRequestForStreamableContent(
url: url,
data: data,
options: options,
timeout: timeout,
context: context
)
// TODO: Address below commented out code.
// // Override normal security rules if this is a local test.
// var configuration = URLSessionConfiguration.default
// if let emulatorOrigin {
// configuration.
// }
} catch {
continuation.finish(throwing: error)
return
}

let stream: URLSession.AsyncBytes

Check failure on line 507 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, watchOS)

'AsyncBytes' is only available in watchOS 8.0 or newer

Check failure on line 507 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, watchOS)

'AsyncBytes' is only available in watchOS 8.0 or newer

Check failure on line 507 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, watchOS)

'AsyncBytes' is only available in watchOS 8.0 or newer

Check failure on line 507 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, watchOS)

'AsyncBytes' is only available in watchOS 8.0 or newer

Check failure on line 507 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, tvOS)

'AsyncBytes' is only available in tvOS 15.0 or newer

Check failure on line 507 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, tvOS)

'AsyncBytes' is only available in tvOS 15.0 or newer

Check failure on line 507 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, tvOS)

'AsyncBytes' is only available in tvOS 15.0 or newer

Check failure on line 507 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, tvOS)

'AsyncBytes' is only available in tvOS 15.0 or newer

Check failure on line 507 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, macOS)

'AsyncBytes' is only available in macOS 12.0 or newer

Check failure on line 507 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, macOS)

'AsyncBytes' is only available in macOS 12.0 or newer

Check failure on line 507 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, tvOS)

'AsyncBytes' is only available in tvOS 15.0 or newer

Check failure on line 507 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, tvOS)

'AsyncBytes' is only available in tvOS 15.0 or newer

Check failure on line 507 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, tvOS)

'AsyncBytes' is only available in tvOS 15.0 or newer

Check failure on line 507 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, tvOS)

'AsyncBytes' is only available in tvOS 15.0 or newer

Check failure on line 507 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, macOS)

'AsyncBytes' is only available in macOS 12.0 or newer

Check failure on line 507 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, macOS)

'AsyncBytes' is only available in macOS 12.0 or newer

Check failure on line 507 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, macOS)

'AsyncBytes' is only available in macOS 12.0 or newer

Check failure on line 507 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, macOS)

'AsyncBytes' is only available in macOS 12.0 or newer

Check failure on line 507 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, watchOS)

'AsyncBytes' is only available in watchOS 8.0 or newer

Check failure on line 507 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, watchOS)

'AsyncBytes' is only available in watchOS 8.0 or newer
let rawResponse: URLResponse
do {
// TODO: Look into injecting URLSession for unit tests.
(stream, rawResponse) = try await URLSession.shared.bytes(for: urlRequest)

Check failure on line 511 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, watchOS)

'bytes(for:delegate:)' is only available in watchOS 8.0 or newer

Check failure on line 511 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, watchOS)

'bytes(for:delegate:)' is only available in watchOS 8.0 or newer

Check failure on line 511 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, tvOS)

'bytes(for:delegate:)' is only available in tvOS 15.0 or newer

Check failure on line 511 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, tvOS)

'bytes(for:delegate:)' is only available in tvOS 15.0 or newer

Check failure on line 511 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, macOS)

'bytes(for:delegate:)' is only available in macOS 12.0 or newer

Check failure on line 511 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, tvOS)

'bytes(for:delegate:)' is only available in tvOS 15.0 or newer

Check failure on line 511 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, tvOS)

'bytes(for:delegate:)' is only available in tvOS 15.0 or newer

Check failure on line 511 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, macOS)

'bytes(for:delegate:)' is only available in macOS 12.0 or newer

Check failure on line 511 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, macOS)

'bytes(for:delegate:)' is only available in macOS 12.0 or newer

Check failure on line 511 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, watchOS)

'bytes(for:delegate:)' is only available in watchOS 8.0 or newer
} catch {
continuation.finish(throwing: error)
return
}

// Verify the status code is 200
guard let response = rawResponse as? HTTPURLResponse else {
continuation.finish(
throwing: FunctionsError(
.internal,
userInfo: [NSLocalizedDescriptionKey: "Response was not an HTTP response."]
)
)
return
}

guard response.statusCode == 200 else {
// TODO: Add test for error case and parse out error.
return
}

for try await line in stream.lines {

Check failure on line 533 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, watchOS)

'lines' is only available in watchOS 8.0 or newer

Check failure on line 533 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, watchOS)

'lines' is only available in watchOS 8.0 or newer

Check failure on line 533 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, tvOS)

'lines' is only available in tvOS 15.0 or newer

Check failure on line 533 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, tvOS)

'lines' is only available in tvOS 15.0 or newer

Check failure on line 533 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, macOS)

'lines' is only available in macOS 12.0 or newer

Check failure on line 533 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, tvOS)

'lines' is only available in tvOS 15.0 or newer

Check failure on line 533 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, tvOS)

'lines' is only available in tvOS 15.0 or newer

Check failure on line 533 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, macOS)

'lines' is only available in macOS 12.0 or newer

Check failure on line 533 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, macOS)

'lines' is only available in macOS 12.0 or newer

Check failure on line 533 in FirebaseFunctions/Sources/Functions.swift

View workflow job for this annotation

GitHub Actions / spm-unit (macos-15, Xcode_16.1, watchOS)

'lines' is only available in watchOS 8.0 or newer
if line.hasPrefix("data:") {
// We can assume 5 characters since it's utf-8 encoded, removing `data:`.
let jsonText = String(line.dropFirst(5))
let data: Data
do {
data = try jsonData(jsonText: jsonText)
} catch {
continuation.finish(throwing: error)
return
}

// Handle the content.
do {
let content = try callableResult(fromResponseData: data)
continuation.yield(content)
} catch {
continuation.finish(throwing: error)
return
}
} else {
// TODO: Throw error with unexpected formatted lines.
}
}

continuation.finish(throwing: nil)
}
}
}

private func jsonData(jsonText: String) throws -> Data {
guard let data = jsonText.data(using: .utf8) else {
throw DecodingError.dataCorrupted(DecodingError.Context(
codingPath: [],
debugDescription: "Could not parse response as UTF8."
))
}
return data
}

@available(iOS 13.0, *)
func callableResultFromResponseAsync(data: Data?,
error: Error?) throws -> AsyncThrowingStream<
HTTPSCallableResult, Error

> {
let processedData =
try processResponseDataForStreamableContent(
from: data,
error: error
)

return processedData
}

private func makeRequestForStreamableContent(url: URL,
data: Any?,
options: HTTPSCallableOptions?,
timeout: TimeInterval,
context: FunctionsContext) throws
-> URLRequest {
var urlRequest = URLRequest(
url: url,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: timeout
)

let data = data ?? NSNull()
let encoded = try serializer.encode(data)
let body = ["data": encoded]
let payload = try JSONSerialization.data(withJSONObject: body, options: [.fragmentsAllowed])
urlRequest.httpBody = payload

// Set the headers for starting a streaming session.
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
urlRequest.setValue("text/event-stream", forHTTPHeaderField: "Accept")
urlRequest.httpMethod = "POST"

if let authToken = context.authToken {
let value = "Bearer \(authToken)"
urlRequest.setValue(value, forHTTPHeaderField: "Authorization")
}

if let fcmToken = context.fcmToken {
urlRequest.setValue(fcmToken, forHTTPHeaderField: Constants.fcmTokenHeader)
}

if options?.requireLimitedUseAppCheckTokens == true {
if let appCheckToken = context.limitedUseAppCheckToken {
urlRequest.setValue(
appCheckToken,
forHTTPHeaderField: Constants.appCheckTokenHeader
)
}
} else if let appCheckToken = context.appCheckToken {
urlRequest.setValue(
appCheckToken,
forHTTPHeaderField: Constants.appCheckTokenHeader
)
}

return urlRequest
}

private func makeFetcher(url: URL,
data: Any?,
options: HTTPSCallableOptions?,
Expand Down Expand Up @@ -556,6 +719,58 @@
return data
}

@available(iOS 13, macCatalyst 13, macOS 10.15, tvOS 13, watchOS 7, *)
private func processResponseDataForStreamableContent(from data: Data?,
error: Error?) throws
-> AsyncThrowingStream<
HTTPSCallableResult,
Error
> {
return AsyncThrowingStream { continuation in
Task {
var resultArray = [String]()
do {
if let error = error {
throw error
}

guard let data = data else {
throw NSError(domain: FunctionsErrorDomain.description, code: -1, userInfo: nil)
}

if let dataChunk = String(data: data, encoding: .utf8) {
// We remove the "data :" field so it can be safely parsed to Json.
let dataChunkToJson = dataChunk.split(separator: "\n").map {
String($0.dropFirst(6))
}
resultArray.append(contentsOf: dataChunkToJson)
} else {
throw NSError(domain: FunctionsErrorDomain.description, code: -1, userInfo: nil)
}

for dataChunk in resultArray {
let json = try callableResult(
fromResponseData: dataChunk.data(
using: .utf8,
allowLossyConversion: true
) ?? Data()
)
continuation.yield(HTTPSCallableResult(data: json.data))
}

continuation.onTermination = { @Sendable _ in
// Callback for cancelling the stream
continuation.finish()
}
// Close the stream once it's done
continuation.finish()
} catch {
continuation.finish(throwing: error)
}
}
}
}

private func responseDataJSON(from data: Data) throws -> Any {
let responseJSONObject = try JSONSerialization.jsonObject(with: data)

Expand All @@ -564,8 +779,10 @@
throw FunctionsError(.internal, userInfo: userInfo)
}

// `result` is checked for backwards compatibility:
guard let dataJSON = responseJSON["data"] ?? responseJSON["result"] else {
// `result` is checked for backwards compatibility,
// `message` is checked for StramableContent:
guard let dataJSON = responseJSON["data"] ?? responseJSON["result"] ?? responseJSON["message"]
else {
let userInfo = [NSLocalizedDescriptionKey: "Response is missing data field."]
throw FunctionsError(.internal, userInfo: userInfo)
}
Expand Down
7 changes: 6 additions & 1 deletion FirebaseFunctions/Sources/HTTPSCallable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ open class HTTPSCallable: NSObject {
// The functions client to use for making calls.
private let functions: Functions

private let url: URL
let url: URL

private let options: HTTPSCallableOptions?

Expand Down Expand Up @@ -143,4 +143,9 @@ open class HTTPSCallable: NSObject {
try await functions
.callFunction(at: url, withObject: data, options: options, timeout: timeoutInterval)
}

@available(iOS 15, *)
func stream(_ data: Any? = nil) -> AsyncThrowingStream<HTTPSCallableResult, Error> {
functions.stream(at: url, withObject: data, options: options, timeout: timeoutInterval)
}
}
72 changes: 72 additions & 0 deletions FirebaseFunctions/Tests/Integration/IntegrationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -597,13 +597,13 @@
_ = try result.get()
} catch {
let error = error as NSError
XCTAssertEqual(FunctionsErrorCode.invalidArgument.rawValue, error.code)

Check failure on line 600 in FirebaseFunctions/Tests/Integration/IntegrationTests.swift

View workflow job for this annotation

GitHub Actions / spm-integration (macos-14, Xcode_15.4)

testHttpError, XCTAssertEqual failed: ("3") is not equal to ("13")
expectation.fulfill()
return
}
XCTFail("Failed to throw error for missing result")
}
waitForExpectations(timeout: 5)

Check failure on line 606 in FirebaseFunctions/Tests/Integration/IntegrationTests.swift

View workflow job for this annotation

GitHub Actions / spm-integration (macos-14, Xcode_15.4)

testHttpError, Asynchronous wait failed: Exceeded timeout of 5 seconds, with unfulfilled expectations: "testHttpError()".
}
}

Expand Down Expand Up @@ -866,6 +866,78 @@
XCTAssertEqual(response, expected)
}
}

@available(iOS 15, *)
func testGenerateStreamContent() async throws {
let options = HTTPSCallableOptions(requireLimitedUseAppCheckTokens: true)

let input: [String: Any] = ["data": "Why is the sky blue"]

let stream = functions.stream(
at: emulatorURL("genStream"),
withObject: input,
options: options,
timeout: 4.0
)
let result = try await response(from: stream)
XCTAssertEqual(
result,
[
"chunk hello",
"chunk world",
"chunk this",
"chunk is",
"chunk cool",
"hello world this is cool",
]
)
}

@available(iOS 15, *)
func testGenerateStreamContentCanceled() async {
let options = HTTPSCallableOptions(requireLimitedUseAppCheckTokens: true)
let input: [String: Any] = ["data": "Why is the sky blue"]

let task = Task.detached { [self] in
let stream = functions.stream(
at: emulatorURL("genStream"),
withObject: input,
options: options,
timeout: 4.0
)

let result = try await response(from: stream)
// Since we cancel the call we are expecting an empty array.
XCTAssertEqual(
result,
[]
)
}
// We cancel the task and we expect a null response even if the stream was initiated.
task.cancel()
let respone = await task.result
XCTAssertNotNil(respone)
}

private func response(from stream: AsyncThrowingStream<HTTPSCallableResult,
any Error>) async throws -> [String] {
var response = [String]()
for try await result in stream {
// First chunk of the stream comes as NSDictionary
if let dataChunk = result.data as? NSDictionary {
for (key, value) in dataChunk {
response.append("\(key) \(value)")
}
} else {
// Last chunk is the concatenated result so we have to parse it as String else will
// fail.
if let dataString = result.data as? String {
response.append(dataString)
}
}
}
return response
}
}

private class AuthTokenProvider: AuthInterop {
Expand Down
8 changes: 7 additions & 1 deletion FirebaseFunctions/Tests/Unit/FunctionsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ import FirebaseCore
import GTMSessionFetcherCore
#endif

import SharedTestUtilities
#if SWIFT_PACKAGE
import SharedTestUtilities
#endif
import XCTest

class FunctionsTests: XCTestCase {
Expand Down Expand Up @@ -358,4 +360,8 @@ class FunctionsTests: XCTestCase {
}
waitForExpectations(timeout: 1.5)
}

// TODO: Implement unit test variants.
func testGenerateStreamContent() async throws {}
func testGenerateStreamContentCanceled() async {}
}
Loading