From a4c1b7d9356cb037651aea83bec852d1c7610f42 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Mon, 18 May 2020 11:32:39 -0700 Subject: [PATCH 01/16] Initial Fake Remote Config backend --- FirebaseRemoteConfig.podspec | 12 ++ .../Sources/Private/RCNFakeFetch.h | 31 +++ FirebaseRemoteConfig/Sources/RCNFakeFetch.m | 47 +++++ FirebaseRemoteConfig/Sources/RCNFetch.m | 41 +++- .../Tests/HermeticAPI/APITests.swift | 185 ++++++++++++++++++ .../Tests/HermeticAPI/Bridging-Header.h | 15 ++ .../HermeticAPI/GoogleService-Info.plist | 28 +++ scripts/build.sh | 10 + 8 files changed, 362 insertions(+), 7 deletions(-) create mode 100644 FirebaseRemoteConfig/Sources/Private/RCNFakeFetch.h create mode 100644 FirebaseRemoteConfig/Sources/RCNFakeFetch.m create mode 100644 FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift create mode 100644 FirebaseRemoteConfig/Tests/HermeticAPI/Bridging-Header.h create mode 100644 FirebaseRemoteConfig/Tests/HermeticAPI/GoogleService-Info.plist diff --git a/FirebaseRemoteConfig.podspec b/FirebaseRemoteConfig.podspec index 844c8436ee7..3e2f75175e2 100644 --- a/FirebaseRemoteConfig.podspec +++ b/FirebaseRemoteConfig.podspec @@ -77,4 +77,16 @@ app update. swift_api_tests.resources = 'FirebaseRemoteConfig/Tests/SwiftAPI/GoogleService-Info.plist' end + + s.test_spec 'hermetic-api' do |hermetic_api_tests| + hermetic_api_tests.platforms = {:ios => '8.0', :osx => '10.11', :tvos => '10.0'} + hermetic_api_tests.source_files = 'FirebaseRemoteConfig/Tests/HermeticAPI/*.swift', + 'FirebaseRemoteConfig/Tests/HermeticAPI/*.h' + hermetic_api_tests.requires_app_host = true + hermetic_api_tests.pod_target_xcconfig = { + 'SWIFT_OBJC_BRIDGING_HEADER' => '$(PODS_TARGET_SRCROOT)/FirebaseRemoteConfig/Tests/HermeticAPI/Bridging-Header.h' + } + hermetic_api_tests.resources = + 'FirebaseRemoteConfig/Tests/HermeticAPI/GoogleService-Info.plist' + end end diff --git a/FirebaseRemoteConfig/Sources/Private/RCNFakeFetch.h b/FirebaseRemoteConfig/Sources/Private/RCNFakeFetch.h new file mode 100644 index 00000000000..2facc8d8e25 --- /dev/null +++ b/FirebaseRemoteConfig/Sources/Private/RCNFakeFetch.h @@ -0,0 +1,31 @@ +/* +* Copyright 2020 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 + +/// Enables RemoteConfig testing without a networked backend by providing a fake RemoteConfig. +NS_SWIFT_NAME(FakeFetch) +@interface RCNFakeFetch : NSObject + +/// Holds the current fake config. +@property (class, nonatomic, copy) NSMutableDictionary *config; + +/// Returns the config and additional metadata. ++(NSDictionary *) get; + +/// If the Fake Fetcher is activated. ++(BOOL)active; +@end diff --git a/FirebaseRemoteConfig/Sources/RCNFakeFetch.m b/FirebaseRemoteConfig/Sources/RCNFakeFetch.m new file mode 100644 index 00000000000..98c38e553d3 --- /dev/null +++ b/FirebaseRemoteConfig/Sources/RCNFakeFetch.m @@ -0,0 +1,47 @@ +/* + * Copyright 2020 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 "FirebaseRemoteConfig/Sources/RCNConfigConstants.h" +#import "FirebaseRemoteConfig/Sources/Private/RCNFakeFetch.h" + +@implementation RCNFakeFetch +static NSMutableDictionary *_config = nil; + ++(NSDictionary *)config { + return _config; +} + ++(void)setConfig:(NSDictionary *)newConfig { + _config = [newConfig mutableCopy]; +} + ++(BOOL)active { + return RCNFakeFetch.config && [RCNFakeFetch.config count] > 0; +} + ++(NSDictionary *) get { + static NSDictionary *last = nil; + if (_config == nil || _config.count == 0) { + last = nil; + return @{RCNFetchResponseKeyState : RCNFetchResponseKeyStateEmptyConfig}; + } + NSString *state = [_config isEqualToDictionary:last] ? RCNFetchResponseKeyStateNoChange : + RCNFetchResponseKeyStateUpdate; + last = _config; + return @{RCNFetchResponseKeyState : state, RCNFetchResponseKeyEntries: _config}; +} + +@end diff --git a/FirebaseRemoteConfig/Sources/RCNFetch.m b/FirebaseRemoteConfig/Sources/RCNFetch.m index cfd445243d9..98ed11a87bd 100644 --- a/FirebaseRemoteConfig/Sources/RCNFetch.m +++ b/FirebaseRemoteConfig/Sources/RCNFetch.m @@ -22,6 +22,7 @@ #import #import #import "FirebaseRemoteConfig/Sources/Private/RCNConfigSettings.h" +#import "FirebaseRemoteConfig/Sources/Private/RCNFakeFetch.h" #import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h" #import "FirebaseRemoteConfig/Sources/RCNConfigContent.h" #import "FirebaseRemoteConfig/Sources/RCNConfigExperiment.h" @@ -204,6 +205,10 @@ - (NSString *)FIRAppNameFromFullyQualifiedNamespace { /// requests to work.(b/14751422). - (void)refreshInstallationsTokenWithCompletionHandler: (FIRRemoteConfigFetchCompletion)completionHandler { + if ([RCNFakeFetch active]) { + [self doFetchCall:completionHandler]; + return; + } FIRInstallations *installations = [FIRInstallations installationsWithApp:[FIRApp appNamed:[self FIRAppNameFromFullyQualifiedNamespace]]]; if (!installations || !_options.GCMSenderID) { @@ -271,13 +276,7 @@ - (void)refreshInstallationsTokenWithCompletionHandler: FIRLogInfo(kFIRLoggerRemoteConfig, @"I-RCN000022", @"Success to get iid : %@.", strongSelfQueue->_settings.configInstallationsIdentifier); - [strongSelf - getAnalyticsUserPropertiesWithCompletionHandler:^(NSDictionary *userProperties) { - dispatch_async(strongSelf->_lockQueue, ^{ - [strongSelf fetchWithUserProperties:userProperties - completionHandler:completionHandler]; - }); - }]; + [strongSelf doFetchCall:completionHandler]; }); }]; }; @@ -286,6 +285,14 @@ - (void)refreshInstallationsTokenWithCompletionHandler: [installations authTokenWithCompletion:installationsTokenHandler]; } +- (void)doFetchCall:(FIRRemoteConfigFetchCompletion)completionHandler { + [self getAnalyticsUserPropertiesWithCompletionHandler:^(NSDictionary *userProperties) { + dispatch_async(self->_lockQueue, ^{ + [self fetchWithUserProperties:userProperties completionHandler:completionHandler]; + }); + }]; +} + - (void)getAnalyticsUserPropertiesWithCompletionHandler: (FIRAInteropUserPropertiesCallback)completionHandler { FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000060", @"Fetch with user properties completed."); @@ -489,6 +496,26 @@ - (void)fetchWithUserProperties:(NSDictionary *)userProperties }); }; + // If there is a fake config, use that and skip the big block above that does the remote fetch. + if ([RCNFakeFetch active]) { + self->_settings.isFetchInProgress = NO; + + NSDictionary *fakeConfig = [RCNFakeFetch get]; + // Update config content to cache and DB. + if ([fakeConfig[@"state"] isEqualToString:RCNFetchResponseKeyStateUpdate]) { + NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; + [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; + self->_settings.lastETag = [formatter stringFromDate:[NSDate date]]; + } + [_content updateConfigContentWithResponse:fakeConfig forNamespace:self->_FIRNamespace]; + // Update experiments. + [_experiment + updateExperimentsWithResponse:fakeConfig[RCNFetchResponseKeyExperimentDescriptions]]; + [self->_settings updateMetadataWithFetchSuccessStatus:YES]; + completionHandler(FIRRemoteConfigFetchStatusSuccess, nil); + return; + } + if (gGlobalTestBlock) { gGlobalTestBlock(fetcherCompletion); return; diff --git a/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift b/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift new file mode 100644 index 00000000000..7579c627d9a --- /dev/null +++ b/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift @@ -0,0 +1,185 @@ +// Copyright 2020 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 FirebaseCore +@testable import FirebaseRemoteConfig + +import XCTest + +class APITests: XCTestCase { + var app: FirebaseApp! + var config: RemoteConfig! + + override class func setUp() { + FirebaseApp.configure() + } + + override func setUp() { + super.setUp() + app = FirebaseApp.app() + config = RemoteConfig.remoteConfig(app: app!) + let settings = RemoteConfigSettings() + settings.minimumFetchInterval = 0 + config.configSettings = settings + + FakeFetch.config = [ "Key1": "Value1"] + +// Uncomment for verbose debug logging. + FirebaseConfiguration.shared.setLoggerLevel(FirebaseLoggerLevel.debug) + } + + override func tearDown() { + app = nil + config = nil + FakeFetch.config = nil + super.tearDown() + } + + func testFetchThenActivate() { + let expectation = self.expectation(description: #function) + config.fetch { status, error in + if let error = error { + XCTFail("Fetch Error \(error)") + } + XCTAssertEqual(status, RemoteConfigFetchStatus.success) + self.config.activate { error in + if let error = error { + // This API returns an error if the config was unchanged. + // + print("Activate Error \(error)") + } + XCTAssertEqual(self.config["Key1"].stringValue, "Value1") + expectation.fulfill() + } + } + waitForExpectations() + } + + func testFetchWithExpirationThenActivate() { + let expectation = self.expectation(description: #function) + config.fetch(withExpirationDuration: 0) { status, error in + if let error = error { + XCTFail("Fetch Error \(error)") + } + XCTAssertEqual(status, RemoteConfigFetchStatus.success) + self.config.activate { error in + if let error = error { + // This API returns an error if the config was unchanged. + // + print("Activate Error \(error)") + } + XCTAssertEqual(self.config["Key1"].stringValue, "Value1") + expectation.fulfill() + } + } + waitForExpectations() + } + + func testFetchAndActivate() { + let expectation = self.expectation(description: #function) + config.fetchAndActivate { status, error in + if let error = error { + XCTFail("Fetch and Activate Error \(error)") + } + XCTAssertEqual(self.config["Key1"].stringValue, "Value1") + expectation.fulfill() + } + waitForExpectations() + } + + func testUnchangedActivateWillError() { + let expectation = self.expectation(description: #function) + config.fetch { status, error in + if let error = error { + XCTFail("Fetch Error \(error)") + } + XCTAssertEqual(status, RemoteConfigFetchStatus.success) + self.config.activate { error in + if let error = error { + // This API returns an error if the config was unchanged. + // + print("Activate Error \(error)") + } + XCTAssertEqual(self.config["Key1"].stringValue, "Value1") + expectation.fulfill() + } + } + waitForExpectations() + let expectation2 = self.expectation(description: #function + "2") + config.fetch { status, error in + if let error = error { + XCTFail("Fetch Error \(error)") + } + XCTAssertEqual(status, RemoteConfigFetchStatus.success) + self.config.activate { error in + XCTAssertNotNil(error) + if let error = error { + // This API returns an error if the config was unchanged. + // + XCTAssertEqual((error as NSError).code, RemoteConfigError.internalError.rawValue) + } + XCTAssertEqual(self.config["Key1"].stringValue, "Value1") + expectation2.fulfill() + } + } + waitForExpectations() + } + + func testChangedActivateWillNotError() { + let expectation = self.expectation(description: #function) + config.fetch { status, error in + if let error = error { + XCTFail("Fetch Error \(error)") + } + XCTAssertEqual(status, RemoteConfigFetchStatus.success) + self.config.activate { error in + if let error = error { + // This API returns an error if the config was unchanged. + // + print("Activate Error \(error)") + } + XCTAssertEqual(self.config["Key1"].stringValue, "Value1") + expectation.fulfill() + } + } + waitForExpectations() + + // Simulate updating console. + FakeFetch.config = [ "Key1": "Value2"] + + let expectation2 = self.expectation(description: #function + "2") + config.fetch { status, error in + if let error = error { + XCTFail("Fetch Error \(error)") + } + XCTAssertEqual(status, RemoteConfigFetchStatus.success) + self.config.activate { error in + XCTAssertNil(error) + XCTAssertEqual(self.config["Key1"].stringValue, "Value2") + expectation2.fulfill() + } + } + waitForExpectations() + } + + private func waitForExpectations() { + let kFIRStorageIntegrationTestTimeout = 100.0 + waitForExpectations(timeout: kFIRStorageIntegrationTestTimeout, + handler: { (error) -> Void in + if let error = error { + print(error) + } + }) + } +} diff --git a/FirebaseRemoteConfig/Tests/HermeticAPI/Bridging-Header.h b/FirebaseRemoteConfig/Tests/HermeticAPI/Bridging-Header.h new file mode 100644 index 00000000000..116151fb794 --- /dev/null +++ b/FirebaseRemoteConfig/Tests/HermeticAPI/Bridging-Header.h @@ -0,0 +1,15 @@ +// Copyright 2020 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 diff --git a/FirebaseRemoteConfig/Tests/HermeticAPI/GoogleService-Info.plist b/FirebaseRemoteConfig/Tests/HermeticAPI/GoogleService-Info.plist new file mode 100644 index 00000000000..884aa6c6f11 --- /dev/null +++ b/FirebaseRemoteConfig/Tests/HermeticAPI/GoogleService-Info.plist @@ -0,0 +1,28 @@ + + + + + API_KEY + correct_api_key + TRACKING_ID + correct_tracking_id + CLIENT_ID + correct_client_id + REVERSED_CLIENT_ID + correct_reversed_client_id + GOOGLE_APP_ID + 1:123:ios:123abc + GCM_SENDER_ID + correct_gcm_sender_id + PLIST_VERSION + 1 + BUNDLE_ID + org.cocoapods.AppHost-FirebaseRemoteConfig-Unit-Tests + PROJECT_ID + abc-xyz-123 + DATABASE_URL + https://abc-xyz-123.firebaseio.com + STORAGE_BUCKET + project-id-123.storage.firebase.com + + diff --git a/scripts/build.sh b/scripts/build.sh index 1915bf42f56..d62ce9bb4c2 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -434,6 +434,16 @@ case "$product-$platform-$method" in test ;; + RemoteConfig-*-hermetic) + pod_gen FirebaseRemoteConfig.podspec --platforms="${gen_platform}" + RunXcodebuild \ + -workspace 'gen/FirebaseRemoteConfig/FirebaseRemoteConfig.xcworkspace' \ + -scheme "FirebaseRemoteConfig-Unit-hermetic-api" \ + "${xcb_flags[@]}" \ + build \ + test + ;; + RemoteConfig-*-integration) pod_gen FirebaseRemoteConfig.podspec --platforms="${gen_platform}" RunXcodebuild \ From 04be952809b38c8267a36b779d28b1bf5a220c76 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Mon, 18 May 2020 11:37:39 -0700 Subject: [PATCH 02/16] yml and cleanup --- .github/workflows/remoteconfig.yml | 2 ++ FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift | 8 -------- FirebaseRemoteConfig/Tests/SwiftAPI/APITests.swift | 2 -- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/.github/workflows/remoteconfig.yml b/.github/workflows/remoteconfig.yml index ef42e268633..9d060ce42db 100644 --- a/.github/workflows/remoteconfig.yml +++ b/.github/workflows/remoteconfig.yml @@ -31,6 +31,8 @@ jobs: FirebaseRemoteConfig/Tests/SwiftAPI/GoogleService-Info.plist "$plist_secret" - name: BuildAndUnitTest # can be replaced with pod lib lint with CocoaPods 1.10 run: scripts/third_party/travis/retry.sh scripts/build.sh RemoteConfig ${{ matrix.target }} unit + - name: Hermetic API Tests + run: scripts/third_party/travis/retry.sh scripts/build.sh RemoteConfig iOS hermetic - name: IntegrationTest if: matrix.target == 'iOS' run: ([ -z $plist_secret ] || scripts/third_party/travis/retry.sh scripts/build.sh RemoteConfig iOS integration) diff --git a/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift b/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift index 7579c627d9a..858dd8f735a 100644 --- a/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift +++ b/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift @@ -56,7 +56,6 @@ class APITests: XCTestCase { self.config.activate { error in if let error = error { // This API returns an error if the config was unchanged. - // print("Activate Error \(error)") } XCTAssertEqual(self.config["Key1"].stringValue, "Value1") @@ -76,7 +75,6 @@ class APITests: XCTestCase { self.config.activate { error in if let error = error { // This API returns an error if the config was unchanged. - // print("Activate Error \(error)") } XCTAssertEqual(self.config["Key1"].stringValue, "Value1") @@ -107,8 +105,6 @@ class APITests: XCTestCase { XCTAssertEqual(status, RemoteConfigFetchStatus.success) self.config.activate { error in if let error = error { - // This API returns an error if the config was unchanged. - // print("Activate Error \(error)") } XCTAssertEqual(self.config["Key1"].stringValue, "Value1") @@ -125,8 +121,6 @@ class APITests: XCTestCase { self.config.activate { error in XCTAssertNotNil(error) if let error = error { - // This API returns an error if the config was unchanged. - // XCTAssertEqual((error as NSError).code, RemoteConfigError.internalError.rawValue) } XCTAssertEqual(self.config["Key1"].stringValue, "Value1") @@ -145,8 +139,6 @@ class APITests: XCTestCase { XCTAssertEqual(status, RemoteConfigFetchStatus.success) self.config.activate { error in if let error = error { - // This API returns an error if the config was unchanged. - // print("Activate Error \(error)") } XCTAssertEqual(self.config["Key1"].stringValue, "Value1") diff --git a/FirebaseRemoteConfig/Tests/SwiftAPI/APITests.swift b/FirebaseRemoteConfig/Tests/SwiftAPI/APITests.swift index 4156a7d4d2b..b7392346c46 100644 --- a/FirebaseRemoteConfig/Tests/SwiftAPI/APITests.swift +++ b/FirebaseRemoteConfig/Tests/SwiftAPI/APITests.swift @@ -51,7 +51,6 @@ class APITests: XCTestCase { self.config.activate { error in if let error = error { // This API returns an error if the config was unchanged. - // print("Activate Error \(error)") } XCTAssertEqual(self.config["Key1"].stringValue, "Value1") @@ -71,7 +70,6 @@ class APITests: XCTestCase { self.config.activate { error in if let error = error { // This API returns an error if the config was unchanged. - // print("Activate Error \(error)") } XCTAssertEqual(self.config["Key1"].stringValue, "Value1") From 0809e79b79c928d4f8d989f4aaaa4906dac4f422 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Mon, 18 May 2020 11:41:52 -0700 Subject: [PATCH 03/16] style and changelog --- FirebaseRemoteConfig/CHANGELOG.md | 1 + .../Sources/Private/RCNFakeFetch.h | 34 +++++++++---------- FirebaseRemoteConfig/Sources/RCNFakeFetch.m | 16 ++++----- FirebaseRemoteConfig/Sources/RCNFetch.m | 8 ++--- .../Tests/HermeticAPI/APITests.swift | 8 ++--- 5 files changed, 34 insertions(+), 33 deletions(-) diff --git a/FirebaseRemoteConfig/CHANGELOG.md b/FirebaseRemoteConfig/CHANGELOG.md index c88b91c01a2..91c67095f50 100644 --- a/FirebaseRemoteConfig/CHANGELOG.md +++ b/FirebaseRemoteConfig/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased - [changed] Updated `fetchAndActivateWithCompletionHandler:` implementation to activate asynchronously. (#5617) - [fixed] Remove undefined class via removing unused proto generated source files. (#4334) +- [added] Private API for a Fake Remote Config to enable testing without a backend. (#5633) # v4.4.11 - [fixed] Fixed a bug where settings updates weren't applied before fetches. (#4740) diff --git a/FirebaseRemoteConfig/Sources/Private/RCNFakeFetch.h b/FirebaseRemoteConfig/Sources/Private/RCNFakeFetch.h index 2facc8d8e25..dfcf21fc589 100644 --- a/FirebaseRemoteConfig/Sources/Private/RCNFakeFetch.h +++ b/FirebaseRemoteConfig/Sources/Private/RCNFakeFetch.h @@ -1,18 +1,18 @@ /* -* Copyright 2020 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. -*/ + * Copyright 2020 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 @@ -21,11 +21,11 @@ NS_SWIFT_NAME(FakeFetch) @interface RCNFakeFetch : NSObject /// Holds the current fake config. -@property (class, nonatomic, copy) NSMutableDictionary *config; +@property(class, nonatomic, copy) NSMutableDictionary *config; /// Returns the config and additional metadata. -+(NSDictionary *) get; ++ (NSDictionary *)get; /// If the Fake Fetcher is activated. -+(BOOL)active; ++ (BOOL)active; @end diff --git a/FirebaseRemoteConfig/Sources/RCNFakeFetch.m b/FirebaseRemoteConfig/Sources/RCNFakeFetch.m index 98c38e553d3..277dcd2278b 100644 --- a/FirebaseRemoteConfig/Sources/RCNFakeFetch.m +++ b/FirebaseRemoteConfig/Sources/RCNFakeFetch.m @@ -14,34 +14,34 @@ * limitations under the License. */ -#import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h" #import "FirebaseRemoteConfig/Sources/Private/RCNFakeFetch.h" +#import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h" @implementation RCNFakeFetch static NSMutableDictionary *_config = nil; -+(NSDictionary *)config { ++ (NSDictionary *)config { return _config; } -+(void)setConfig:(NSDictionary *)newConfig { ++ (void)setConfig:(NSDictionary *)newConfig { _config = [newConfig mutableCopy]; } -+(BOOL)active { ++ (BOOL)active { return RCNFakeFetch.config && [RCNFakeFetch.config count] > 0; } -+(NSDictionary *) get { ++ (NSDictionary *)get { static NSDictionary *last = nil; if (_config == nil || _config.count == 0) { last = nil; return @{RCNFetchResponseKeyState : RCNFetchResponseKeyStateEmptyConfig}; } - NSString *state = [_config isEqualToDictionary:last] ? RCNFetchResponseKeyStateNoChange : - RCNFetchResponseKeyStateUpdate; + NSString *state = [_config isEqualToDictionary:last] ? RCNFetchResponseKeyStateNoChange + : RCNFetchResponseKeyStateUpdate; last = _config; - return @{RCNFetchResponseKeyState : state, RCNFetchResponseKeyEntries: _config}; + return @{RCNFetchResponseKeyState : state, RCNFetchResponseKeyEntries : _config}; } @end diff --git a/FirebaseRemoteConfig/Sources/RCNFetch.m b/FirebaseRemoteConfig/Sources/RCNFetch.m index 98ed11a87bd..78a0fff3f32 100644 --- a/FirebaseRemoteConfig/Sources/RCNFetch.m +++ b/FirebaseRemoteConfig/Sources/RCNFetch.m @@ -287,10 +287,10 @@ - (void)refreshInstallationsTokenWithCompletionHandler: - (void)doFetchCall:(FIRRemoteConfigFetchCompletion)completionHandler { [self getAnalyticsUserPropertiesWithCompletionHandler:^(NSDictionary *userProperties) { - dispatch_async(self->_lockQueue, ^{ - [self fetchWithUserProperties:userProperties completionHandler:completionHandler]; - }); - }]; + dispatch_async(self->_lockQueue, ^{ + [self fetchWithUserProperties:userProperties completionHandler:completionHandler]; + }); + }]; } - (void)getAnalyticsUserPropertiesWithCompletionHandler: diff --git a/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift b/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift index 858dd8f735a..31bb928cced 100644 --- a/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift +++ b/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift @@ -33,10 +33,10 @@ class APITests: XCTestCase { settings.minimumFetchInterval = 0 config.configSettings = settings - FakeFetch.config = [ "Key1": "Value1"] + FakeFetch.config = ["Key1": "Value1"] -// Uncomment for verbose debug logging. - FirebaseConfiguration.shared.setLoggerLevel(FirebaseLoggerLevel.debug) + // Uncomment for verbose debug logging. + FirebaseConfiguration.shared.setLoggerLevel(FirebaseLoggerLevel.debug) } override func tearDown() { @@ -148,7 +148,7 @@ class APITests: XCTestCase { waitForExpectations() // Simulate updating console. - FakeFetch.config = [ "Key1": "Value2"] + FakeFetch.config = ["Key1": "Value2"] let expectation2 = self.expectation(description: #function + "2") config.fetch { status, error in From 5e188fa5bc37092c5101696cc5827581b27cdc23 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Mon, 18 May 2020 11:49:19 -0700 Subject: [PATCH 04/16] Fix analyze --- FirebaseRemoteConfig/Sources/Private/RCNFakeFetch.h | 2 +- FirebaseRemoteConfig/Sources/RCNFakeFetch.m | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/FirebaseRemoteConfig/Sources/Private/RCNFakeFetch.h b/FirebaseRemoteConfig/Sources/Private/RCNFakeFetch.h index dfcf21fc589..d95c10f284c 100644 --- a/FirebaseRemoteConfig/Sources/Private/RCNFakeFetch.h +++ b/FirebaseRemoteConfig/Sources/Private/RCNFakeFetch.h @@ -21,7 +21,7 @@ NS_SWIFT_NAME(FakeFetch) @interface RCNFakeFetch : NSObject /// Holds the current fake config. -@property(class, nonatomic, copy) NSMutableDictionary *config; +@property(class, nonatomic, assign) NSMutableDictionary *config; /// Returns the config and additional metadata. + (NSDictionary *)get; diff --git a/FirebaseRemoteConfig/Sources/RCNFakeFetch.m b/FirebaseRemoteConfig/Sources/RCNFakeFetch.m index 277dcd2278b..a08e2ea5de4 100644 --- a/FirebaseRemoteConfig/Sources/RCNFakeFetch.m +++ b/FirebaseRemoteConfig/Sources/RCNFakeFetch.m @@ -38,7 +38,7 @@ + (BOOL)active { last = nil; return @{RCNFetchResponseKeyState : RCNFetchResponseKeyStateEmptyConfig}; } - NSString *state = [_config isEqualToDictionary:last] ? RCNFetchResponseKeyStateNoChange + NSString *state = [last isEqualToDictionary:_config] ? RCNFetchResponseKeyStateNoChange : RCNFetchResponseKeyStateUpdate; last = _config; return @{RCNFetchResponseKeyState : state, RCNFetchResponseKeyEntries : _config}; From 5dd518234371553f3fa9844e2bbed3311a0161e7 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Mon, 18 May 2020 11:57:10 -0700 Subject: [PATCH 05/16] Remove unused global for old serverless testing --- FirebaseRemoteConfig/Sources/RCNConfigFetch.h | 3 --- FirebaseRemoteConfig/Sources/RCNFetch.m | 12 ------------ 2 files changed, 15 deletions(-) diff --git a/FirebaseRemoteConfig/Sources/RCNConfigFetch.h b/FirebaseRemoteConfig/Sources/RCNConfigFetch.h index 8133fe9ed4d..b3ad688ff54 100644 --- a/FirebaseRemoteConfig/Sources/RCNConfigFetch.h +++ b/FirebaseRemoteConfig/Sources/RCNConfigFetch.h @@ -56,9 +56,6 @@ typedef void (^RCNConfigFetcherTestBlock)(RCNConfigFetcherCompletion completion) /// Add the ability to update NSURLSession's timeout after a session has already been created. - (void)recreateNetworkSession; -/// Sets the test block to mock the fetch response instead of performing the fetch task from server. -+ (void)setGlobalTestBlock:(RCNConfigFetcherTestBlock)block; - NS_ASSUME_NONNULL_END @end diff --git a/FirebaseRemoteConfig/Sources/RCNFetch.m b/FirebaseRemoteConfig/Sources/RCNFetch.m index 78a0fff3f32..4f6d77e397d 100644 --- a/FirebaseRemoteConfig/Sources/RCNFetch.m +++ b/FirebaseRemoteConfig/Sources/RCNFetch.m @@ -67,8 +67,6 @@ // Deprecated error code previously from FirebaseCore static const NSInteger FIRErrorCodeConfigFailed = -114; -static RCNConfigFetcherTestBlock gGlobalTestBlock; - #pragma mark - RCNConfig @implementation RCNConfigFetch { @@ -516,10 +514,6 @@ - (void)fetchWithUserProperties:(NSDictionary *)userProperties return; } - if (gGlobalTestBlock) { - gGlobalTestBlock(fetcherCompletion); - return; - } FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000061", @"Making remote config fetch."); NSURLSessionDataTask *dataTask = [self URLSessionDataTaskWithContent:compressedContent @@ -527,12 +521,6 @@ - (void)fetchWithUserProperties:(NSDictionary *)userProperties [dataTask resume]; } -+ (void)setGlobalTestBlock:(RCNConfigFetcherTestBlock)block { - FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000027", - @"Set global test block for NSSessionFetcher, it will not fetch from server."); - gGlobalTestBlock = [block copy]; -} - - (NSString *)constructServerURL { NSString *serverURLStr = [[NSString alloc] initWithString:kServerURLDomain]; serverURLStr = [serverURLStr stringByAppendingString:kServerURLVersion]; From c087d438108e3eff72a582ad57a8aef2e8548b5b Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Thu, 21 May 2020 13:59:46 -0700 Subject: [PATCH 06/16] wip - Mock console --- FirebaseRemoteConfig.podspec | 2 +- .../Sources/FIRRemoteConfig.m | 2 +- .../Sources/Private/FIRRemoteConfig_Private.h | 5 ++++ .../Sources/Public/FIRRemoteConfig.h | 5 ++++ FirebaseRemoteConfig/Sources/RCNConfigFetch.h | 5 ++-- .../Sources/RCNConfigSettings.m | 3 +++ FirebaseRemoteConfig/Sources/RCNFetch.m | 23 +------------------ .../Tests/HermeticAPI/APITests.swift | 8 +++---- .../Tests/HermeticAPI/Bridging-Header.h | 3 ++- .../HermeticAPI/FakeConsole.h} | 3 +-- .../HermeticAPI/FakeConsole.m} | 6 ++--- 11 files changed, 28 insertions(+), 37 deletions(-) rename FirebaseRemoteConfig/{Sources/Private/RCNFakeFetch.h => Tests/HermeticAPI/FakeConsole.h} (94%) rename FirebaseRemoteConfig/{Sources/RCNFakeFetch.m => Tests/HermeticAPI/FakeConsole.m} (90%) diff --git a/FirebaseRemoteConfig.podspec b/FirebaseRemoteConfig.podspec index 3e2f75175e2..23afda8a1da 100644 --- a/FirebaseRemoteConfig.podspec +++ b/FirebaseRemoteConfig.podspec @@ -81,7 +81,7 @@ app update. s.test_spec 'hermetic-api' do |hermetic_api_tests| hermetic_api_tests.platforms = {:ios => '8.0', :osx => '10.11', :tvos => '10.0'} hermetic_api_tests.source_files = 'FirebaseRemoteConfig/Tests/HermeticAPI/*.swift', - 'FirebaseRemoteConfig/Tests/HermeticAPI/*.h' + 'FirebaseRemoteConfig/Tests/HermeticAPI/*.[hm]' hermetic_api_tests.requires_app_host = true hermetic_api_tests.pod_target_xcconfig = { 'SWIFT_OBJC_BRIDGING_HEADER' => '$(PODS_TARGET_SRCROOT)/FirebaseRemoteConfig/Tests/HermeticAPI/Bridging-Header.h' diff --git a/FirebaseRemoteConfig/Sources/FIRRemoteConfig.m b/FirebaseRemoteConfig/Sources/FIRRemoteConfig.m index 7e6f950e5db..39ba73efa80 100644 --- a/FirebaseRemoteConfig/Sources/FIRRemoteConfig.m +++ b/FirebaseRemoteConfig/Sources/FIRRemoteConfig.m @@ -623,7 +623,7 @@ - (void)setConfigSettings:(FIRRemoteConfigSettings *)configSettings { self->_settings.minimumFetchInterval = configSettings.minimumFetchInterval; self->_settings.fetchTimeout = configSettings.fetchTimeout; /// The NSURLSession needs to be recreated whenever the fetch timeout may be updated. - [self->_configFetch recreateNetworkSession]; +// [self->_configFetch recreateNetworkSession]; FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000067", @"Successfully set configSettings. Developer Mode: %@, Minimum Fetch Interval:%f, " @"Fetch timeout:%f", diff --git a/FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h b/FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h index 4155621da1d..3d629432bc0 100644 --- a/FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h +++ b/FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h @@ -18,10 +18,12 @@ #import #import +#import "RCNConfigFetch.h" @class FIROptions; @class RCNConfigContent; @class RCNConfigDBManager; +@class RCNConfigFetch; NS_ASSUME_NONNULL_BEGIN @@ -34,6 +36,9 @@ NS_ASSUME_NONNULL_BEGIN /// Internal settings @property(nonatomic, readonly, strong) RCNConfigSettings *settings; +/// Config settings are custom settings. +@property(nonatomic, readwrite, strong, nonnull) RCNConfigFetch *configFetch; + /// Returns the FIRRemoteConfig instance for your namespace and for the default Firebase App. /// This singleton object contains the complete set of Remote Config parameter values available to /// the app, including the Active Config and Default Config.. This object also caches values fetched diff --git a/FirebaseRemoteConfig/Sources/Public/FIRRemoteConfig.h b/FirebaseRemoteConfig/Sources/Public/FIRRemoteConfig.h index 5a69a876678..1dc7f3c59e6 100644 --- a/FirebaseRemoteConfig/Sources/Public/FIRRemoteConfig.h +++ b/FirebaseRemoteConfig/Sources/Public/FIRRemoteConfig.h @@ -149,6 +149,11 @@ NS_SWIFT_NAME(RemoteConfigSettings) "documentation for additional details."); @end + +@class RCNConfigFetch; + + + #pragma mark - FIRRemoteConfig /// Firebase Remote Config class. The shared instance method +remoteConfig can be created and used /// to fetch, activate and read config results and set default config results. diff --git a/FirebaseRemoteConfig/Sources/RCNConfigFetch.h b/FirebaseRemoteConfig/Sources/RCNConfigFetch.h index b3ad688ff54..8b627212fe5 100644 --- a/FirebaseRemoteConfig/Sources/RCNConfigFetch.h +++ b/FirebaseRemoteConfig/Sources/RCNConfigFetch.h @@ -30,9 +30,6 @@ NS_ASSUME_NONNULL_BEGIN /// Completion handler invoked by NSSessionFetcher. typedef void (^RCNConfigFetcherCompletion)(NSData *data, NSURLResponse *response, NSError *error); -/// Test block used for global NSSessionFetcher. -typedef void (^RCNConfigFetcherTestBlock)(RCNConfigFetcherCompletion completion); - @interface RCNConfigFetch : NSObject - (instancetype)init NS_UNAVAILABLE; @@ -56,6 +53,8 @@ typedef void (^RCNConfigFetcherTestBlock)(RCNConfigFetcherCompletion completion) /// Add the ability to update NSURLSession's timeout after a session has already been created. - (void)recreateNetworkSession; +@property(nonatomic, readwrite, strong, nonnull) NSURLSession *fetchSession; + NS_ASSUME_NONNULL_END @end diff --git a/FirebaseRemoteConfig/Sources/RCNConfigSettings.m b/FirebaseRemoteConfig/Sources/RCNConfigSettings.m index ba1d7d31ac9..9a46d7f538a 100644 --- a/FirebaseRemoteConfig/Sources/RCNConfigSettings.m +++ b/FirebaseRemoteConfig/Sources/RCNConfigSettings.m @@ -126,6 +126,7 @@ - (void)setLastETag:(NSString *)lastETag { } - (void)setLastETagUpdateTime:(NSTimeInterval)lastETagUpdateTime { + NSLog(@"xyzzy lastETagUpdateTime %f", lastETagUpdateTime); [_userDefaultsManager setLastETagUpdateTime:lastETagUpdateTime]; } @@ -169,6 +170,7 @@ - (NSDictionary *)loadConfigFromMetadataTable { } if (metadata[RCNKeyLastApplyTime]) { self->_lastApplyTimeInterval = [metadata[RCNKeyLastApplyTime] doubleValue]; + NSLog(@"loading config lastETagUpdateTime %f", _lastApplyTimeInterval); } if (metadata[RCNKeyLastFetchStatus]) { self->_lastSetDefaultsTimeInterval = [metadata[RCNKeyLastSetDefaultsTime] doubleValue]; @@ -429,6 +431,7 @@ - (void)setFetchTimeout:(NSTimeInterval)fetchTimeout { - (void)setLastApplyTimeInterval:(NSTimeInterval)lastApplyTimestamp { _lastApplyTimeInterval = lastApplyTimestamp; + NSLog(@"apply timestamp to _lastApplyTimeInterval %f", _lastApplyTimeInterval); [_DBManager updateMetadataWithOption:RCNUpdateOptionApplyTime values:@[ @(lastApplyTimestamp) ] completionHandler:nil]; diff --git a/FirebaseRemoteConfig/Sources/RCNFetch.m b/FirebaseRemoteConfig/Sources/RCNFetch.m index 4f6d77e397d..bb4a4c4ab89 100644 --- a/FirebaseRemoteConfig/Sources/RCNFetch.m +++ b/FirebaseRemoteConfig/Sources/RCNFetch.m @@ -22,7 +22,6 @@ #import #import #import "FirebaseRemoteConfig/Sources/Private/RCNConfigSettings.h" -#import "FirebaseRemoteConfig/Sources/Private/RCNFakeFetch.h" #import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h" #import "FirebaseRemoteConfig/Sources/RCNConfigContent.h" #import "FirebaseRemoteConfig/Sources/RCNConfigExperiment.h" @@ -203,7 +202,7 @@ - (NSString *)FIRAppNameFromFullyQualifiedNamespace { /// requests to work.(b/14751422). - (void)refreshInstallationsTokenWithCompletionHandler: (FIRRemoteConfigFetchCompletion)completionHandler { - if ([RCNFakeFetch active]) { + if (YES) { [self doFetchCall:completionHandler]; return; } @@ -494,26 +493,6 @@ - (void)fetchWithUserProperties:(NSDictionary *)userProperties }); }; - // If there is a fake config, use that and skip the big block above that does the remote fetch. - if ([RCNFakeFetch active]) { - self->_settings.isFetchInProgress = NO; - - NSDictionary *fakeConfig = [RCNFakeFetch get]; - // Update config content to cache and DB. - if ([fakeConfig[@"state"] isEqualToString:RCNFetchResponseKeyStateUpdate]) { - NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; - [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; - self->_settings.lastETag = [formatter stringFromDate:[NSDate date]]; - } - [_content updateConfigContentWithResponse:fakeConfig forNamespace:self->_FIRNamespace]; - // Update experiments. - [_experiment - updateExperimentsWithResponse:fakeConfig[RCNFetchResponseKeyExperimentDescriptions]]; - [self->_settings updateMetadataWithFetchSuccessStatus:YES]; - completionHandler(FIRRemoteConfigFetchStatusSuccess, nil); - return; - } - FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000061", @"Making remote config fetch."); NSURLSessionDataTask *dataTask = [self URLSessionDataTaskWithContent:compressedContent diff --git a/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift b/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift index 31bb928cced..aed959d9bda 100644 --- a/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift +++ b/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift @@ -32,8 +32,8 @@ class APITests: XCTestCase { let settings = RemoteConfigSettings() settings.minimumFetchInterval = 0 config.configSettings = settings - - FakeFetch.config = ["Key1": "Value1"] + config.configFetch.fetchSession = URLSessionMock.init() + FakeConsole.config = ["Key1": "Value1"] // Uncomment for verbose debug logging. FirebaseConfiguration.shared.setLoggerLevel(FirebaseLoggerLevel.debug) @@ -42,7 +42,7 @@ class APITests: XCTestCase { override func tearDown() { app = nil config = nil - FakeFetch.config = nil + FakeConsole.config = nil super.tearDown() } @@ -148,7 +148,7 @@ class APITests: XCTestCase { waitForExpectations() // Simulate updating console. - FakeFetch.config = ["Key1": "Value2"] + FakeConsole.config = ["Key1": "Value2"] let expectation2 = self.expectation(description: #function + "2") config.fetch { status, error in diff --git a/FirebaseRemoteConfig/Tests/HermeticAPI/Bridging-Header.h b/FirebaseRemoteConfig/Tests/HermeticAPI/Bridging-Header.h index 116151fb794..31b6858593e 100644 --- a/FirebaseRemoteConfig/Tests/HermeticAPI/Bridging-Header.h +++ b/FirebaseRemoteConfig/Tests/HermeticAPI/Bridging-Header.h @@ -12,4 +12,5 @@ // See the License for the specific language governing permissions and // limitations under the License. -#import +#import +#import "FakeConsole.h" diff --git a/FirebaseRemoteConfig/Sources/Private/RCNFakeFetch.h b/FirebaseRemoteConfig/Tests/HermeticAPI/FakeConsole.h similarity index 94% rename from FirebaseRemoteConfig/Sources/Private/RCNFakeFetch.h rename to FirebaseRemoteConfig/Tests/HermeticAPI/FakeConsole.h index d95c10f284c..b92f59de4ca 100644 --- a/FirebaseRemoteConfig/Sources/Private/RCNFakeFetch.h +++ b/FirebaseRemoteConfig/Tests/HermeticAPI/FakeConsole.h @@ -17,8 +17,7 @@ #import /// Enables RemoteConfig testing without a networked backend by providing a fake RemoteConfig. -NS_SWIFT_NAME(FakeFetch) -@interface RCNFakeFetch : NSObject +@interface FakeConsole : NSObject /// Holds the current fake config. @property(class, nonatomic, assign) NSMutableDictionary *config; diff --git a/FirebaseRemoteConfig/Sources/RCNFakeFetch.m b/FirebaseRemoteConfig/Tests/HermeticAPI/FakeConsole.m similarity index 90% rename from FirebaseRemoteConfig/Sources/RCNFakeFetch.m rename to FirebaseRemoteConfig/Tests/HermeticAPI/FakeConsole.m index a08e2ea5de4..a360f553475 100644 --- a/FirebaseRemoteConfig/Sources/RCNFakeFetch.m +++ b/FirebaseRemoteConfig/Tests/HermeticAPI/FakeConsole.m @@ -14,10 +14,10 @@ * limitations under the License. */ -#import "FirebaseRemoteConfig/Sources/Private/RCNFakeFetch.h" +#import "FirebaseRemoteConfig/Tests/HermeticAPI/FakeConsole.h" #import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h" -@implementation RCNFakeFetch +@implementation FakeConsole static NSMutableDictionary *_config = nil; + (NSDictionary *)config { @@ -29,7 +29,7 @@ + (void)setConfig:(NSDictionary *)newConfig { } + (BOOL)active { - return RCNFakeFetch.config && [RCNFakeFetch.config count] > 0; + return FakeConsole.config && [FakeConsole.config count] > 0; } + (NSDictionary *)get { From c7f7b8a37edffb7788c7721c5812f2013a0830f9 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Thu, 21 May 2020 14:39:49 -0700 Subject: [PATCH 07/16] initial mock --- .../HermeticAPI/URLSessionPartialMock.swift | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 FirebaseRemoteConfig/Tests/HermeticAPI/URLSessionPartialMock.swift diff --git a/FirebaseRemoteConfig/Tests/HermeticAPI/URLSessionPartialMock.swift b/FirebaseRemoteConfig/Tests/HermeticAPI/URLSessionPartialMock.swift new file mode 100644 index 00000000000..09da051de24 --- /dev/null +++ b/FirebaseRemoteConfig/Tests/HermeticAPI/URLSessionPartialMock.swift @@ -0,0 +1,50 @@ +// Copyright 2020 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 + +// Create a partial mock by subclassing the URLSessionDataTask. +class URLSessionDataTaskMock: URLSessionDataTask { + private let closure: () -> Void + + init(closure: @escaping () -> Void) { + self.closure = closure + } + + override func resume() { + closure() + } +} + +class URLSessionMock: URLSession { + typealias CompletionHandler = (Data?, URLResponse?, Error?) -> Void + + // Properties to control what gets returned to the URLSession callback. + // error could also be added here. + var data: Data? + var response: URLResponse? + var etag = "" + + override func dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask { + let consoleValues = FakeConsole.get() as Dictionary +// if etag == "" || consoleValues["state"] == "UPDATE" { +// +// } + let jsonData = try! JSONSerialization.data(withJSONObject: consoleValues) + let response = HTTPURLResponse.init(url: URL.init(fileURLWithPath: "fakeURL"), statusCode: 200, httpVersion: nil, headerFields: nil) + return URLSessionDataTaskMock { + completionHandler(jsonData, response, nil) + } + } +} From 01f2cb0ab9e9bbd9dbefb20e4f9ae7b970503c94 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Thu, 21 May 2020 16:26:38 -0700 Subject: [PATCH 08/16] checkpoint --- FirebaseRemoteConfig.podspec | 2 +- FirebaseRemoteConfig/CHANGELOG.md | 2 +- .../Sources/FIRRemoteConfig.m | 2 +- .../Sources/Private/FIRRemoteConfig_Private.h | 3 +- .../Sources/{ => Private}/RCNConfigFetch.h | 0 .../Sources/Public/FIRRemoteConfig.h | 5 -- .../Sources/RCNConfigSettings.m | 3 -- FirebaseRemoteConfig/Sources/RCNFetch.m | 2 +- .../Tests/HermeticAPI/APITests.swift | 2 +- .../Tests/HermeticAPI/Bridging-Header.h | 2 +- .../Tests/HermeticAPI/FakeConsole.h | 30 ------------ .../Tests/HermeticAPI/FakeConsole.m | 47 ------------------- .../Tests/HermeticAPI/FakeConsole.swift | 35 ++++++++++++++ .../HermeticAPI/URLSessionPartialMock.swift | 18 ++++--- .../Tests/Unit/RCNInstanceIDTest.m | 2 +- .../Tests/Unit/RCNRemoteConfigTest.m | 2 +- 16 files changed, 56 insertions(+), 101 deletions(-) rename FirebaseRemoteConfig/Sources/{ => Private}/RCNConfigFetch.h (100%) delete mode 100644 FirebaseRemoteConfig/Tests/HermeticAPI/FakeConsole.h delete mode 100644 FirebaseRemoteConfig/Tests/HermeticAPI/FakeConsole.m create mode 100644 FirebaseRemoteConfig/Tests/HermeticAPI/FakeConsole.swift diff --git a/FirebaseRemoteConfig.podspec b/FirebaseRemoteConfig.podspec index 23afda8a1da..3e2f75175e2 100644 --- a/FirebaseRemoteConfig.podspec +++ b/FirebaseRemoteConfig.podspec @@ -81,7 +81,7 @@ app update. s.test_spec 'hermetic-api' do |hermetic_api_tests| hermetic_api_tests.platforms = {:ios => '8.0', :osx => '10.11', :tvos => '10.0'} hermetic_api_tests.source_files = 'FirebaseRemoteConfig/Tests/HermeticAPI/*.swift', - 'FirebaseRemoteConfig/Tests/HermeticAPI/*.[hm]' + 'FirebaseRemoteConfig/Tests/HermeticAPI/*.h' hermetic_api_tests.requires_app_host = true hermetic_api_tests.pod_target_xcconfig = { 'SWIFT_OBJC_BRIDGING_HEADER' => '$(PODS_TARGET_SRCROOT)/FirebaseRemoteConfig/Tests/HermeticAPI/Bridging-Header.h' diff --git a/FirebaseRemoteConfig/CHANGELOG.md b/FirebaseRemoteConfig/CHANGELOG.md index 91c67095f50..3af34d85e62 100644 --- a/FirebaseRemoteConfig/CHANGELOG.md +++ b/FirebaseRemoteConfig/CHANGELOG.md @@ -1,7 +1,7 @@ # Unreleased - [changed] Updated `fetchAndActivateWithCompletionHandler:` implementation to activate asynchronously. (#5617) - [fixed] Remove undefined class via removing unused proto generated source files. (#4334) -- [added] Private API for a Fake Remote Config to enable testing without a backend. (#5633) +- [added] Add an URLSession Partial Mock to enable testing without a backend. (#5633) # v4.4.11 - [fixed] Fixed a bug where settings updates weren't applied before fetches. (#4740) diff --git a/FirebaseRemoteConfig/Sources/FIRRemoteConfig.m b/FirebaseRemoteConfig/Sources/FIRRemoteConfig.m index 39ba73efa80..ea19c654ab5 100644 --- a/FirebaseRemoteConfig/Sources/FIRRemoteConfig.m +++ b/FirebaseRemoteConfig/Sources/FIRRemoteConfig.m @@ -23,12 +23,12 @@ #import #import "FirebaseRemoteConfig/Sources/FIRRemoteConfigComponent.h" #import "FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h" +#import "FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h" #import "FirebaseRemoteConfig/Sources/Private/RCNConfigSettings.h" #import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h" #import "FirebaseRemoteConfig/Sources/RCNConfigContent.h" #import "FirebaseRemoteConfig/Sources/RCNConfigDBManager.h" #import "FirebaseRemoteConfig/Sources/RCNConfigExperiment.h" -#import "FirebaseRemoteConfig/Sources/RCNConfigFetch.h" #import "FirebaseRemoteConfig/Sources/RCNConfigValue_Internal.h" #import "FirebaseRemoteConfig/Sources/RCNDevice.h" diff --git a/FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h b/FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h index 3d629432bc0..0db69e11016 100644 --- a/FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h +++ b/FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h @@ -18,12 +18,11 @@ #import #import -#import "RCNConfigFetch.h" +#import @class FIROptions; @class RCNConfigContent; @class RCNConfigDBManager; -@class RCNConfigFetch; NS_ASSUME_NONNULL_BEGIN diff --git a/FirebaseRemoteConfig/Sources/RCNConfigFetch.h b/FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h similarity index 100% rename from FirebaseRemoteConfig/Sources/RCNConfigFetch.h rename to FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h diff --git a/FirebaseRemoteConfig/Sources/Public/FIRRemoteConfig.h b/FirebaseRemoteConfig/Sources/Public/FIRRemoteConfig.h index 1dc7f3c59e6..5a69a876678 100644 --- a/FirebaseRemoteConfig/Sources/Public/FIRRemoteConfig.h +++ b/FirebaseRemoteConfig/Sources/Public/FIRRemoteConfig.h @@ -149,11 +149,6 @@ NS_SWIFT_NAME(RemoteConfigSettings) "documentation for additional details."); @end - -@class RCNConfigFetch; - - - #pragma mark - FIRRemoteConfig /// Firebase Remote Config class. The shared instance method +remoteConfig can be created and used /// to fetch, activate and read config results and set default config results. diff --git a/FirebaseRemoteConfig/Sources/RCNConfigSettings.m b/FirebaseRemoteConfig/Sources/RCNConfigSettings.m index 9a46d7f538a..ba1d7d31ac9 100644 --- a/FirebaseRemoteConfig/Sources/RCNConfigSettings.m +++ b/FirebaseRemoteConfig/Sources/RCNConfigSettings.m @@ -126,7 +126,6 @@ - (void)setLastETag:(NSString *)lastETag { } - (void)setLastETagUpdateTime:(NSTimeInterval)lastETagUpdateTime { - NSLog(@"xyzzy lastETagUpdateTime %f", lastETagUpdateTime); [_userDefaultsManager setLastETagUpdateTime:lastETagUpdateTime]; } @@ -170,7 +169,6 @@ - (NSDictionary *)loadConfigFromMetadataTable { } if (metadata[RCNKeyLastApplyTime]) { self->_lastApplyTimeInterval = [metadata[RCNKeyLastApplyTime] doubleValue]; - NSLog(@"loading config lastETagUpdateTime %f", _lastApplyTimeInterval); } if (metadata[RCNKeyLastFetchStatus]) { self->_lastSetDefaultsTimeInterval = [metadata[RCNKeyLastSetDefaultsTime] doubleValue]; @@ -431,7 +429,6 @@ - (void)setFetchTimeout:(NSTimeInterval)fetchTimeout { - (void)setLastApplyTimeInterval:(NSTimeInterval)lastApplyTimestamp { _lastApplyTimeInterval = lastApplyTimestamp; - NSLog(@"apply timestamp to _lastApplyTimeInterval %f", _lastApplyTimeInterval); [_DBManager updateMetadataWithOption:RCNUpdateOptionApplyTime values:@[ @(lastApplyTimestamp) ] completionHandler:nil]; diff --git a/FirebaseRemoteConfig/Sources/RCNFetch.m b/FirebaseRemoteConfig/Sources/RCNFetch.m index bb4a4c4ab89..b8b8df28191 100644 --- a/FirebaseRemoteConfig/Sources/RCNFetch.m +++ b/FirebaseRemoteConfig/Sources/RCNFetch.m @@ -14,7 +14,7 @@ * limitations under the License. */ -#import "FirebaseRemoteConfig/Sources/RCNConfigFetch.h" +#import "FirebaseRemoteConfig/Sources/Private//RCNConfigFetch.h" #import #import diff --git a/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift b/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift index aed959d9bda..db7314778d5 100644 --- a/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift +++ b/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift @@ -42,7 +42,7 @@ class APITests: XCTestCase { override func tearDown() { app = nil config = nil - FakeConsole.config = nil + FakeConsole.empty() super.tearDown() } diff --git a/FirebaseRemoteConfig/Tests/HermeticAPI/Bridging-Header.h b/FirebaseRemoteConfig/Tests/HermeticAPI/Bridging-Header.h index 31b6858593e..c3f1c728259 100644 --- a/FirebaseRemoteConfig/Tests/HermeticAPI/Bridging-Header.h +++ b/FirebaseRemoteConfig/Tests/HermeticAPI/Bridging-Header.h @@ -13,4 +13,4 @@ // limitations under the License. #import -#import "FakeConsole.h" +#import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h" diff --git a/FirebaseRemoteConfig/Tests/HermeticAPI/FakeConsole.h b/FirebaseRemoteConfig/Tests/HermeticAPI/FakeConsole.h deleted file mode 100644 index b92f59de4ca..00000000000 --- a/FirebaseRemoteConfig/Tests/HermeticAPI/FakeConsole.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2020 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 - -/// Enables RemoteConfig testing without a networked backend by providing a fake RemoteConfig. -@interface FakeConsole : NSObject - -/// Holds the current fake config. -@property(class, nonatomic, assign) NSMutableDictionary *config; - -/// Returns the config and additional metadata. -+ (NSDictionary *)get; - -/// If the Fake Fetcher is activated. -+ (BOOL)active; -@end diff --git a/FirebaseRemoteConfig/Tests/HermeticAPI/FakeConsole.m b/FirebaseRemoteConfig/Tests/HermeticAPI/FakeConsole.m deleted file mode 100644 index a360f553475..00000000000 --- a/FirebaseRemoteConfig/Tests/HermeticAPI/FakeConsole.m +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2020 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 "FirebaseRemoteConfig/Tests/HermeticAPI/FakeConsole.h" -#import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h" - -@implementation FakeConsole -static NSMutableDictionary *_config = nil; - -+ (NSDictionary *)config { - return _config; -} - -+ (void)setConfig:(NSDictionary *)newConfig { - _config = [newConfig mutableCopy]; -} - -+ (BOOL)active { - return FakeConsole.config && [FakeConsole.config count] > 0; -} - -+ (NSDictionary *)get { - static NSDictionary *last = nil; - if (_config == nil || _config.count == 0) { - last = nil; - return @{RCNFetchResponseKeyState : RCNFetchResponseKeyStateEmptyConfig}; - } - NSString *state = [last isEqualToDictionary:_config] ? RCNFetchResponseKeyStateNoChange - : RCNFetchResponseKeyStateUpdate; - last = _config; - return @{RCNFetchResponseKeyState : state, RCNFetchResponseKeyEntries : _config}; -} - -@end diff --git a/FirebaseRemoteConfig/Tests/HermeticAPI/FakeConsole.swift b/FirebaseRemoteConfig/Tests/HermeticAPI/FakeConsole.swift new file mode 100644 index 00000000000..101f25d11d6 --- /dev/null +++ b/FirebaseRemoteConfig/Tests/HermeticAPI/FakeConsole.swift @@ -0,0 +1,35 @@ +// Copyright 2020 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. + +class FakeConsole { + static var config = [String: AnyHashable]() + static var last = [String: AnyHashable]() + + static func empty() { + config = [String: AnyHashable]() + } + + static func get() -> [String: AnyHashable] { + if config.count == 0 { + last = config + return [ RCNFetchResponseKeyState : RCNFetchResponseKeyStateEmptyConfig ] + } + var state = RCNFetchResponseKeyStateNoChange + if last != config { + state = RCNFetchResponseKeyStateUpdate + } + last = config + return [ RCNFetchResponseKeyState : state, RCNFetchResponseKeyEntries : config] + } +} diff --git a/FirebaseRemoteConfig/Tests/HermeticAPI/URLSessionPartialMock.swift b/FirebaseRemoteConfig/Tests/HermeticAPI/URLSessionPartialMock.swift index 09da051de24..f7662019f78 100644 --- a/FirebaseRemoteConfig/Tests/HermeticAPI/URLSessionPartialMock.swift +++ b/FirebaseRemoteConfig/Tests/HermeticAPI/URLSessionPartialMock.swift @@ -36,13 +36,19 @@ class URLSessionMock: URLSession { var response: URLResponse? var etag = "" - override func dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask { - let consoleValues = FakeConsole.get() as Dictionary -// if etag == "" || consoleValues["state"] == "UPDATE" { -// -// } + override func dataTask(with request: URLRequest, + completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) + -> URLSessionDataTask { + let consoleValues = FakeConsole.get() + if etag == "" || consoleValues["state"] as! String == RCNFetchResponseKeyStateUpdate { + // Time string in microseconds to insure a different string from previous change. + etag = String(NSDate().timeIntervalSince1970) + } let jsonData = try! JSONSerialization.data(withJSONObject: consoleValues) - let response = HTTPURLResponse.init(url: URL.init(fileURLWithPath: "fakeURL"), statusCode: 200, httpVersion: nil, headerFields: nil) + let response = HTTPURLResponse.init(url: URL.init(fileURLWithPath: "fakeURL"), + statusCode: 200, + httpVersion: nil, + headerFields: ["etag": etag]) return URLSessionDataTaskMock { completionHandler(jsonData, response, nil) } diff --git a/FirebaseRemoteConfig/Tests/Unit/RCNInstanceIDTest.m b/FirebaseRemoteConfig/Tests/Unit/RCNInstanceIDTest.m index ba4f109881d..39289394fe2 100644 --- a/FirebaseRemoteConfig/Tests/Unit/RCNInstanceIDTest.m +++ b/FirebaseRemoteConfig/Tests/Unit/RCNInstanceIDTest.m @@ -18,9 +18,9 @@ #import #import "FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h" +#import "FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h" #import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h" #import "FirebaseRemoteConfig/Sources/RCNConfigDBManager.h" -#import "FirebaseRemoteConfig/Sources/RCNConfigFetch.h" #import "FirebaseRemoteConfig/Sources/RCNUserDefaultsManager.h" #import "FirebaseRemoteConfig/Tests/Unit/RCNTestUtilities.h" diff --git a/FirebaseRemoteConfig/Tests/Unit/RCNRemoteConfigTest.m b/FirebaseRemoteConfig/Tests/Unit/RCNRemoteConfigTest.m index e997d860c9f..53812e85db3 100644 --- a/FirebaseRemoteConfig/Tests/Unit/RCNRemoteConfigTest.m +++ b/FirebaseRemoteConfig/Tests/Unit/RCNRemoteConfigTest.m @@ -18,9 +18,9 @@ #import #import "FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h" +#import "FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h" #import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h" #import "FirebaseRemoteConfig/Sources/RCNConfigDBManager.h" -#import "FirebaseRemoteConfig/Sources/RCNConfigFetch.h" #import "FirebaseRemoteConfig/Sources/RCNUserDefaultsManager.h" #import "FirebaseRemoteConfig/Tests/Unit/RCNTestUtilities.h" From 244196715e84379fd5adf1add615ec07bb9aa571 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Thu, 21 May 2020 16:49:27 -0700 Subject: [PATCH 09/16] cleanup --- FirebaseRemoteConfig/Sources/FIRRemoteConfig.m | 2 +- .../Sources/Private/FIRRemoteConfig_Private.h | 2 +- FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h | 4 ++++ FirebaseRemoteConfig/Sources/RCNFetch.m | 5 ++++- FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift | 3 ++- FirebaseRemoteConfig/Tests/HermeticAPI/FakeConsole.swift | 4 ++-- .../Tests/HermeticAPI/URLSessionPartialMock.swift | 8 ++++---- 7 files changed, 18 insertions(+), 10 deletions(-) diff --git a/FirebaseRemoteConfig/Sources/FIRRemoteConfig.m b/FirebaseRemoteConfig/Sources/FIRRemoteConfig.m index ea19c654ab5..3f7f5e83b08 100644 --- a/FirebaseRemoteConfig/Sources/FIRRemoteConfig.m +++ b/FirebaseRemoteConfig/Sources/FIRRemoteConfig.m @@ -623,7 +623,7 @@ - (void)setConfigSettings:(FIRRemoteConfigSettings *)configSettings { self->_settings.minimumFetchInterval = configSettings.minimumFetchInterval; self->_settings.fetchTimeout = configSettings.fetchTimeout; /// The NSURLSession needs to be recreated whenever the fetch timeout may be updated. -// [self->_configFetch recreateNetworkSession]; + // [self->_configFetch recreateNetworkSession]; FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000067", @"Successfully set configSettings. Developer Mode: %@, Minimum Fetch Interval:%f, " @"Fetch timeout:%f", diff --git a/FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h b/FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h index 0db69e11016..55ee1e34fe3 100644 --- a/FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h +++ b/FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h @@ -17,8 +17,8 @@ #import #import -#import #import +#import @class FIROptions; @class RCNConfigContent; diff --git a/FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h b/FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h index 8b627212fe5..7cb44d8d480 100644 --- a/FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h +++ b/FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h @@ -53,8 +53,12 @@ typedef void (^RCNConfigFetcherCompletion)(NSData *data, NSURLResponse *response /// Add the ability to update NSURLSession's timeout after a session has already been created. - (void)recreateNetworkSession; +/// Provide fetchSession for tests to override. @property(nonatomic, readwrite, strong, nonnull) NSURLSession *fetchSession; +/// Flag for tests to disable network accesses. +@property(nonatomic, readwrite) BOOL testWithoutNetwork; + NS_ASSUME_NONNULL_END @end diff --git a/FirebaseRemoteConfig/Sources/RCNFetch.m b/FirebaseRemoteConfig/Sources/RCNFetch.m index b8b8df28191..9cd0bec2f5d 100644 --- a/FirebaseRemoteConfig/Sources/RCNFetch.m +++ b/FirebaseRemoteConfig/Sources/RCNFetch.m @@ -109,6 +109,9 @@ - (instancetype)initWithContent:(RCNConfigContent *)content /// Force a new NSURLSession creation for updated config. - (void)recreateNetworkSession { + if (self.testWithoutNetwork) { + return; + } if (_fetchSession) { [_fetchSession invalidateAndCancel]; } @@ -202,7 +205,7 @@ - (NSString *)FIRAppNameFromFullyQualifiedNamespace { /// requests to work.(b/14751422). - (void)refreshInstallationsTokenWithCompletionHandler: (FIRRemoteConfigFetchCompletion)completionHandler { - if (YES) { + if (self.testWithoutNetwork) { [self doFetchCall:completionHandler]; return; } diff --git a/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift b/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift index db7314778d5..051a9eb54cc 100644 --- a/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift +++ b/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift @@ -32,7 +32,8 @@ class APITests: XCTestCase { let settings = RemoteConfigSettings() settings.minimumFetchInterval = 0 config.configSettings = settings - config.configFetch.fetchSession = URLSessionMock.init() + config.configFetch.fetchSession = URLSessionMock() + config.configFetch.testWithoutNetwork = true FakeConsole.config = ["Key1": "Value1"] // Uncomment for verbose debug logging. diff --git a/FirebaseRemoteConfig/Tests/HermeticAPI/FakeConsole.swift b/FirebaseRemoteConfig/Tests/HermeticAPI/FakeConsole.swift index 101f25d11d6..fb813867658 100644 --- a/FirebaseRemoteConfig/Tests/HermeticAPI/FakeConsole.swift +++ b/FirebaseRemoteConfig/Tests/HermeticAPI/FakeConsole.swift @@ -23,13 +23,13 @@ class FakeConsole { static func get() -> [String: AnyHashable] { if config.count == 0 { last = config - return [ RCNFetchResponseKeyState : RCNFetchResponseKeyStateEmptyConfig ] + return [RCNFetchResponseKeyState: RCNFetchResponseKeyStateEmptyConfig] } var state = RCNFetchResponseKeyStateNoChange if last != config { state = RCNFetchResponseKeyStateUpdate } last = config - return [ RCNFetchResponseKeyState : state, RCNFetchResponseKeyEntries : config] + return [RCNFetchResponseKeyState: state, RCNFetchResponseKeyEntries: config] } } diff --git a/FirebaseRemoteConfig/Tests/HermeticAPI/URLSessionPartialMock.swift b/FirebaseRemoteConfig/Tests/HermeticAPI/URLSessionPartialMock.swift index f7662019f78..9bbbd66c244 100644 --- a/FirebaseRemoteConfig/Tests/HermeticAPI/URLSessionPartialMock.swift +++ b/FirebaseRemoteConfig/Tests/HermeticAPI/URLSessionPartialMock.swift @@ -45,10 +45,10 @@ class URLSessionMock: URLSession { etag = String(NSDate().timeIntervalSince1970) } let jsonData = try! JSONSerialization.data(withJSONObject: consoleValues) - let response = HTTPURLResponse.init(url: URL.init(fileURLWithPath: "fakeURL"), - statusCode: 200, - httpVersion: nil, - headerFields: ["etag": etag]) + let response = HTTPURLResponse(url: URL(fileURLWithPath: "fakeURL"), + statusCode: 200, + httpVersion: nil, + headerFields: ["etag": etag]) return URLSessionDataTaskMock { completionHandler(jsonData, response, nil) } From 9039bee7543b443e9918d211db6c31e1982c18a5 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Fri, 22 May 2020 08:22:22 -0700 Subject: [PATCH 10/16] FakeConsole does not need to be a singleton --- .../Tests/HermeticAPI/APITests.swift | 10 ++++++---- .../Tests/HermeticAPI/FakeConsole.swift | 12 ++++++++---- .../Tests/HermeticAPI/URLSessionPartialMock.swift | 7 ++++++- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift b/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift index 051a9eb54cc..5a05a253643 100644 --- a/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift +++ b/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift @@ -20,6 +20,7 @@ import XCTest class APITests: XCTestCase { var app: FirebaseApp! var config: RemoteConfig! + var fakeConsole: FakeConsole! override class func setUp() { FirebaseApp.configure() @@ -31,10 +32,11 @@ class APITests: XCTestCase { config = RemoteConfig.remoteConfig(app: app!) let settings = RemoteConfigSettings() settings.minimumFetchInterval = 0 + fakeConsole = FakeConsole(with: ["Key1": "Value1"]) config.configSettings = settings - config.configFetch.fetchSession = URLSessionMock() + config.configFetch.fetchSession = URLSessionMock(with: fakeConsole) config.configFetch.testWithoutNetwork = true - FakeConsole.config = ["Key1": "Value1"] + fakeConsole.config = ["Key1": "Value1"] // Uncomment for verbose debug logging. FirebaseConfiguration.shared.setLoggerLevel(FirebaseLoggerLevel.debug) @@ -43,7 +45,7 @@ class APITests: XCTestCase { override func tearDown() { app = nil config = nil - FakeConsole.empty() + fakeConsole.empty() super.tearDown() } @@ -149,7 +151,7 @@ class APITests: XCTestCase { waitForExpectations() // Simulate updating console. - FakeConsole.config = ["Key1": "Value2"] + fakeConsole.config = ["Key1": "Value2"] let expectation2 = self.expectation(description: #function + "2") config.fetch { status, error in diff --git a/FirebaseRemoteConfig/Tests/HermeticAPI/FakeConsole.swift b/FirebaseRemoteConfig/Tests/HermeticAPI/FakeConsole.swift index fb813867658..3ff7987d2ac 100644 --- a/FirebaseRemoteConfig/Tests/HermeticAPI/FakeConsole.swift +++ b/FirebaseRemoteConfig/Tests/HermeticAPI/FakeConsole.swift @@ -13,14 +13,18 @@ // limitations under the License. class FakeConsole { - static var config = [String: AnyHashable]() - static var last = [String: AnyHashable]() + var config = [String: AnyHashable]() + private var last = [String: AnyHashable]() - static func empty() { + init(with first: [String: AnyHashable]) { + config = first + } + + func empty() { config = [String: AnyHashable]() } - static func get() -> [String: AnyHashable] { + func get() -> [String: AnyHashable] { if config.count == 0 { last = config return [RCNFetchResponseKeyState: RCNFetchResponseKeyStateEmptyConfig] diff --git a/FirebaseRemoteConfig/Tests/HermeticAPI/URLSessionPartialMock.swift b/FirebaseRemoteConfig/Tests/HermeticAPI/URLSessionPartialMock.swift index 9bbbd66c244..68407005d96 100644 --- a/FirebaseRemoteConfig/Tests/HermeticAPI/URLSessionPartialMock.swift +++ b/FirebaseRemoteConfig/Tests/HermeticAPI/URLSessionPartialMock.swift @@ -30,6 +30,11 @@ class URLSessionDataTaskMock: URLSessionDataTask { class URLSessionMock: URLSession { typealias CompletionHandler = (Data?, URLResponse?, Error?) -> Void + private let fakeConsole: FakeConsole + init(with fakeConsole: FakeConsole) { + self.fakeConsole = fakeConsole + } + // Properties to control what gets returned to the URLSession callback. // error could also be added here. var data: Data? @@ -39,7 +44,7 @@ class URLSessionMock: URLSession { override func dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask { - let consoleValues = FakeConsole.get() + let consoleValues = fakeConsole.get() if etag == "" || consoleValues["state"] as! String == RCNFetchResponseKeyStateUpdate { // Time string in microseconds to insure a different string from previous change. etag = String(NSDate().timeIntervalSince1970) From db529b1cbd7f3de91e00c7129f2831105bbdbd1a Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Fri, 22 May 2020 08:26:14 -0700 Subject: [PATCH 11/16] cleanup --- FirebaseRemoteConfig/Sources/FIRRemoteConfig.m | 2 +- FirebaseRemoteConfig/Sources/RCNFetch.m | 2 +- FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/FirebaseRemoteConfig/Sources/FIRRemoteConfig.m b/FirebaseRemoteConfig/Sources/FIRRemoteConfig.m index 3f7f5e83b08..c665109a20c 100644 --- a/FirebaseRemoteConfig/Sources/FIRRemoteConfig.m +++ b/FirebaseRemoteConfig/Sources/FIRRemoteConfig.m @@ -623,7 +623,7 @@ - (void)setConfigSettings:(FIRRemoteConfigSettings *)configSettings { self->_settings.minimumFetchInterval = configSettings.minimumFetchInterval; self->_settings.fetchTimeout = configSettings.fetchTimeout; /// The NSURLSession needs to be recreated whenever the fetch timeout may be updated. - // [self->_configFetch recreateNetworkSession]; + [self->_configFetch recreateNetworkSession]; FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000067", @"Successfully set configSettings. Developer Mode: %@, Minimum Fetch Interval:%f, " @"Fetch timeout:%f", diff --git a/FirebaseRemoteConfig/Sources/RCNFetch.m b/FirebaseRemoteConfig/Sources/RCNFetch.m index 9cd0bec2f5d..58c90f05a7b 100644 --- a/FirebaseRemoteConfig/Sources/RCNFetch.m +++ b/FirebaseRemoteConfig/Sources/RCNFetch.m @@ -14,7 +14,7 @@ * limitations under the License. */ -#import "FirebaseRemoteConfig/Sources/Private//RCNConfigFetch.h" +#import "FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h" #import #import diff --git a/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift b/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift index 5a05a253643..3e25d64ef47 100644 --- a/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift +++ b/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift @@ -39,7 +39,7 @@ class APITests: XCTestCase { fakeConsole.config = ["Key1": "Value1"] // Uncomment for verbose debug logging. - FirebaseConfiguration.shared.setLoggerLevel(FirebaseLoggerLevel.debug) + // FirebaseConfiguration.shared.setLoggerLevel(FirebaseLoggerLevel.debug) } override func tearDown() { From 79b0e4195aafaca8dbf0fd38c85cf07c4d99f6f7 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Fri, 22 May 2020 15:16:34 -0700 Subject: [PATCH 12/16] skip pll tests on travis now that secrets are needed --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f41d140888c..7b3011b03ca 100644 --- a/.travis.yml +++ b/.travis.yml @@ -194,7 +194,7 @@ jobs: env: - PROJECT=RemoteConfig METHOD=pod-lib-lint script: - - travis_retry ./scripts/if_changed.sh ./scripts/pod_lib_lint.rb FirebaseRemoteConfig.podspec --platforms=ios + - travis_retry ./scripts/if_changed.sh ./scripts/pod_lib_lint.rb FirebaseRemoteConfig.podspec --platforms=ios --skip-tests - stage: test osx_image: xcode10.3 From 9745af3eee451788c0a8f3687531a915bb347231 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Sun, 24 May 2020 10:51:38 -0700 Subject: [PATCH 13/16] DRY up the test organization --- .github/workflows/remoteconfig.yml | 4 +- FirebaseRemoteConfig.podspec | 37 ++-- .../Tests/FakeConsole/FakeConsoleTests.swift | 100 ++++++++++ .../Bridging-Header.h | 0 .../FakeConsole.swift | 0 .../GoogleService-Info.plist | 2 +- .../URLSessionPartialMock.swift | 0 .../Tests/HermeticAPI/APITests.swift | 180 ------------------ .../Tests/SwiftAPI/APITests.swift | 58 +++++- scripts/build.sh | 6 +- 10 files changed, 182 insertions(+), 205 deletions(-) create mode 100644 FirebaseRemoteConfig/Tests/FakeConsole/FakeConsoleTests.swift rename FirebaseRemoteConfig/Tests/{HermeticAPI => FakeUtils}/Bridging-Header.h (100%) rename FirebaseRemoteConfig/Tests/{HermeticAPI => FakeUtils}/FakeConsole.swift (100%) rename FirebaseRemoteConfig/Tests/{HermeticAPI => FakeUtils}/GoogleService-Info.plist (96%) rename FirebaseRemoteConfig/Tests/{HermeticAPI => FakeUtils}/URLSessionPartialMock.swift (100%) delete mode 100644 FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift diff --git a/.github/workflows/remoteconfig.yml b/.github/workflows/remoteconfig.yml index 9d060ce42db..2a5aa88b541 100644 --- a/.github/workflows/remoteconfig.yml +++ b/.github/workflows/remoteconfig.yml @@ -31,8 +31,8 @@ jobs: FirebaseRemoteConfig/Tests/SwiftAPI/GoogleService-Info.plist "$plist_secret" - name: BuildAndUnitTest # can be replaced with pod lib lint with CocoaPods 1.10 run: scripts/third_party/travis/retry.sh scripts/build.sh RemoteConfig ${{ matrix.target }} unit - - name: Hermetic API Tests - run: scripts/third_party/travis/retry.sh scripts/build.sh RemoteConfig iOS hermetic + - name: Fake Console API Tests + run: scripts/third_party/travis/retry.sh scripts/build.sh RemoteConfig iOS fakeconsole - name: IntegrationTest if: matrix.target == 'iOS' run: ([ -z $plist_secret ] || scripts/third_party/travis/retry.sh scripts/build.sh RemoteConfig iOS integration) diff --git a/FirebaseRemoteConfig.podspec b/FirebaseRemoteConfig.podspec index 3e2f75175e2..2bf456c1c9a 100644 --- a/FirebaseRemoteConfig.podspec +++ b/FirebaseRemoteConfig.podspec @@ -70,23 +70,30 @@ app update. unit_tests.requires_arc = true end - s.test_spec 'swift-api' do |swift_api_tests| - swift_api_tests.platforms = {:ios => '8.0', :osx => '10.11', :tvos => '10.0'} - swift_api_tests.source_files = 'FirebaseRemoteConfig/Tests/SwiftAPI/*.swift' - swift_api_tests.requires_app_host = true - swift_api_tests.resources = - 'FirebaseRemoteConfig/Tests/SwiftAPI/GoogleService-Info.plist' + # Run Swift API tests on a real backend. + s.test_spec 'swift-api-tests' do |swift_api| + swift_api.platforms = {:ios => '8.0', :osx => '10.11', :tvos => '10.0'} + swift_api.source_files = 'FirebaseRemoteConfig/Tests/SwiftAPI/*.swift', + 'FirebaseRemoteConfig/Tests/FakeUtils/*.h', + 'FirebaseRemoteConfig/Tests/FakeUtils/*.swift' + swift_api.requires_app_host = true + swift_api.pod_target_xcconfig = { + 'SWIFT_OBJC_BRIDGING_HEADER' => '$(PODS_TARGET_SRCROOT)/FirebaseRemoteConfig/Tests/FakeUtils/Bridging-Header.h' + } + swift_api.resources = 'FirebaseRemoteConfig/Tests/SwiftAPI/GoogleService-Info.plist' end - s.test_spec 'hermetic-api' do |hermetic_api_tests| - hermetic_api_tests.platforms = {:ios => '8.0', :osx => '10.11', :tvos => '10.0'} - hermetic_api_tests.source_files = 'FirebaseRemoteConfig/Tests/HermeticAPI/*.swift', - 'FirebaseRemoteConfig/Tests/HermeticAPI/*.h' - hermetic_api_tests.requires_app_host = true - hermetic_api_tests.pod_target_xcconfig = { - 'SWIFT_OBJC_BRIDGING_HEADER' => '$(PODS_TARGET_SRCROOT)/FirebaseRemoteConfig/Tests/HermeticAPI/Bridging-Header.h' + # Run Swift API tests and tests requiring console changes on a Fake Console. + s.test_spec 'fake-console-tests' do |fake_console| + fake_console.platforms = {:ios => '8.0', :osx => '10.11', :tvos => '10.0'} + fake_console.source_files = 'FirebaseRemoteConfig/Tests/SwiftAPI/*.swift', + 'FirebaseRemoteConfig/Tests/FakeUtils/*.h', + 'FirebaseRemoteConfig/Tests/FakeUtils/*.swift', + 'FirebaseRemoteConfig/Tests/FakeConsole/*.swift' + fake_console.requires_app_host = true + fake_console.pod_target_xcconfig = { + 'SWIFT_OBJC_BRIDGING_HEADER' => '$(PODS_TARGET_SRCROOT)/FirebaseRemoteConfig/Tests/FakeUtils/Bridging-Header.h' } - hermetic_api_tests.resources = - 'FirebaseRemoteConfig/Tests/HermeticAPI/GoogleService-Info.plist' + fake_console.resources = 'FirebaseRemoteConfig/Tests/FakeUtils/GoogleService-Info.plist' end end diff --git a/FirebaseRemoteConfig/Tests/FakeConsole/FakeConsoleTests.swift b/FirebaseRemoteConfig/Tests/FakeConsole/FakeConsoleTests.swift new file mode 100644 index 00000000000..f24c46c7579 --- /dev/null +++ b/FirebaseRemoteConfig/Tests/FakeConsole/FakeConsoleTests.swift @@ -0,0 +1,100 @@ +// Copyright 2020 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 FirebaseCore +@testable import FirebaseRemoteConfig + +import XCTest + +class APITests: XCTestCase { + static var useFakeConfig: Bool! + var app: FirebaseApp! + var config: RemoteConfig! + var fakeConsole: FakeConsole! + + override class func setUp() { + if !(FirebaseApp.app() != nil) { + FirebaseApp.configure() + } + useFakeConfig = FirebaseApp.app()!.options.projectID == "FakeProject" + } + + override func setUp() { + super.setUp() + app = FirebaseApp.app() + config = RemoteConfig.remoteConfig(app: app!) + let settings = RemoteConfigSettings() + settings.minimumFetchInterval = 0 + config.configSettings = settings + fakeConsole = FakeConsole(with: ["Key1": "Value1"]) + config.configFetch.fetchSession = URLSessionMock(with: fakeConsole) + config.configFetch.testWithoutNetwork = true + + // Uncomment for verbose debug logging. + // FirebaseConfiguration.shared.setLoggerLevel(FirebaseLoggerLevel.debug) + } + + override func tearDown() { + app = nil + config = nil + fakeConsole.empty() + super.tearDown() + } + + // Contrast with testUnchangedActivateWillError in APITests.swift. + func testChangedActivateWillNotError() { + let expectation = self.expectation(description: #function) + config.fetch { status, error in + if let error = error { + XCTFail("Fetch Error \(error)") + } + XCTAssertEqual(status, RemoteConfigFetchStatus.success) + self.config.activate { error in + if let error = error { + print("Activate Error \(error)") + } + XCTAssertEqual(self.config["Key1"].stringValue, "Value1") + expectation.fulfill() + } + } + waitForExpectations() + + // Simulate updating console. + fakeConsole.config = ["Key1": "Value2"] + + let expectation2 = self.expectation(description: #function + "2") + config.fetch { status, error in + if let error = error { + XCTFail("Fetch Error \(error)") + } + XCTAssertEqual(status, RemoteConfigFetchStatus.success) + self.config.activate { error in + XCTAssertNil(error) + XCTAssertEqual(self.config["Key1"].stringValue, "Value2") + expectation2.fulfill() + } + } + waitForExpectations() + } + + private func waitForExpectations() { + let kFIRStorageIntegrationTestTimeout = 10.0 + waitForExpectations(timeout: kFIRStorageIntegrationTestTimeout, + handler: { (error) -> Void in + if let error = error { + print(error) + } + }) + } +} diff --git a/FirebaseRemoteConfig/Tests/HermeticAPI/Bridging-Header.h b/FirebaseRemoteConfig/Tests/FakeUtils/Bridging-Header.h similarity index 100% rename from FirebaseRemoteConfig/Tests/HermeticAPI/Bridging-Header.h rename to FirebaseRemoteConfig/Tests/FakeUtils/Bridging-Header.h diff --git a/FirebaseRemoteConfig/Tests/HermeticAPI/FakeConsole.swift b/FirebaseRemoteConfig/Tests/FakeUtils/FakeConsole.swift similarity index 100% rename from FirebaseRemoteConfig/Tests/HermeticAPI/FakeConsole.swift rename to FirebaseRemoteConfig/Tests/FakeUtils/FakeConsole.swift diff --git a/FirebaseRemoteConfig/Tests/HermeticAPI/GoogleService-Info.plist b/FirebaseRemoteConfig/Tests/FakeUtils/GoogleService-Info.plist similarity index 96% rename from FirebaseRemoteConfig/Tests/HermeticAPI/GoogleService-Info.plist rename to FirebaseRemoteConfig/Tests/FakeUtils/GoogleService-Info.plist index 884aa6c6f11..b94df6544b3 100644 --- a/FirebaseRemoteConfig/Tests/HermeticAPI/GoogleService-Info.plist +++ b/FirebaseRemoteConfig/Tests/FakeUtils/GoogleService-Info.plist @@ -19,7 +19,7 @@ BUNDLE_ID org.cocoapods.AppHost-FirebaseRemoteConfig-Unit-Tests PROJECT_ID - abc-xyz-123 + FakeProject DATABASE_URL https://abc-xyz-123.firebaseio.com STORAGE_BUCKET diff --git a/FirebaseRemoteConfig/Tests/HermeticAPI/URLSessionPartialMock.swift b/FirebaseRemoteConfig/Tests/FakeUtils/URLSessionPartialMock.swift similarity index 100% rename from FirebaseRemoteConfig/Tests/HermeticAPI/URLSessionPartialMock.swift rename to FirebaseRemoteConfig/Tests/FakeUtils/URLSessionPartialMock.swift diff --git a/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift b/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift deleted file mode 100644 index 3e25d64ef47..00000000000 --- a/FirebaseRemoteConfig/Tests/HermeticAPI/APITests.swift +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright 2020 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 FirebaseCore -@testable import FirebaseRemoteConfig - -import XCTest - -class APITests: XCTestCase { - var app: FirebaseApp! - var config: RemoteConfig! - var fakeConsole: FakeConsole! - - override class func setUp() { - FirebaseApp.configure() - } - - override func setUp() { - super.setUp() - app = FirebaseApp.app() - config = RemoteConfig.remoteConfig(app: app!) - let settings = RemoteConfigSettings() - settings.minimumFetchInterval = 0 - fakeConsole = FakeConsole(with: ["Key1": "Value1"]) - config.configSettings = settings - config.configFetch.fetchSession = URLSessionMock(with: fakeConsole) - config.configFetch.testWithoutNetwork = true - fakeConsole.config = ["Key1": "Value1"] - - // Uncomment for verbose debug logging. - // FirebaseConfiguration.shared.setLoggerLevel(FirebaseLoggerLevel.debug) - } - - override func tearDown() { - app = nil - config = nil - fakeConsole.empty() - super.tearDown() - } - - func testFetchThenActivate() { - let expectation = self.expectation(description: #function) - config.fetch { status, error in - if let error = error { - XCTFail("Fetch Error \(error)") - } - XCTAssertEqual(status, RemoteConfigFetchStatus.success) - self.config.activate { error in - if let error = error { - // This API returns an error if the config was unchanged. - print("Activate Error \(error)") - } - XCTAssertEqual(self.config["Key1"].stringValue, "Value1") - expectation.fulfill() - } - } - waitForExpectations() - } - - func testFetchWithExpirationThenActivate() { - let expectation = self.expectation(description: #function) - config.fetch(withExpirationDuration: 0) { status, error in - if let error = error { - XCTFail("Fetch Error \(error)") - } - XCTAssertEqual(status, RemoteConfigFetchStatus.success) - self.config.activate { error in - if let error = error { - // This API returns an error if the config was unchanged. - print("Activate Error \(error)") - } - XCTAssertEqual(self.config["Key1"].stringValue, "Value1") - expectation.fulfill() - } - } - waitForExpectations() - } - - func testFetchAndActivate() { - let expectation = self.expectation(description: #function) - config.fetchAndActivate { status, error in - if let error = error { - XCTFail("Fetch and Activate Error \(error)") - } - XCTAssertEqual(self.config["Key1"].stringValue, "Value1") - expectation.fulfill() - } - waitForExpectations() - } - - func testUnchangedActivateWillError() { - let expectation = self.expectation(description: #function) - config.fetch { status, error in - if let error = error { - XCTFail("Fetch Error \(error)") - } - XCTAssertEqual(status, RemoteConfigFetchStatus.success) - self.config.activate { error in - if let error = error { - print("Activate Error \(error)") - } - XCTAssertEqual(self.config["Key1"].stringValue, "Value1") - expectation.fulfill() - } - } - waitForExpectations() - let expectation2 = self.expectation(description: #function + "2") - config.fetch { status, error in - if let error = error { - XCTFail("Fetch Error \(error)") - } - XCTAssertEqual(status, RemoteConfigFetchStatus.success) - self.config.activate { error in - XCTAssertNotNil(error) - if let error = error { - XCTAssertEqual((error as NSError).code, RemoteConfigError.internalError.rawValue) - } - XCTAssertEqual(self.config["Key1"].stringValue, "Value1") - expectation2.fulfill() - } - } - waitForExpectations() - } - - func testChangedActivateWillNotError() { - let expectation = self.expectation(description: #function) - config.fetch { status, error in - if let error = error { - XCTFail("Fetch Error \(error)") - } - XCTAssertEqual(status, RemoteConfigFetchStatus.success) - self.config.activate { error in - if let error = error { - print("Activate Error \(error)") - } - XCTAssertEqual(self.config["Key1"].stringValue, "Value1") - expectation.fulfill() - } - } - waitForExpectations() - - // Simulate updating console. - fakeConsole.config = ["Key1": "Value2"] - - let expectation2 = self.expectation(description: #function + "2") - config.fetch { status, error in - if let error = error { - XCTFail("Fetch Error \(error)") - } - XCTAssertEqual(status, RemoteConfigFetchStatus.success) - self.config.activate { error in - XCTAssertNil(error) - XCTAssertEqual(self.config["Key1"].stringValue, "Value2") - expectation2.fulfill() - } - } - waitForExpectations() - } - - private func waitForExpectations() { - let kFIRStorageIntegrationTestTimeout = 100.0 - waitForExpectations(timeout: kFIRStorageIntegrationTestTimeout, - handler: { (error) -> Void in - if let error = error { - print(error) - } - }) - } -} diff --git a/FirebaseRemoteConfig/Tests/SwiftAPI/APITests.swift b/FirebaseRemoteConfig/Tests/SwiftAPI/APITests.swift index b7392346c46..aa6a8bd5a3a 100644 --- a/FirebaseRemoteConfig/Tests/SwiftAPI/APITests.swift +++ b/FirebaseRemoteConfig/Tests/SwiftAPI/APITests.swift @@ -13,15 +13,21 @@ // limitations under the License. import FirebaseCore -import FirebaseRemoteConfig +@testable import FirebaseRemoteConfig + import XCTest -class APITests: XCTestCase { +class FakeConsoleTests: XCTestCase { + static var useFakeConfig: Bool! var app: FirebaseApp! var config: RemoteConfig! + var fakeConsole: FakeConsole! override class func setUp() { - FirebaseApp.configure() + if !(FirebaseApp.app() != nil) { + FirebaseApp.configure() + } + useFakeConfig = FirebaseApp.app()!.options.projectID == "FakeProject" } override func setUp() { @@ -31,13 +37,22 @@ class APITests: XCTestCase { let settings = RemoteConfigSettings() settings.minimumFetchInterval = 0 config.configSettings = settings + if APITests.useFakeConfig { + fakeConsole = FakeConsole(with: ["Key1": "Value1"]) + config.configFetch.fetchSession = URLSessionMock(with: fakeConsole) + config.configFetch.testWithoutNetwork = true + } - FirebaseConfiguration.shared.setLoggerLevel(FirebaseLoggerLevel.debug) + // Uncomment for verbose debug logging. + // FirebaseConfiguration.shared.setLoggerLevel(FirebaseLoggerLevel.debug) } override func tearDown() { app = nil config = nil + if APITests.useFakeConfig { + fakeConsole.empty() + } super.tearDown() } @@ -91,6 +106,41 @@ class APITests: XCTestCase { waitForExpectations() } + // Contrast with testChangedActivateWillNotError in FakeConsole.swift. + func testUnchangedActivateWillError() { + let expectation = self.expectation(description: #function) + config.fetch { status, error in + if let error = error { + XCTFail("Fetch Error \(error)") + } + XCTAssertEqual(status, RemoteConfigFetchStatus.success) + self.config.activate { error in + if let error = error { + print("Activate Error \(error)") + } + XCTAssertEqual(self.config["Key1"].stringValue, "Value1") + expectation.fulfill() + } + } + waitForExpectations() + let expectation2 = self.expectation(description: #function + "2") + config.fetch { status, error in + if let error = error { + XCTFail("Fetch Error \(error)") + } + XCTAssertEqual(status, RemoteConfigFetchStatus.success) + self.config.activate { error in + XCTAssertNotNil(error) + if let error = error { + XCTAssertEqual((error as NSError).code, RemoteConfigError.internalError.rawValue) + } + XCTAssertEqual(self.config["Key1"].stringValue, "Value1") + expectation2.fulfill() + } + } + waitForExpectations() + } + private func waitForExpectations() { let kFIRStorageIntegrationTestTimeout = 10.0 waitForExpectations(timeout: kFIRStorageIntegrationTestTimeout, diff --git a/scripts/build.sh b/scripts/build.sh index d62ce9bb4c2..04dc9f5265d 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -434,11 +434,11 @@ case "$product-$platform-$method" in test ;; - RemoteConfig-*-hermetic) + RemoteConfig-*-fakeconsole) pod_gen FirebaseRemoteConfig.podspec --platforms="${gen_platform}" RunXcodebuild \ -workspace 'gen/FirebaseRemoteConfig/FirebaseRemoteConfig.xcworkspace' \ - -scheme "FirebaseRemoteConfig-Unit-hermetic-api" \ + -scheme "FirebaseRemoteConfig-Unit-fake-console-tests" \ "${xcb_flags[@]}" \ build \ test @@ -448,7 +448,7 @@ case "$product-$platform-$method" in pod_gen FirebaseRemoteConfig.podspec --platforms="${gen_platform}" RunXcodebuild \ -workspace 'gen/FirebaseRemoteConfig/FirebaseRemoteConfig.xcworkspace' \ - -scheme "FirebaseRemoteConfig-Unit-swift-api" \ + -scheme "FirebaseRemoteConfig-Unit-swift-api-tests" \ "${xcb_flags[@]}" \ build \ test From 8b078f4faa9cd1e991bc9de51e5bec333aa92181 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Sun, 24 May 2020 11:10:36 -0700 Subject: [PATCH 14/16] fix names --- FirebaseRemoteConfig/Tests/FakeConsole/FakeConsoleTests.swift | 2 +- FirebaseRemoteConfig/Tests/SwiftAPI/APITests.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/FirebaseRemoteConfig/Tests/FakeConsole/FakeConsoleTests.swift b/FirebaseRemoteConfig/Tests/FakeConsole/FakeConsoleTests.swift index f24c46c7579..050553ae1fc 100644 --- a/FirebaseRemoteConfig/Tests/FakeConsole/FakeConsoleTests.swift +++ b/FirebaseRemoteConfig/Tests/FakeConsole/FakeConsoleTests.swift @@ -17,7 +17,7 @@ import FirebaseCore import XCTest -class APITests: XCTestCase { +class FakeConsoleTests: XCTestCase { static var useFakeConfig: Bool! var app: FirebaseApp! var config: RemoteConfig! diff --git a/FirebaseRemoteConfig/Tests/SwiftAPI/APITests.swift b/FirebaseRemoteConfig/Tests/SwiftAPI/APITests.swift index aa6a8bd5a3a..91fccfcde5b 100644 --- a/FirebaseRemoteConfig/Tests/SwiftAPI/APITests.swift +++ b/FirebaseRemoteConfig/Tests/SwiftAPI/APITests.swift @@ -17,7 +17,7 @@ import FirebaseCore import XCTest -class FakeConsoleTests: XCTestCase { +class APITests: XCTestCase { static var useFakeConfig: Bool! var app: FirebaseApp! var config: RemoteConfig! From 190030c985f0ee9c130c8bf69e582663100f98d0 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Tue, 26 May 2020 11:58:15 -0700 Subject: [PATCH 15/16] Use OCMock to eliminate testWithoutNetwork change in library --- FirebaseRemoteConfig.podspec | 6 +- .../Sources/Private/RCNConfigFetch.h | 3 - FirebaseRemoteConfig/Sources/RCNFetch.m | 7 --- .../Tests/FakeConsole/FakeConsoleTests.swift | 32 +--------- .../Tests/FakeUtils/Bridging-Header.h | 1 + .../Tests/FakeUtils/FakeConsole.swift | 4 +- .../Tests/FakeUtils/FetchMocks.h | 24 +++++++ .../Tests/FakeUtils/FetchMocks.m | 35 +++++++++++ .../Tests/SwiftAPI/APITestBase.swift | 63 +++++++++++++++++++ .../Tests/SwiftAPI/APITests.swift | 35 +---------- 10 files changed, 133 insertions(+), 77 deletions(-) create mode 100644 FirebaseRemoteConfig/Tests/FakeUtils/FetchMocks.h create mode 100644 FirebaseRemoteConfig/Tests/FakeUtils/FetchMocks.m create mode 100644 FirebaseRemoteConfig/Tests/SwiftAPI/APITestBase.swift diff --git a/FirebaseRemoteConfig.podspec b/FirebaseRemoteConfig.podspec index 2bf456c1c9a..86df76ba1a8 100644 --- a/FirebaseRemoteConfig.podspec +++ b/FirebaseRemoteConfig.podspec @@ -74,20 +74,21 @@ app update. s.test_spec 'swift-api-tests' do |swift_api| swift_api.platforms = {:ios => '8.0', :osx => '10.11', :tvos => '10.0'} swift_api.source_files = 'FirebaseRemoteConfig/Tests/SwiftAPI/*.swift', - 'FirebaseRemoteConfig/Tests/FakeUtils/*.h', + 'FirebaseRemoteConfig/Tests/FakeUtils/*.[hm]', 'FirebaseRemoteConfig/Tests/FakeUtils/*.swift' swift_api.requires_app_host = true swift_api.pod_target_xcconfig = { 'SWIFT_OBJC_BRIDGING_HEADER' => '$(PODS_TARGET_SRCROOT)/FirebaseRemoteConfig/Tests/FakeUtils/Bridging-Header.h' } swift_api.resources = 'FirebaseRemoteConfig/Tests/SwiftAPI/GoogleService-Info.plist' + swift_api.dependency 'OCMock' end # Run Swift API tests and tests requiring console changes on a Fake Console. s.test_spec 'fake-console-tests' do |fake_console| fake_console.platforms = {:ios => '8.0', :osx => '10.11', :tvos => '10.0'} fake_console.source_files = 'FirebaseRemoteConfig/Tests/SwiftAPI/*.swift', - 'FirebaseRemoteConfig/Tests/FakeUtils/*.h', + 'FirebaseRemoteConfig/Tests/FakeUtils/*.[hm]', 'FirebaseRemoteConfig/Tests/FakeUtils/*.swift', 'FirebaseRemoteConfig/Tests/FakeConsole/*.swift' fake_console.requires_app_host = true @@ -95,5 +96,6 @@ app update. 'SWIFT_OBJC_BRIDGING_HEADER' => '$(PODS_TARGET_SRCROOT)/FirebaseRemoteConfig/Tests/FakeUtils/Bridging-Header.h' } fake_console.resources = 'FirebaseRemoteConfig/Tests/FakeUtils/GoogleService-Info.plist' + fake_console.dependency 'OCMock' end end diff --git a/FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h b/FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h index 7cb44d8d480..948c4d6ed99 100644 --- a/FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h +++ b/FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h @@ -56,9 +56,6 @@ typedef void (^RCNConfigFetcherCompletion)(NSData *data, NSURLResponse *response /// Provide fetchSession for tests to override. @property(nonatomic, readwrite, strong, nonnull) NSURLSession *fetchSession; -/// Flag for tests to disable network accesses. -@property(nonatomic, readwrite) BOOL testWithoutNetwork; - NS_ASSUME_NONNULL_END @end diff --git a/FirebaseRemoteConfig/Sources/RCNFetch.m b/FirebaseRemoteConfig/Sources/RCNFetch.m index 58c90f05a7b..24f962cdfa3 100644 --- a/FirebaseRemoteConfig/Sources/RCNFetch.m +++ b/FirebaseRemoteConfig/Sources/RCNFetch.m @@ -109,9 +109,6 @@ - (instancetype)initWithContent:(RCNConfigContent *)content /// Force a new NSURLSession creation for updated config. - (void)recreateNetworkSession { - if (self.testWithoutNetwork) { - return; - } if (_fetchSession) { [_fetchSession invalidateAndCancel]; } @@ -205,10 +202,6 @@ - (NSString *)FIRAppNameFromFullyQualifiedNamespace { /// requests to work.(b/14751422). - (void)refreshInstallationsTokenWithCompletionHandler: (FIRRemoteConfigFetchCompletion)completionHandler { - if (self.testWithoutNetwork) { - [self doFetchCall:completionHandler]; - return; - } FIRInstallations *installations = [FIRInstallations installationsWithApp:[FIRApp appNamed:[self FIRAppNameFromFullyQualifiedNamespace]]]; if (!installations || !_options.GCMSenderID) { diff --git a/FirebaseRemoteConfig/Tests/FakeConsole/FakeConsoleTests.swift b/FirebaseRemoteConfig/Tests/FakeConsole/FakeConsoleTests.swift index 050553ae1fc..e1f54686f89 100644 --- a/FirebaseRemoteConfig/Tests/FakeConsole/FakeConsoleTests.swift +++ b/FirebaseRemoteConfig/Tests/FakeConsole/FakeConsoleTests.swift @@ -17,39 +17,11 @@ import FirebaseCore import XCTest -class FakeConsoleTests: XCTestCase { - static var useFakeConfig: Bool! - var app: FirebaseApp! - var config: RemoteConfig! - var fakeConsole: FakeConsole! - - override class func setUp() { - if !(FirebaseApp.app() != nil) { - FirebaseApp.configure() - } - useFakeConfig = FirebaseApp.app()!.options.projectID == "FakeProject" - } +class FakeConsoleTests: APITestBase { override func setUp() { super.setUp() - app = FirebaseApp.app() - config = RemoteConfig.remoteConfig(app: app!) - let settings = RemoteConfigSettings() - settings.minimumFetchInterval = 0 - config.configSettings = settings - fakeConsole = FakeConsole(with: ["Key1": "Value1"]) - config.configFetch.fetchSession = URLSessionMock(with: fakeConsole) - config.configFetch.testWithoutNetwork = true - - // Uncomment for verbose debug logging. - // FirebaseConfiguration.shared.setLoggerLevel(FirebaseLoggerLevel.debug) - } - - override func tearDown() { - app = nil - config = nil - fakeConsole.empty() - super.tearDown() + fakeConsole.config = ["Key1": "Value1"] } // Contrast with testUnchangedActivateWillError in APITests.swift. diff --git a/FirebaseRemoteConfig/Tests/FakeUtils/Bridging-Header.h b/FirebaseRemoteConfig/Tests/FakeUtils/Bridging-Header.h index c3f1c728259..fb9f56f32ca 100644 --- a/FirebaseRemoteConfig/Tests/FakeUtils/Bridging-Header.h +++ b/FirebaseRemoteConfig/Tests/FakeUtils/Bridging-Header.h @@ -14,3 +14,4 @@ #import #import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h" +#import "FetchMocks.h" diff --git a/FirebaseRemoteConfig/Tests/FakeUtils/FakeConsole.swift b/FirebaseRemoteConfig/Tests/FakeUtils/FakeConsole.swift index 3ff7987d2ac..34285c00e76 100644 --- a/FirebaseRemoteConfig/Tests/FakeUtils/FakeConsole.swift +++ b/FirebaseRemoteConfig/Tests/FakeUtils/FakeConsole.swift @@ -16,8 +16,8 @@ class FakeConsole { var config = [String: AnyHashable]() private var last = [String: AnyHashable]() - init(with first: [String: AnyHashable]) { - config = first + init() { + config = [String: AnyHashable]() } func empty() { diff --git a/FirebaseRemoteConfig/Tests/FakeUtils/FetchMocks.h b/FirebaseRemoteConfig/Tests/FakeUtils/FetchMocks.h new file mode 100644 index 00000000000..e634e664208 --- /dev/null +++ b/FirebaseRemoteConfig/Tests/FakeUtils/FetchMocks.h @@ -0,0 +1,24 @@ +// Copyright 2020 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 "FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FetchMocks : NSObject ++(RCNConfigFetch *)mockFetch:(RCNConfigFetch *)fetch; +@end + +NS_ASSUME_NONNULL_END diff --git a/FirebaseRemoteConfig/Tests/FakeUtils/FetchMocks.m b/FirebaseRemoteConfig/Tests/FakeUtils/FetchMocks.m new file mode 100644 index 00000000000..2845e0fae44 --- /dev/null +++ b/FirebaseRemoteConfig/Tests/FakeUtils/FetchMocks.m @@ -0,0 +1,35 @@ +// Copyright 2020 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 "FetchMocks.h" +#import "FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h" + +@interface RCNConfigFetch (ExposedForTest) +- (void)refreshInstallationsTokenWithCompletionHandler: + (FIRRemoteConfigFetchCompletion)completionHandler; +- (void)doFetchCall:(FIRRemoteConfigFetchCompletion)completionHandler; +@end + +@implementation FetchMocks + ++(RCNConfigFetch *)mockFetch:(RCNConfigFetch *)fetch { + RCNConfigFetch *mock = OCMPartialMock(fetch); + OCMStub([mock recreateNetworkSession]).andDo(nil); + OCMStub([mock refreshInstallationsTokenWithCompletionHandler:[OCMArg any]]).andCall(mock, @selector(doFetchCall:)); + return mock; +} + +@end diff --git a/FirebaseRemoteConfig/Tests/SwiftAPI/APITestBase.swift b/FirebaseRemoteConfig/Tests/SwiftAPI/APITestBase.swift new file mode 100644 index 00000000000..6d16c2d6470 --- /dev/null +++ b/FirebaseRemoteConfig/Tests/SwiftAPI/APITestBase.swift @@ -0,0 +1,63 @@ +// Copyright 2020 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 FirebaseCore +@testable import FirebaseRemoteConfig + +import XCTest + +class APITestBase: XCTestCase { + static var useFakeConfig: Bool! + static var mockedFetch: Bool! + var app: FirebaseApp! + var config: RemoteConfig! + var fakeConsole: FakeConsole! + + override class func setUp() { + if FirebaseApp.app() == nil { + FirebaseApp.configure() + APITests.mockedFetch = false + } + useFakeConfig = FirebaseApp.app()!.options.projectID == "FakeProject" + } + + override func setUp() { + super.setUp() + app = FirebaseApp.app() + config = RemoteConfig.remoteConfig(app: app!) + let settings = RemoteConfigSettings() + settings.minimumFetchInterval = 0 + config.configSettings = settings + if APITests.useFakeConfig { + fakeConsole = FakeConsole() + config.configFetch.fetchSession = URLSessionMock(with: fakeConsole) + if !APITests.mockedFetch { + APITests.mockedFetch = true + config.configFetch = FetchMocks.mockFetch(config.configFetch) + } + } + + // Uncomment for verbose debug logging. + // FirebaseConfiguration.shared.setLoggerLevel(FirebaseLoggerLevel.debug) + } + + override func tearDown() { + if APITests.useFakeConfig { + fakeConsole.empty() + } + app = nil + config = nil + super.tearDown() + } +} diff --git a/FirebaseRemoteConfig/Tests/SwiftAPI/APITests.swift b/FirebaseRemoteConfig/Tests/SwiftAPI/APITests.swift index 91fccfcde5b..9398bd56c4b 100644 --- a/FirebaseRemoteConfig/Tests/SwiftAPI/APITests.swift +++ b/FirebaseRemoteConfig/Tests/SwiftAPI/APITests.swift @@ -17,43 +17,12 @@ import FirebaseCore import XCTest -class APITests: XCTestCase { - static var useFakeConfig: Bool! - var app: FirebaseApp! - var config: RemoteConfig! - var fakeConsole: FakeConsole! - - override class func setUp() { - if !(FirebaseApp.app() != nil) { - FirebaseApp.configure() - } - useFakeConfig = FirebaseApp.app()!.options.projectID == "FakeProject" - } - +class APITests: APITestBase { override func setUp() { super.setUp() - app = FirebaseApp.app() - config = RemoteConfig.remoteConfig(app: app!) - let settings = RemoteConfigSettings() - settings.minimumFetchInterval = 0 - config.configSettings = settings - if APITests.useFakeConfig { - fakeConsole = FakeConsole(with: ["Key1": "Value1"]) - config.configFetch.fetchSession = URLSessionMock(with: fakeConsole) - config.configFetch.testWithoutNetwork = true - } - - // Uncomment for verbose debug logging. - // FirebaseConfiguration.shared.setLoggerLevel(FirebaseLoggerLevel.debug) - } - - override func tearDown() { - app = nil - config = nil if APITests.useFakeConfig { - fakeConsole.empty() + fakeConsole.config = ["Key1": "Value1"] } - super.tearDown() } func testFetchThenActivate() { From 98d3dab31c0d4de521f6dbb2b3b30b00b439269c Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Tue, 26 May 2020 12:00:42 -0700 Subject: [PATCH 16/16] style --- .../Tests/FakeConsole/FakeConsoleTests.swift | 1 - FirebaseRemoteConfig/Tests/FakeUtils/Bridging-Header.h | 2 +- FirebaseRemoteConfig/Tests/FakeUtils/FetchMocks.h | 2 +- FirebaseRemoteConfig/Tests/FakeUtils/FetchMocks.m | 7 ++++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/FirebaseRemoteConfig/Tests/FakeConsole/FakeConsoleTests.swift b/FirebaseRemoteConfig/Tests/FakeConsole/FakeConsoleTests.swift index e1f54686f89..cd6ea549062 100644 --- a/FirebaseRemoteConfig/Tests/FakeConsole/FakeConsoleTests.swift +++ b/FirebaseRemoteConfig/Tests/FakeConsole/FakeConsoleTests.swift @@ -18,7 +18,6 @@ import FirebaseCore import XCTest class FakeConsoleTests: APITestBase { - override func setUp() { super.setUp() fakeConsole.config = ["Key1": "Value1"] diff --git a/FirebaseRemoteConfig/Tests/FakeUtils/Bridging-Header.h b/FirebaseRemoteConfig/Tests/FakeUtils/Bridging-Header.h index fb9f56f32ca..5e326b93c11 100644 --- a/FirebaseRemoteConfig/Tests/FakeUtils/Bridging-Header.h +++ b/FirebaseRemoteConfig/Tests/FakeUtils/Bridging-Header.h @@ -13,5 +13,5 @@ // limitations under the License. #import -#import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h" #import "FetchMocks.h" +#import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h" diff --git a/FirebaseRemoteConfig/Tests/FakeUtils/FetchMocks.h b/FirebaseRemoteConfig/Tests/FakeUtils/FetchMocks.h index e634e664208..f889e689936 100644 --- a/FirebaseRemoteConfig/Tests/FakeUtils/FetchMocks.h +++ b/FirebaseRemoteConfig/Tests/FakeUtils/FetchMocks.h @@ -18,7 +18,7 @@ NS_ASSUME_NONNULL_BEGIN @interface FetchMocks : NSObject -+(RCNConfigFetch *)mockFetch:(RCNConfigFetch *)fetch; ++ (RCNConfigFetch *)mockFetch:(RCNConfigFetch *)fetch; @end NS_ASSUME_NONNULL_END diff --git a/FirebaseRemoteConfig/Tests/FakeUtils/FetchMocks.m b/FirebaseRemoteConfig/Tests/FakeUtils/FetchMocks.m index 2845e0fae44..e8510eacd9d 100644 --- a/FirebaseRemoteConfig/Tests/FakeUtils/FetchMocks.m +++ b/FirebaseRemoteConfig/Tests/FakeUtils/FetchMocks.m @@ -19,16 +19,17 @@ @interface RCNConfigFetch (ExposedForTest) - (void)refreshInstallationsTokenWithCompletionHandler: - (FIRRemoteConfigFetchCompletion)completionHandler; + (FIRRemoteConfigFetchCompletion)completionHandler; - (void)doFetchCall:(FIRRemoteConfigFetchCompletion)completionHandler; @end @implementation FetchMocks -+(RCNConfigFetch *)mockFetch:(RCNConfigFetch *)fetch { ++ (RCNConfigFetch *)mockFetch:(RCNConfigFetch *)fetch { RCNConfigFetch *mock = OCMPartialMock(fetch); OCMStub([mock recreateNetworkSession]).andDo(nil); - OCMStub([mock refreshInstallationsTokenWithCompletionHandler:[OCMArg any]]).andCall(mock, @selector(doFetchCall:)); + OCMStub([mock refreshInstallationsTokenWithCompletionHandler:[OCMArg any]]) + .andCall(mock, @selector(doFetchCall:)); return mock; }