diff --git a/.github/workflows/combine.yml b/.github/workflows/combine.yml index 158d266d73..012ad39566 100644 --- a/.github/workflows/combine.yml +++ b/.github/workflows/combine.yml @@ -20,8 +20,9 @@ on: # Combine sources - 'FirebaseCombineSwift/**' - # Podspec + # Podspecs - 'FirebaseCombineSwift.podspec' + - 'FirebaseFunctionsTestingSupport.podspec' # This workflow - '.github/workflows/combine.yml' diff --git a/.github/workflows/functions.yml b/.github/workflows/functions.yml index d447c850ef..b72276ffa0 100644 --- a/.github/workflows/functions.yml +++ b/.github/workflows/functions.yml @@ -25,7 +25,7 @@ jobs: runs-on: macos-11 strategy: matrix: - target: [ios, tvos, macos] + target: [ios, tvos, macos, watchos] steps: - uses: actions/checkout@v2 - name: Setup Bundler diff --git a/FirebaseFunctions.podspec b/FirebaseFunctions.podspec index df2f4f2241..d592bb4dbe 100644 --- a/FirebaseFunctions.podspec +++ b/FirebaseFunctions.podspec @@ -54,8 +54,19 @@ Cloud Functions for Firebase. s.test_spec 'integration' do |int_tests| int_tests.scheme = { :code_coverage => true } int_tests.source_files = 'FirebaseFunctions/Tests/Integration/*.[mh]', - 'FirebaseFunctions/Tests/Integration/*.plist', 'SharedTestUtilities/FIRAuthInteropFake*', 'SharedTestUtilities/FIRMessagingInteropFake*' end + + # Uncomment to use pod gen to run the Swift Integration tests. This can't be + # committed because of the dependency on the unpublished FirebaseFunctionsTestingSupport. + # Alternatively, use Swift Package Manager to run the swift integration tests locally. + # + # s.test_spec 'swift-integration' do |swift_int| + # swift_int.platforms = {:ios => '15.0', :osx => '12.0', :tvos => '15.0', :watchos => '8.0'} + # swift_int.scheme = { :code_coverage => true } + # swift_int.dependency 'FirebaseFunctionsTestingSupport' + # swift_int.source_files = 'FirebaseFunctions/Tests/SwiftIntegration/*', + # 'FirebaseTestingSupport/Functions/Sources/*' + # end end diff --git a/FirebaseFunctions/Tests/SwiftIntegration/IntegrationTests.swift b/FirebaseFunctions/Tests/SwiftIntegration/IntegrationTests.swift index 69d7b5c17c..beef878200 100644 --- a/FirebaseFunctions/Tests/SwiftIntegration/IntegrationTests.swift +++ b/FirebaseFunctions/Tests/SwiftIntegration/IntegrationTests.swift @@ -65,9 +65,35 @@ class IntegrationTests: XCTestCase { XCTAssert(false, "Failed to unwrap the function result: \(error)") } } - waitForExpectations(timeout: 1) + waitForExpectations(timeout: 5) } + #if compiler(>=5.5) && canImport(_Concurrency) + @available(iOS 15, tvOS 15, macOS 12, watchOS 8, *) + func testDataAsync() async throws { + let input = [ + "bool": true, + "int": 2 as Int32, + "long": 9_876_543_210, + "string": "four", + "array": [5 as Int32, 6 as Int32], + "null": nil, + ] as [String: Any?] + + let function = functions.httpsCallable("dataTest") + XCTAssertNotNil(function) + + let result = try await function.call(input) + let data = try XCTUnwrap(result.data as? [String: Any]) + let message = try XCTUnwrap(data["message"] as? String) + let long = try XCTUnwrap(data["long"] as? Int64) + let code = try XCTUnwrap(data["code"] as? Int32) + XCTAssertEqual(message, "stub response") + XCTAssertEqual(long, 420) + XCTAssertEqual(code, 42) + } + #endif + func testScalar() { let expectation = expectation(description: #function) let function = functions.httpsCallable("scalarTest") @@ -82,11 +108,23 @@ class IntegrationTests: XCTestCase { XCTAssert(false, "Failed to unwrap the function result: \(error)") } } - waitForExpectations(timeout: 1) + waitForExpectations(timeout: 5) } + #if compiler(>=5.5) && canImport(_Concurrency) + @available(iOS 15, tvOS 15, macOS 12, watchOS 8, *) + func testScalarAsync() async throws { + let function = functions.httpsCallable("scalarTest") + XCTAssertNotNil(function) + + let result = try await function.call(17 as Int16) + let data = try XCTUnwrap(result.data as? Int) + XCTAssertEqual(data, 76) + } + #endif + func testToken() { - // Recreate _functions with a token. + // Recreate functions with a token. let functions = FunctionsFake( projectID: "functions-integration-test", region: "us-central1", @@ -108,9 +146,30 @@ class IntegrationTests: XCTestCase { XCTAssert(false, "Failed to unwrap the function result: \(error)") } } - waitForExpectations(timeout: 1) + waitForExpectations(timeout: 5) } + #if compiler(>=5.5) && canImport(_Concurrency) + @available(iOS 15, tvOS 15, macOS 12, watchOS 8, *) + func testTokenAsync() async throws { + // Recreate functions with a token. + let functions = FunctionsFake( + projectID: "functions-integration-test", + region: "us-central1", + customDomain: nil, + withToken: "token" + ) + functions.useLocalhost() + + let function = functions.httpsCallable("FCMTokenTest") + XCTAssertNotNil(function) + + let result = try await function.call([:]) + let data = try XCTUnwrap(result.data) as? [String: Int] + XCTAssertEqual(data, [:]) + } + #endif + func testFCMToken() { let expectation = expectation(description: #function) let function = functions.httpsCallable("FCMTokenTest") @@ -125,9 +184,21 @@ class IntegrationTests: XCTestCase { XCTAssert(false, "Failed to unwrap the function result: \(error)") } } - waitForExpectations(timeout: 1) + waitForExpectations(timeout: 5) } + #if compiler(>=5.5) && canImport(_Concurrency) + @available(iOS 15, tvOS 15, macOS 12, watchOS 8, *) + func testFCMTokenAsync() async throws { + let function = functions.httpsCallable("FCMTokenTest") + XCTAssertNotNil(function) + + let result = try await function.call([:]) + let data = try XCTUnwrap(result.data) as? [String: Int] + XCTAssertEqual(data, [:]) + } + #endif + func testNull() { let expectation = expectation(description: #function) let function = functions.httpsCallable("nullTest") @@ -142,9 +213,51 @@ class IntegrationTests: XCTestCase { XCTAssert(false, "Failed to unwrap the function result: \(error)") } } - waitForExpectations(timeout: 1) + waitForExpectations(timeout: 5) } + #if compiler(>=5.5) && canImport(_Concurrency) + @available(iOS 15, tvOS 15, macOS 12, watchOS 8, *) + func testNullAsync() async throws { + let function = functions.httpsCallable("nullTest") + XCTAssertNotNil(function) + + let result = try await function.call(nil) + let data = try XCTUnwrap(result.data) as? NSNull + XCTAssertEqual(data, NSNull()) + } + #endif + + // No parameters to call should be the same as passing nil. + func testParameterless() { + let expectation = expectation(description: #function) + let function = functions.httpsCallable("nullTest") + XCTAssertNotNil(function) + function.call { result, error in + do { + XCTAssertNil(error) + let data = try XCTUnwrap(result?.data) as? NSNull + XCTAssertEqual(data, NSNull()) + expectation.fulfill() + } catch { + XCTAssert(false, "Failed to unwrap the function result: \(error)") + } + } + waitForExpectations(timeout: 5) + } + + #if compiler(>=5.5) && canImport(_Concurrency) + @available(iOS 15, tvOS 15, macOS 12, watchOS 8, *) + func testParameterlessAsync() async throws { + let function = functions.httpsCallable("nullTest") + XCTAssertNotNil(function) + + let result = try await function.call() + let data = try XCTUnwrap(result.data) as? NSNull + XCTAssertEqual(data, NSNull()) + } + #endif + func testMissingResult() { let expectation = expectation(description: #function) let function = functions.httpsCallable("missingResultTest") @@ -161,9 +274,26 @@ class IntegrationTests: XCTestCase { } } XCTAssert(true) - waitForExpectations(timeout: 1) + waitForExpectations(timeout: 5) } + #if compiler(>=5.5) && canImport(_Concurrency) + @available(iOS 15, tvOS 15, macOS 12, watchOS 8, *) + func testMissingResultAsync() async throws { + let function = functions.httpsCallable("missingResultTest") + XCTAssertNotNil(function) + do { + _ = try await function.call(nil) + } catch { + let error = try XCTUnwrap(error) as NSError + XCTAssertEqual(FunctionsErrorCode.internal.rawValue, error.code) + XCTAssertEqual("Response is missing data field.", error.localizedDescription) + return + } + XCTAssertFalse(true, "Failed to throw error for missing result") + } + #endif + func testUnhandledError() { let expectation = expectation(description: #function) let function = functions.httpsCallable("unhandledErrorTest") @@ -180,9 +310,26 @@ class IntegrationTests: XCTestCase { } } XCTAssert(true) - waitForExpectations(timeout: 1) + waitForExpectations(timeout: 5) } + #if compiler(>=5.5) && canImport(_Concurrency) + @available(iOS 15, tvOS 15, macOS 12, watchOS 8, *) + func testUnhandledErrorAsync() async throws { + let function = functions.httpsCallable("unhandledErrorTest") + XCTAssertNotNil(function) + do { + _ = try await function.call([]) + } catch { + let error = try XCTUnwrap(error) as NSError + XCTAssertEqual(FunctionsErrorCode.internal.rawValue, error.code) + XCTAssertEqual("INTERNAL", error.localizedDescription) + return + } + XCTAssertFalse(true, "Failed to throw error for missing result") + } + #endif + func testUnknownError() { let expectation = expectation(description: #function) let function = functions.httpsCallable("unknownErrorTest") @@ -199,9 +346,26 @@ class IntegrationTests: XCTestCase { } } XCTAssert(true) - waitForExpectations(timeout: 1) + waitForExpectations(timeout: 5) } + #if compiler(>=5.5) && canImport(_Concurrency) + @available(iOS 15, tvOS 15, macOS 12, watchOS 8, *) + func testUnknownErrorAsync() async throws { + let function = functions.httpsCallable("unknownErrorTest") + XCTAssertNotNil(function) + do { + _ = try await function.call([]) + } catch { + let error = try XCTUnwrap(error) as NSError + XCTAssertEqual(FunctionsErrorCode.internal.rawValue, error.code) + XCTAssertEqual("INTERNAL", error.localizedDescription) + return + } + XCTAssertFalse(true, "Failed to throw error for missing result") + } + #endif + func testExplicitError() { let expectation = expectation(description: #function) let function = functions.httpsCallable("explicitErrorTest") @@ -220,9 +384,28 @@ class IntegrationTests: XCTestCase { } } XCTAssert(true) - waitForExpectations(timeout: 1) + waitForExpectations(timeout: 5) } + #if compiler(>=5.5) && canImport(_Concurrency) + @available(iOS 15, tvOS 15, macOS 12, watchOS 8, *) + func testExplicitErrorAsync() async throws { + let function = functions.httpsCallable("explicitErrorTest") + XCTAssertNotNil(function) + do { + _ = try await function.call([]) + } catch { + let error = try XCTUnwrap(error) as NSError + XCTAssertEqual(FunctionsErrorCode.outOfRange.rawValue, error.code) + XCTAssertEqual("explicit nope", error.localizedDescription) + XCTAssertEqual(["start": 10 as Int32, "end": 20 as Int32, "long": 30], + error.userInfo[FunctionsErrorDetailsKey] as! [String: Int32]) + return + } + XCTAssertFalse(true, "Failed to throw error for missing result") + } + #endif + func testHttpError() { let expectation = expectation(description: #function) let function = functions.httpsCallable("httpErrorTest") @@ -238,9 +421,25 @@ class IntegrationTests: XCTestCase { } } XCTAssert(true) - waitForExpectations(timeout: 1) + waitForExpectations(timeout: 5) } + #if compiler(>=5.5) && canImport(_Concurrency) + @available(iOS 15, tvOS 15, macOS 12, watchOS 8, *) + func testHttpErrorAsync() async throws { + let function = functions.httpsCallable("httpErrorTest") + XCTAssertNotNil(function) + do { + _ = try await function.call([]) + } catch { + let error = try XCTUnwrap(error) as NSError + XCTAssertEqual(FunctionsErrorCode.invalidArgument.rawValue, error.code) + return + } + XCTAssertFalse(true, "Failed to throw error for missing result") + } + #endif + func testTimeout() { let expectation = expectation(description: #function) let function = functions.httpsCallable("timeoutTest") @@ -259,6 +458,25 @@ class IntegrationTests: XCTestCase { } } XCTAssert(true) - waitForExpectations(timeout: 1) + waitForExpectations(timeout: 5) } + + #if compiler(>=5.5) && canImport(_Concurrency) + @available(iOS 15, tvOS 15, macOS 12, watchOS 8, *) + func testTimeoutAsync() async throws { + let function = functions.httpsCallable("timeoutTest") + XCTAssertNotNil(function) + function.timeoutInterval = 0.05 + do { + _ = try await function.call([]) + } catch { + let error = try XCTUnwrap(error) as NSError + XCTAssertEqual(FunctionsErrorCode.deadlineExceeded.rawValue, error.code) + XCTAssertEqual("DEADLINE EXCEEDED", error.localizedDescription) + XCTAssertNil(error.userInfo[FunctionsErrorDetailsKey]) + return + } + XCTAssertFalse(true, "Failed to throw error for missing result") + } + #endif } diff --git a/FirebaseFunctionsTestingSupport.podspec b/FirebaseFunctionsTestingSupport.podspec index 75129e6a33..99828f227a 100644 --- a/FirebaseFunctionsTestingSupport.podspec +++ b/FirebaseFunctionsTestingSupport.podspec @@ -35,6 +35,8 @@ Pod::Spec.new do |s| s.source_files = [ base_dir + 'Sources/**/*.{m,mm,h}', + 'SharedTestUtilities/FIRAuthInteropFake*', + 'SharedTestUtilities/FIRMessagingInteropFake*' ] s.public_header_files = base_dir + '**/*.h'