diff --git a/.github/workflows/functions.yml b/.github/workflows/functions.yml index 97ee8af4a48..d447c850efa 100644 --- a/.github/workflows/functions.yml +++ b/.github/workflows/functions.yml @@ -47,6 +47,12 @@ jobs: run: scripts/setup_spm_tests.sh - name: iOS Unit Tests run: scripts/third_party/travis/retry.sh ./scripts/build.sh FirebaseFunctions iOS spmbuildonly + - name: Integration Test Server + run: FirebaseFunctions/Backend/start.sh synchronous + - name: iOS Swift Integration Tests + run: scripts/third_party/travis/retry.sh ./scripts/build.sh FunctionsSwiftIntegration iOS spm + - name: iOS Objective C Integration Tests + run: scripts/third_party/travis/retry.sh ./scripts/build.sh FunctionsIntegration iOS spm - name: Combine Unit Tests run: scripts/third_party/travis/retry.sh ./scripts/build.sh FunctionsCombineUnit iOS spm diff --git a/.github/workflows/spm.yml b/.github/workflows/spm.yml index 4f2f5292dcc..f0f22fa6d76 100644 --- a/.github/workflows/spm.yml +++ b/.github/workflows/spm.yml @@ -25,6 +25,8 @@ jobs: - uses: actions/checkout@v2 - name: Initialize xcodebuild run: scripts/setup_spm_tests.sh + - name: Functions Integration Test Server + run: FirebaseFunctions/Backend/start.sh synchronous - name: iOS Unit Tests run: scripts/third_party/travis/retry.sh ./scripts/build.sh Firebase-Package iOS spm diff --git a/FirebaseFunctions/Tests/SwiftIntegration/IntegrationTests.swift b/FirebaseFunctions/Tests/SwiftIntegration/IntegrationTests.swift new file mode 100644 index 00000000000..69d7b5c17c1 --- /dev/null +++ b/FirebaseFunctions/Tests/SwiftIntegration/IntegrationTests.swift @@ -0,0 +1,264 @@ +// Copyright 2021 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 + +import FirebaseFunctions +import FirebaseFunctionsTestingSupport +import XCTest + +/// This file was intitialized as a direct port of the Objective C +/// FirebaseFunctions/Tests/Integration/FIRIntegrationTests.m +/// +/// The tests require the emulator to be running with `FirebaseFunctions/Backend/start.sh synchronous` +/// The Firebase Functions called in the tests are implemented in `FirebaseFunctions/Backend/index.js`. + +class IntegrationTests: XCTestCase { + let functions = FunctionsFake( + projectID: "functions-integration-test", + region: "us-central1", + customDomain: nil, + withToken: nil + ) + let projectID = "functions-swift-integration-test" + + override func setUp() { + super.setUp() + functions.useLocalhost() + } + + func testData() { + let expectation = expectation(description: #function) + let data = [ + "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) + function.call(data) { result, error in + do { + XCTAssertNil(error) + 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) + expectation.fulfill() + } catch { + XCTAssert(false, "Failed to unwrap the function result: \(error)") + } + } + waitForExpectations(timeout: 1) + } + + func testScalar() { + let expectation = expectation(description: #function) + let function = functions.httpsCallable("scalarTest") + XCTAssertNotNil(function) + function.call(17 as Int16) { result, error in + do { + XCTAssertNil(error) + let data = try XCTUnwrap(result?.data as? Int) + XCTAssertEqual(data, 76) + expectation.fulfill() + } catch { + XCTAssert(false, "Failed to unwrap the function result: \(error)") + } + } + waitForExpectations(timeout: 1) + } + + func testToken() { + // Recreate _functions with a token. + let functions = FunctionsFake( + projectID: "functions-integration-test", + region: "us-central1", + customDomain: nil, + withToken: "token" + ) + functions.useLocalhost() + + let expectation = expectation(description: #function) + let function = functions.httpsCallable("FCMTokenTest") + XCTAssertNotNil(function) + function.call([:]) { result, error in + do { + XCTAssertNil(error) + let data = try XCTUnwrap(result?.data) as? [String: Int] + XCTAssertEqual(data, [:]) + expectation.fulfill() + } catch { + XCTAssert(false, "Failed to unwrap the function result: \(error)") + } + } + waitForExpectations(timeout: 1) + } + + func testFCMToken() { + let expectation = expectation(description: #function) + let function = functions.httpsCallable("FCMTokenTest") + XCTAssertNotNil(function) + function.call([:]) { result, error in + do { + XCTAssertNil(error) + let data = try XCTUnwrap(result?.data) as? [String: Int] + XCTAssertEqual(data, [:]) + expectation.fulfill() + } catch { + XCTAssert(false, "Failed to unwrap the function result: \(error)") + } + } + waitForExpectations(timeout: 1) + } + + func testNull() { + let expectation = expectation(description: #function) + let function = functions.httpsCallable("nullTest") + XCTAssertNotNil(function) + function.call(nil) { 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: 1) + } + + func testMissingResult() { + let expectation = expectation(description: #function) + let function = functions.httpsCallable("missingResultTest") + XCTAssertNotNil(function) + function.call(nil) { result, error in + do { + XCTAssertNotNil(error) + let error = try XCTUnwrap(error) as NSError + XCTAssertEqual(FunctionsErrorCode.internal.rawValue, error.code) + XCTAssertEqual("Response is missing data field.", error.localizedDescription) + expectation.fulfill() + } catch { + XCTAssert(false, "Failed to unwrap the function result: \(error)") + } + } + XCTAssert(true) + waitForExpectations(timeout: 1) + } + + func testUnhandledError() { + let expectation = expectation(description: #function) + let function = functions.httpsCallable("unhandledErrorTest") + XCTAssertNotNil(function) + function.call([]) { result, error in + do { + XCTAssertNotNil(error) + let error = try XCTUnwrap(error! as NSError) + XCTAssertEqual(FunctionsErrorCode.internal.rawValue, error.code) + XCTAssertEqual("INTERNAL", error.localizedDescription) + expectation.fulfill() + } catch { + XCTAssert(false, "Failed to unwrap the function result: \(error)") + } + } + XCTAssert(true) + waitForExpectations(timeout: 1) + } + + func testUnknownError() { + let expectation = expectation(description: #function) + let function = functions.httpsCallable("unknownErrorTest") + XCTAssertNotNil(function) + function.call([]) { result, error in + do { + XCTAssertNotNil(error) + let error = try XCTUnwrap(error! as NSError) + XCTAssertEqual(FunctionsErrorCode.internal.rawValue, error.code) + XCTAssertEqual("INTERNAL", error.localizedDescription) + expectation.fulfill() + } catch { + XCTAssert(false, "Failed to unwrap the function result: \(error)") + } + } + XCTAssert(true) + waitForExpectations(timeout: 1) + } + + func testExplicitError() { + let expectation = expectation(description: #function) + let function = functions.httpsCallable("explicitErrorTest") + XCTAssertNotNil(function) + function.call([]) { result, error in + do { + XCTAssertNotNil(error) + 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]) + expectation.fulfill() + } catch { + XCTAssert(false, "Failed to unwrap the function result: \(error)") + } + } + XCTAssert(true) + waitForExpectations(timeout: 1) + } + + func testHttpError() { + let expectation = expectation(description: #function) + let function = functions.httpsCallable("httpErrorTest") + XCTAssertNotNil(function) + function.call([]) { result, error in + do { + XCTAssertNotNil(error) + let error = try XCTUnwrap(error! as NSError) + XCTAssertEqual(FunctionsErrorCode.invalidArgument.rawValue, error.code) + expectation.fulfill() + } catch { + XCTAssert(false, "Failed to unwrap the function result: \(error)") + } + } + XCTAssert(true) + waitForExpectations(timeout: 1) + } + + func testTimeout() { + let expectation = expectation(description: #function) + let function = functions.httpsCallable("timeoutTest") + XCTAssertNotNil(function) + function.timeoutInterval = 0.05 + function.call([]) { result, error in + do { + XCTAssertNotNil(error) + let error = try XCTUnwrap(error! as NSError) + XCTAssertEqual(FunctionsErrorCode.deadlineExceeded.rawValue, error.code) + XCTAssertEqual("DEADLINE EXCEEDED", error.localizedDescription) + XCTAssertNil(error.userInfo[FunctionsErrorDetailsKey]) + expectation.fulfill() + } catch { + XCTAssert(false, "Failed to unwrap the function result: \(error)") + } + } + XCTAssert(true) + waitForExpectations(timeout: 1) + } +} diff --git a/FirebaseTestingSupport/Functions/Sources/FIRFunctionsFake.m b/FirebaseTestingSupport/Functions/Sources/FIRFunctionsFake.m new file mode 100644 index 00000000000..1768158907c --- /dev/null +++ b/FirebaseTestingSupport/Functions/Sources/FIRFunctionsFake.m @@ -0,0 +1,36 @@ +// Copyright 2021 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 "FirebaseTestingSupport/Functions/Sources/Public/FirebaseFunctionsTestingSupport/FIRFunctionsFake.h" +#import "FirebaseTestingSupport/Functions/Sources/Public/FirebaseFunctionsTestingSupport/FIRFunctions+Testing.h" +#import "SharedTestUtilities/FIRAuthInteropFake.h" +#import "SharedTestUtilities/FIRMessagingInteropFake.h" + +@implementation FIRFunctionsFake + +- (instancetype)initWithProjectID:(NSString *)projectID + region:(NSString *)region + customDomain:(nullable NSString *)customDomain + withToken:(nullable NSString *)token { + return [super initWithProjectID:projectID + region:region + customDomain:customDomain + auth:[[FIRAuthInteropFake alloc] initWithToken:token + userID:nil + error:nil] + messaging:[[FIRMessagingInteropFake alloc] init] + appCheck:nil]; +} + +@end diff --git a/FirebaseTestingSupport/Functions/Sources/Public/FirebaseFunctionsTestingSupport/FIRFunctionsFake.h b/FirebaseTestingSupport/Functions/Sources/Public/FirebaseFunctionsTestingSupport/FIRFunctionsFake.h new file mode 100644 index 00000000000..a8ae4e229ce --- /dev/null +++ b/FirebaseTestingSupport/Functions/Sources/Public/FirebaseFunctionsTestingSupport/FIRFunctionsFake.h @@ -0,0 +1,38 @@ +// Copyright 2021 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 + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// A functions object with fake tokens. +NS_SWIFT_NAME(FunctionsFake) +@interface FIRFunctionsFake : FIRFunctions + +/** + * Internal initializer for testing a Cloud Functions client with fakes. + * @param projectID The project ID for the Firebase project. + * @param region The region for the http trigger, such as "us-central1". + * @param customDomain A custom domain for the http trigger, such as "https://mydomain.com". + * @param token A token to use for validation (optional). + */ +- (instancetype)initWithProjectID:(NSString *)projectID + region:(NSString *)region + customDomain:(nullable NSString *)customDomain + withToken:(nullable NSString *)token; +@end + +NS_ASSUME_NONNULL_END diff --git a/Package.swift b/Package.swift index c8c2e655968..baed60ae28e 100644 --- a/Package.swift +++ b/Package.swift @@ -694,6 +694,22 @@ let package = Package( dependencies: ["FirebaseFunctions"], path: "FirebaseFunctions/Tests/SwiftUnit" ), + .testTarget( + name: "FunctionsIntegration", + dependencies: ["FirebaseFunctions", + "SharedTestUtilities"], + path: "FirebaseFunctions/Tests/Integration", + cSettings: [ + .headerSearchPath("../../../"), + ] + ), + .testTarget( + name: "FunctionsSwiftIntegration", + dependencies: ["FirebaseFunctions", + "FirebaseFunctionsTestingSupport", + "SharedTestUtilities"], + path: "FirebaseFunctions/Tests/SwiftIntegration" + ), .target( name: "FirebaseFunctionsTestingSupport", dependencies: ["FirebaseFunctions"], @@ -704,6 +720,8 @@ let package = Package( ] ), + // MARK: - Firebase In App Messaging + .target( name: "FirebaseInAppMessagingTarget", dependencies: [ diff --git a/scripts/spm_test_schemes/FunctionsIntegration.xcscheme b/scripts/spm_test_schemes/FunctionsIntegration.xcscheme new file mode 100644 index 00000000000..417dc668bc2 --- /dev/null +++ b/scripts/spm_test_schemes/FunctionsIntegration.xcscheme @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/spm_test_schemes/FunctionsSwiftIntegration.xcscheme b/scripts/spm_test_schemes/FunctionsSwiftIntegration.xcscheme new file mode 100644 index 00000000000..e90e5705803 --- /dev/null +++ b/scripts/spm_test_schemes/FunctionsSwiftIntegration.xcscheme @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + +