aboutsummaryrefslogtreecommitdiffhomepage
path: root/Example/Core/Tests
diff options
context:
space:
mode:
authorGravatar Paul Beusterien <paulbeusterien@google.com>2017-05-15 12:27:07 -0700
committerGravatar Paul Beusterien <paulbeusterien@google.com>2017-05-15 12:27:07 -0700
commit98ba64449a632518bd2b86fe8d927f4a960d3ddc (patch)
tree131d9c4272fa6179fcda6c5a33fcb3b1bd57ad2e /Example/Core/Tests
parent32461366c9e204a527ca05e6e9b9404a2454ac51 (diff)
Initial
Diffstat (limited to 'Example/Core/Tests')
-rw-r--r--Example/Core/Tests/FIRAppAssociationRegistrationUnitTests.m193
-rw-r--r--Example/Core/Tests/FIRAppTest.m582
-rw-r--r--Example/Core/Tests/FIRBundleUtilTest.m86
-rw-r--r--Example/Core/Tests/FIRConfigurationTest.m31
-rw-r--r--Example/Core/Tests/FIRLoggerTest.m265
-rw-r--r--Example/Core/Tests/FIROptionsTest.m468
-rw-r--r--Example/Core/Tests/FIRTestCase.h45
-rw-r--r--Example/Core/Tests/FIRTestCase.m47
-rw-r--r--Example/Core/Tests/Tests-Info.plist22
9 files changed, 1739 insertions, 0 deletions
diff --git a/Example/Core/Tests/FIRAppAssociationRegistrationUnitTests.m b/Example/Core/Tests/FIRAppAssociationRegistrationUnitTests.m
new file mode 100644
index 0000000..9649c99
--- /dev/null
+++ b/Example/Core/Tests/FIRAppAssociationRegistrationUnitTests.m
@@ -0,0 +1,193 @@
+// Copyright 2017 Google
+//
+// 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 <XCTest/XCTest.h>
+
+#import "FIRAppAssociationRegistration.h"
+
+/** @var kKey
+ @brief A unique string key.
+ */
+static NSString *kKey = @"key";
+
+/** @var kKey1
+ @brief A unique string key.
+ */
+static NSString *kKey1 = @"key1";
+
+/** @var kKey2
+ @brief A unique string key.
+ */
+static NSString *kKey2 = @"key2";
+
+/** @var gCreateNewObject
+ @brief A block that returns a new object everytime it is called.
+ */
+static id _Nullable (^gCreateNewObject)() = ^id _Nullable() {
+ return [[NSObject alloc] init];
+};
+
+/** @class FIRAppAssociationRegistrationTests
+ @brief Tests for @c FIRAppAssociationRegistration
+ */
+@interface FIRAppAssociationRegistrationTests : XCTestCase
+@end
+
+@implementation FIRAppAssociationRegistrationTests
+
+- (void)testPassObject {
+ id host = gCreateNewObject();
+ id obj = gCreateNewObject();
+ id result = [FIRAppAssociationRegistration registeredObjectWithHost:host
+ key:kKey
+ creationBlock:^id _Nullable() {
+ return obj;
+ }];
+ XCTAssertEqual(obj, result);
+}
+
+- (void)testPassNil {
+ id host = gCreateNewObject();
+ id obj = [FIRAppAssociationRegistration registeredObjectWithHost:host
+ key:kKey
+ creationBlock:^id _Nullable() {
+ return nil;
+ }];
+ XCTAssertNil(obj);
+}
+
+- (void)testObjectOwnership {
+ __weak id weakHost;
+ __block __weak id weakObj;
+ @autoreleasepool {
+ id host = gCreateNewObject();
+ weakHost = host;
+ [FIRAppAssociationRegistration registeredObjectWithHost:host
+ key:kKey
+ creationBlock:^id _Nullable() {
+ id obj = gCreateNewObject();
+ weakObj = obj;
+ return obj;
+ }];
+ // Verify that neither the host nor the object is released yet, i.e., the host owns the object
+ // because nothing else retains the object.
+ XCTAssertNotNil(weakHost);
+ XCTAssertNotNil(weakObj);
+ }
+ // Verify that both the host and the object are released upon exit of the autorelease pool,
+ // i.e., the host is the sole owner of the object.
+ XCTAssertNil(weakHost);
+ XCTAssertNil(weakObj);
+}
+
+- (void)testSameHostSameKey {
+ id host = gCreateNewObject();
+ id obj1 = [FIRAppAssociationRegistration registeredObjectWithHost:host
+ key:kKey
+ creationBlock:gCreateNewObject];
+ id obj2 = [FIRAppAssociationRegistration registeredObjectWithHost:host
+ key:kKey
+ creationBlock:gCreateNewObject];
+ XCTAssertEqual(obj1, obj2);
+}
+
+- (void)testSameHostDifferentKey {
+ id host = gCreateNewObject();
+ id obj1 = [FIRAppAssociationRegistration registeredObjectWithHost:host
+ key:kKey1
+ creationBlock:gCreateNewObject];
+ id obj2 = [FIRAppAssociationRegistration registeredObjectWithHost:host
+ key:kKey2
+ creationBlock:gCreateNewObject];
+ XCTAssertNotEqual(obj1, obj2);
+}
+
+- (void)testDifferentHostSameKey {
+ id host1 = gCreateNewObject();
+ id obj1 = [FIRAppAssociationRegistration registeredObjectWithHost:host1
+ key:kKey
+ creationBlock:gCreateNewObject];
+ id host2 = gCreateNewObject();
+ id obj2 = [FIRAppAssociationRegistration registeredObjectWithHost:host2
+ key:kKey
+ creationBlock:gCreateNewObject];
+ XCTAssertNotEqual(obj1, obj2);
+}
+
+- (void)testDifferentHostDifferentKey {
+ id host1 = gCreateNewObject();
+ id obj1 = [FIRAppAssociationRegistration registeredObjectWithHost:host1
+ key:kKey1
+ creationBlock:gCreateNewObject];
+ id host2 = gCreateNewObject();
+ id obj2 = [FIRAppAssociationRegistration registeredObjectWithHost:host2
+ key:kKey2
+ creationBlock:gCreateNewObject];
+ XCTAssertNotEqual(obj1, obj2);
+}
+
+- (void)testReentrySameHostSameKey {
+ id host = gCreateNewObject();
+ XCTAssertThrows([FIRAppAssociationRegistration registeredObjectWithHost:host
+ key:kKey
+ creationBlock:^id _Nullable() {
+ [FIRAppAssociationRegistration registeredObjectWithHost:host
+ key:kKey
+ creationBlock:gCreateNewObject];
+ return gCreateNewObject();
+ }]);
+}
+
+- (void)testReentrySameHostDifferentKey {
+ id host = gCreateNewObject();
+ [FIRAppAssociationRegistration registeredObjectWithHost:host
+ key:kKey1
+ creationBlock:^id _Nullable() {
+ [FIRAppAssociationRegistration registeredObjectWithHost:host
+ key:kKey2
+ creationBlock:gCreateNewObject];
+ return gCreateNewObject();
+ }];
+ // Expect no exception raised.
+}
+
+- (void)testReentryDifferentHostSameKey {
+ id host1 = gCreateNewObject();
+ id host2 = gCreateNewObject();
+ [FIRAppAssociationRegistration registeredObjectWithHost:host1
+ key:kKey
+ creationBlock:^id _Nullable() {
+ [FIRAppAssociationRegistration registeredObjectWithHost:host2
+ key:kKey
+ creationBlock:gCreateNewObject];
+ return gCreateNewObject();
+ }];
+ // Expect no exception raised.
+}
+
+- (void)testReentryDifferentHostDifferentKey {
+ id host1 = gCreateNewObject();
+ id host2 = gCreateNewObject();
+ [FIRAppAssociationRegistration registeredObjectWithHost:host1
+ key:kKey1
+ creationBlock:^id _Nullable() {
+ [FIRAppAssociationRegistration registeredObjectWithHost:host2
+ key:kKey2
+ creationBlock:gCreateNewObject];
+ return gCreateNewObject();
+ }];
+ // Expect no exception raised.
+}
+
+@end
diff --git a/Example/Core/Tests/FIRAppTest.m b/Example/Core/Tests/FIRAppTest.m
new file mode 100644
index 0000000..da97c6c
--- /dev/null
+++ b/Example/Core/Tests/FIRAppTest.m
@@ -0,0 +1,582 @@
+// Copyright 2017 Google
+//
+// 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 "FIRAppInternal.h"
+#import "FIROptionsInternal.h"
+#import "FIRTestCase.h"
+
+NSString *const kFIRTestAppName1 = @"test_app_name_1";
+NSString *const kFIRTestAppName2 = @"test-app-name-2";
+
+@interface FIRApp (TestInternal)
+
+@property(nonatomic) BOOL alreadySentConfigureNotification;
+@property(nonatomic) BOOL alreadySentDeleteNotification;
+
++ (void)resetApps;
+- (instancetype)initInstanceWithName:(NSString *)name options:(FIROptions *)options;
+- (BOOL)configureCore;
++ (NSError *)errorForInvalidAppID;
+- (BOOL)isAppIDValid;
++ (NSString *)actualBundleID;
++ (NSNumber *)mapFromServiceStringToTypeEnum:(NSString *)serviceString;
++ (NSString *)deviceModel;
++ (NSString *)installString;
++ (NSURL *)filePathURLWithName:(NSString *)fileName;
++ (NSString *)stringAtURL:(NSURL *)filePathURL;
++ (BOOL)writeString:(NSString *)string toURL:(NSURL *)filePathURL;
++ (void)logAppInfo:(NSNotification *)notification;
++ (BOOL)validateAppID:(NSString *)appID;
++ (BOOL)validateAppIDFormat:(NSString *)appID withVersion:(NSString *)version;
++ (BOOL)validateAppIDFingerprint:(NSString *)appID withVersion:(NSString *)version;
+
+@end
+
+
+@interface FIRAppTest : FIRTestCase
+
+@property(nonatomic) id appClassMock;
+@property(nonatomic) id optionsInstanceMock;
+@property(nonatomic) id notificationCenterMock;
+@property(nonatomic) FIRApp *app;
+
+@end
+
+@implementation FIRAppTest
+
+- (void)setUp {
+ [super setUp];
+ [FIROptions resetDefaultOptions];
+ [FIRApp resetApps];
+ _appClassMock = OCMClassMock([FIRApp class]);
+ _optionsInstanceMock = OCMPartialMock([FIROptions defaultOptions]);
+ _notificationCenterMock = OCMPartialMock([NSNotificationCenter defaultCenter]);
+}
+
+- (void)tearDown {
+ [_appClassMock stopMocking];
+ [_optionsInstanceMock stopMocking];
+ [_notificationCenterMock stopMocking];
+
+ [super tearDown];
+}
+
+- (void)testConfigure {
+ NSDictionary *expectedUserInfo = [self expectedUserInfoWithAppName:kFIRDefaultAppName
+ isDefaultApp:YES];
+ OCMExpect([self.notificationCenterMock postNotificationName:kFIRAppReadyToConfigureSDKNotification
+ object:[FIRApp class]
+ userInfo:expectedUserInfo]);
+ XCTAssertNoThrow([FIRApp configure]);
+ OCMVerifyAll(self.notificationCenterMock);
+
+ self.app = [FIRApp defaultApp];
+ XCTAssertNotNil(self.app);
+ XCTAssertEqualObjects(self.app.name, kFIRDefaultAppName);
+ XCTAssertEqualObjects(self.app.options.clientID, kClientID);
+ XCTAssertTrue([FIRApp allApps].count == 1);
+ XCTAssertTrue(self.app.alreadySentConfigureNotification);
+
+ // Test if options is nil
+ id optionsClassMock = OCMClassMock([FIROptions class]);
+ OCMStub([optionsClassMock defaultOptions]).andReturn(nil);
+ XCTAssertThrows([FIRApp configure]);
+}
+
+- (void)testConfigureWithOptions {
+ // nil options
+ XCTAssertThrows([FIRApp configureWithOptions:nil]);
+ XCTAssertTrue([FIRApp allApps].count == 0);
+
+ NSDictionary *expectedUserInfo = [self expectedUserInfoWithAppName:kFIRDefaultAppName
+ isDefaultApp:YES];
+ OCMExpect([self.notificationCenterMock postNotificationName:kFIRAppReadyToConfigureSDKNotification
+ object:[FIRApp class]
+ userInfo:expectedUserInfo]);
+ // default options
+ XCTAssertNoThrow([FIRApp configureWithOptions:[FIROptions defaultOptions]]);
+ OCMVerifyAll(self.notificationCenterMock);
+
+ self.app = [FIRApp defaultApp];
+ XCTAssertNotNil(self.app);
+ XCTAssertEqualObjects(self.app.name, kFIRDefaultAppName);
+ XCTAssertEqualObjects(self.app.options.clientID, kClientID);
+ XCTAssertTrue([FIRApp allApps].count == 1);
+}
+
+- (void)testConfigureWithCustomizedOptions {
+ // valid customized options
+ FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:kGoogleAppID
+ bundleID:kBundleID
+ GCMSenderID:kGCMSenderID
+ APIKey:kCustomizedAPIKey
+ clientID:nil
+ trackingID:nil
+ androidClientID:nil
+ databaseURL:nil
+ storageBucket:nil
+ deepLinkURLScheme:nil];
+
+ NSDictionary *expectedUserInfo = [self expectedUserInfoWithAppName:kFIRDefaultAppName
+ isDefaultApp:YES];
+ OCMExpect([self.notificationCenterMock postNotificationName:kFIRAppReadyToConfigureSDKNotification
+ object:[FIRApp class]
+ userInfo:expectedUserInfo]);
+
+ XCTAssertNoThrow([FIRApp configureWithOptions:options]);
+ OCMVerifyAll(self.notificationCenterMock);
+
+ self.app = [FIRApp defaultApp];
+ XCTAssertNotNil(self.app);
+ XCTAssertEqualObjects(self.app.name, kFIRDefaultAppName);
+ XCTAssertEqualObjects(self.app.options.googleAppID, kGoogleAppID);
+ XCTAssertEqualObjects(self.app.options.APIKey, kCustomizedAPIKey);
+ XCTAssertTrue([FIRApp allApps].count == 1);
+}
+
+- (void)testConfigureWithNameAndOptions {
+ XCTAssertThrows([FIRApp configureWithName:nil options:[FIROptions defaultOptions]]);
+ XCTAssertThrows([FIRApp configureWithName:kFIRTestAppName1 options:nil]);
+ XCTAssertThrows([FIRApp configureWithName:@"" options:[FIROptions defaultOptions]]);
+ XCTAssertThrows([FIRApp configureWithName:kFIRDefaultAppName
+ options:[FIROptions defaultOptions]]);
+ XCTAssertTrue([FIRApp allApps].count == 0);
+
+ NSDictionary *expectedUserInfo = [self expectedUserInfoWithAppName:kFIRTestAppName1
+ isDefaultApp:NO];
+ OCMExpect([self.notificationCenterMock postNotificationName:kFIRAppReadyToConfigureSDKNotification
+ object:[FIRApp class]
+ userInfo:expectedUserInfo]);
+ XCTAssertNoThrow([FIRApp configureWithName:kFIRTestAppName1 options:[FIROptions defaultOptions]]);
+ OCMVerifyAll(self.notificationCenterMock);
+
+ XCTAssertTrue([FIRApp allApps].count == 1);
+ self.app = [FIRApp appNamed:kFIRTestAppName1];
+ XCTAssertNotNil(self.app);
+ XCTAssertEqualObjects(self.app.name, kFIRTestAppName1);
+ XCTAssertEqualObjects(self.app.options.clientID, kClientID);
+
+ // Configure the same app again should throw an exception.
+ XCTAssertThrows([FIRApp configureWithName:kFIRTestAppName1 options:[FIROptions defaultOptions]]);
+}
+
+- (void)testConfigureWithNameAndCustomizedOptions {
+ FIROptions *options = [FIROptions defaultOptions];
+ FIROptions *newOptions = [options copy];
+ newOptions.deepLinkURLScheme = kDeepLinkURLScheme;
+
+ NSDictionary *expectedUserInfo1 = [self expectedUserInfoWithAppName:kFIRTestAppName1
+ isDefaultApp:NO];
+ OCMExpect([self.notificationCenterMock postNotificationName:kFIRAppReadyToConfigureSDKNotification
+ object:[FIRApp class]
+ userInfo:expectedUserInfo1]);
+ XCTAssertNoThrow([FIRApp configureWithName:kFIRTestAppName1 options:newOptions]);
+ XCTAssertTrue([FIRApp allApps].count == 1);
+ self.app = [FIRApp appNamed:kFIRTestAppName1];
+
+ // Configure a different app with valid customized options
+ FIROptions *customizedOptions = [[FIROptions alloc] initWithGoogleAppID:kGoogleAppID
+ bundleID:kBundleID
+ GCMSenderID:kGCMSenderID
+ APIKey:kCustomizedAPIKey
+ clientID:nil
+ trackingID:nil
+ androidClientID:nil
+ databaseURL:nil
+ storageBucket:nil
+ deepLinkURLScheme:nil];
+
+ NSDictionary *expectedUserInfo2 = [self expectedUserInfoWithAppName:kFIRTestAppName2
+ isDefaultApp:NO];
+ OCMExpect([self.notificationCenterMock postNotificationName:kFIRAppReadyToConfigureSDKNotification
+ object:[FIRApp class]
+ userInfo:expectedUserInfo2]);
+ XCTAssertNoThrow([FIRApp configureWithName:kFIRTestAppName2 options:customizedOptions]);
+ OCMVerifyAll(self.notificationCenterMock);
+
+ XCTAssertTrue([FIRApp allApps].count == 2);
+ self.app = [FIRApp appNamed:kFIRTestAppName2];
+ XCTAssertNotNil(self.app);
+ XCTAssertEqualObjects(self.app.name, kFIRTestAppName2);
+ XCTAssertEqualObjects(self.app.options.googleAppID, kGoogleAppID);
+ XCTAssertEqualObjects(self.app.options.APIKey, kCustomizedAPIKey);
+}
+
+- (void)testValidName {
+ XCTAssertNoThrow([FIRApp configureWithName:@"aA1_" options:[FIROptions defaultOptions]]);
+ XCTAssertThrows([FIRApp configureWithName:@"aA1%" options:[FIROptions defaultOptions]]);
+ XCTAssertThrows([FIRApp configureWithName:@"aA1?" options:[FIROptions defaultOptions]]);
+ XCTAssertThrows([FIRApp configureWithName:@"aA1!" options:[FIROptions defaultOptions]]);
+}
+
+- (void)testDefaultApp {
+ self.app = [FIRApp defaultApp];
+ XCTAssertNil(self.app);
+
+ [FIRApp configure];
+ self.app = [FIRApp defaultApp];
+ XCTAssertEqualObjects(self.app.name, kFIRDefaultAppName);
+ XCTAssertEqualObjects(self.app.options.clientID, kClientID);
+}
+
+- (void)testAppNamed {
+ self.app = [FIRApp appNamed:kFIRTestAppName1];
+ XCTAssertNil(self.app);
+
+ [FIRApp configureWithName:kFIRTestAppName1 options:[FIROptions defaultOptions]];
+ self.app = [FIRApp appNamed:kFIRTestAppName1];
+ XCTAssertEqualObjects(self.app.name, kFIRTestAppName1);
+ XCTAssertEqualObjects(self.app.options.clientID, kClientID);
+}
+
+- (void)testDeleteApp {
+ [FIRApp configure];
+ self.app = [FIRApp defaultApp];
+ XCTAssertTrue([FIRApp allApps].count == 1);
+ [self.app deleteApp:^(BOOL success) {
+ XCTAssertTrue(success);
+ }];
+ OCMVerify([self.notificationCenterMock postNotificationName:kFIRAppDeleteNotification
+ object:[FIRApp class]
+ userInfo:[OCMArg any]]);
+ XCTAssertTrue(self.app.alreadySentDeleteNotification);
+ XCTAssertTrue([FIRApp allApps].count == 0);
+}
+
+- (void)testErrorForSubspecConfigurationFailure {
+ NSError *error = [FIRApp errorForSubspecConfigurationFailureWithDomain:kFirebaseAdMobErrorDomain
+ errorCode:FIRErrorCodeAdMobFailed
+ service:kFIRServiceAdMob
+ reason:@"some reason"];
+ XCTAssertNotNil(error);
+ XCTAssert([error.domain isEqualToString:kFirebaseAdMobErrorDomain]);
+ XCTAssert(error.code == FIRErrorCodeAdMobFailed);
+ XCTAssert([error.description containsString:@"Configuration failed for"]);
+}
+
+- (void)testGetTokenWithCallback {
+ [FIRApp configure];
+ FIRApp *app = [FIRApp defaultApp];
+
+ __block BOOL getTokenImplementationWasCalled = NO;
+ __block BOOL getTokenCallbackWasCalled = NO;
+ __block BOOL passedRefreshValue = NO;
+
+ [app getTokenForcingRefresh:YES
+ withCallback:^(NSString *_Nullable token, NSError *_Nullable error) {
+ getTokenCallbackWasCalled = YES;
+ }];
+
+ XCTAssert(getTokenCallbackWasCalled,
+ @"The callback should be invoked by the base implementation when no block for "
+ "'getTokenImplementation' has been specified.");
+
+ getTokenCallbackWasCalled = NO;
+
+ app.getTokenImplementation = ^(BOOL refresh, FIRTokenCallback callback) {
+ getTokenImplementationWasCalled = YES;
+ passedRefreshValue = refresh;
+ callback(nil, nil);
+ };
+ [app getTokenForcingRefresh:YES
+ withCallback:^(NSString *_Nullable token, NSError *_Nullable error) {
+ getTokenCallbackWasCalled = YES;
+ }];
+
+ XCTAssert(getTokenImplementationWasCalled,
+ @"The 'getTokenImplementation' block was never called.");
+ XCTAssert(passedRefreshValue,
+ @"The value for the 'refresh' parameter wasn't passed to the 'getTokenImplementation' "
+ "block correctly.");
+ XCTAssert(getTokenCallbackWasCalled,
+ @"The 'getTokenImplementation' should have invoked the callback. This could be an "
+ "error in this test, or the callback parameter may not have been passed to the "
+ "implementation correctly.");
+
+ getTokenImplementationWasCalled = NO;
+ getTokenCallbackWasCalled = NO;
+ passedRefreshValue = NO;
+
+ [app getTokenForcingRefresh:NO
+ withCallback:^(NSString *_Nullable token, NSError *_Nullable error) {
+ getTokenCallbackWasCalled = YES;
+ }];
+
+ XCTAssertFalse(passedRefreshValue,
+ @"The value for the 'refresh' parameter wasn't passed to the "
+ "'getTokenImplementation' block correctly.");
+}
+
+- (void)testModifyingOptionsThrows {
+ [FIRApp configure];
+ FIROptions *options = [[FIRApp defaultApp] options];
+ XCTAssertTrue(options.isEditingLocked);
+
+ // Modification to every property should result in an exception.
+ XCTAssertThrows(options.androidClientID = @"should_throw");
+ XCTAssertThrows(options.APIKey = @"should_throw");
+ XCTAssertThrows(options.bundleID = @"should_throw");
+ XCTAssertThrows(options.clientID = @"should_throw");
+ XCTAssertThrows(options.databaseURL = @"should_throw");
+ XCTAssertThrows(options.deepLinkURLScheme = @"should_throw");
+ XCTAssertThrows(options.GCMSenderID = @"should_throw");
+ XCTAssertThrows(options.googleAppID = @"should_throw");
+ XCTAssertThrows(options.projectID = @"should_throw");
+ XCTAssertThrows(options.storageBucket = @"should_throw");
+ XCTAssertThrows(options.trackingID = @"should_throw");
+}
+
+- (void)testOptionsLocking {
+ FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:kGoogleAppID
+ GCMSenderID:kGCMSenderID];
+ options.projectID = kProjectID;
+ options.databaseURL = kDatabaseURL;
+
+ // Options should not be locked before they are used to configure a `FIRApp`.
+ XCTAssertFalse(options.isEditingLocked);
+
+ // The options returned should be locked after configuring `FIRApp`.
+ [FIRApp configureWithOptions:options];
+ FIROptions *optionsCopy = [[FIRApp defaultApp] options];
+ XCTAssertTrue(optionsCopy.isEditingLocked);
+}
+
+#pragma mark - App ID v1
+
+- (void)testAppIDV1 {
+ // Missing separator between platform:fingerprint.
+ XCTAssertFalse([FIRApp validateAppID:@"1:1337:iosdeadbeef"]);
+
+ // Wrong platform "android".
+ XCTAssertFalse([FIRApp validateAppID:@"1:1337:android:deadbeef"]);
+
+ // The fingerprint, aka 4th field, should only contain hex characters.
+ XCTAssertFalse([FIRApp validateAppID:@"1:1337:ios:123abcxyz"]);
+
+ // The fingerprint, aka 4th field, is not tested in V1, so a bad value shouldn't cause a failure.
+ XCTAssertTrue([FIRApp validateAppID:@"1:1337:ios:deadbeef"]);
+}
+
+#pragma mark - App ID v2
+
+- (void)testAppIDV2 {
+ // Missing separator between platform:fingerprint.
+ XCTAssertTrue([FIRApp validateAppID:@"2:1337:ios5e18052ab54fbfec"]);
+
+ // Unknown versions may contain anything.
+ XCTAssertTrue([FIRApp validateAppID:@"2:1337:ios:123abcxyz"]);
+ XCTAssertTrue([FIRApp validateAppID:@"2:thisdoesn'teven_m:a:t:t:e:r_"]);
+
+ // Known good fingerprint.
+ XCTAssertTrue([FIRApp validateAppID:@"2:1337:ios:5e18052ab54fbfec"]);
+
+ // Unknown fingerprint, not tested so shouldn't cause a failure.
+ XCTAssertTrue([FIRApp validateAppID:@"2:1337:ios:deadbeef"]);
+}
+
+#pragma mark - App ID other
+
+- (void)testAppIDV3 {
+ // Currently there is no specification for v3, so we would not expect it to fail.
+ XCTAssertTrue([FIRApp validateAppID:@"3:1337:ios:deadbeef"]);
+}
+
+- (void)testAppIDEmpty {
+ XCTAssertFalse([FIRApp validateAppID:@""]);
+}
+
+- (void)testAppIDValidationTrue {
+ // Ensure that isAppIDValid matches validateAppID.
+ [FIRApp configure];
+ OCMStub([self.appClassMock validateAppID:[OCMArg any]]).andReturn(YES);
+ XCTAssertTrue([[FIRApp defaultApp] isAppIDValid]);
+}
+
+- (void)testAppIDValidationFalse {
+ // Ensure that isAppIDValid matches validateAppID.
+ [FIRApp configure];
+ OCMStub([self.appClassMock validateAppID:[OCMArg any]]).andReturn(NO);
+ XCTAssertFalse([[FIRApp defaultApp] isAppIDValid]);
+}
+
+- (void)testAppIDPrefix {
+ // Unknown numeric-character prefixes should pass.
+ XCTAssertTrue([FIRApp validateAppID:@"0:"]);
+ XCTAssertTrue([FIRApp validateAppID:@"01:"]);
+ XCTAssertTrue([FIRApp validateAppID:@"10:"]);
+ XCTAssertTrue([FIRApp validateAppID:@"010:"]);
+ XCTAssertTrue([FIRApp validateAppID:@"3:"]);
+ XCTAssertTrue([FIRApp validateAppID:@"123:"]);
+ XCTAssertTrue([FIRApp validateAppID:@"999999999:"]);
+
+ // Non-numeric prefixes should not pass.
+ XCTAssertFalse([FIRApp validateAppID:@"a:"]);
+ XCTAssertFalse([FIRApp validateAppID:@"abcsdf0:"]);
+ XCTAssertFalse([FIRApp validateAppID:@"0aaaa:"]);
+ XCTAssertFalse([FIRApp validateAppID:@"0aaaa0450:"]);
+ XCTAssertFalse([FIRApp validateAppID:@"-1:"]);
+ XCTAssertFalse([FIRApp validateAppID:@"abcsdf:"]);
+ XCTAssertFalse([FIRApp validateAppID:@"ABDCF:"]);
+ XCTAssertFalse([FIRApp validateAppID:@" :"]);
+ XCTAssertFalse([FIRApp validateAppID:@"1 :"]);
+ XCTAssertFalse([FIRApp validateAppID:@" 1:"]);
+ XCTAssertFalse([FIRApp validateAppID:@" 123 :"]);
+ XCTAssertFalse([FIRApp validateAppID:@"1 23:"]);
+ XCTAssertFalse([FIRApp validateAppID:@"&($*&%(*$&:"]);
+ XCTAssertFalse([FIRApp validateAppID:@"abCDSF$%%df:"]);
+
+ // Known version prefixes should never pass without the rest of the app ID string present.
+ XCTAssertFalse([FIRApp validateAppID:@"1:"]);
+
+ // Version must include ":".
+ XCTAssertFalse([FIRApp validateAppID:@"0"]);
+ XCTAssertFalse([FIRApp validateAppID:@"01"]);
+ XCTAssertFalse([FIRApp validateAppID:@"10"]);
+ XCTAssertFalse([FIRApp validateAppID:@"010"]);
+ XCTAssertFalse([FIRApp validateAppID:@"3"]);
+ XCTAssertFalse([FIRApp validateAppID:@"123"]);
+ XCTAssertFalse([FIRApp validateAppID:@"999999999"]);
+ XCTAssertFalse([FIRApp validateAppID:@"com.google.bundleID"]);
+}
+
+- (void)testAppIDFormatInvalid {
+ OCMStub([self.appClassMock actualBundleID]).andReturn(@"com.google.bundleID");
+ // Some direct tests of the validateAppIDFormat:withVersion: method.
+ // Sanity checks first.
+ NSString *const kGoodAppIDV1 = @"1:1337:ios:deadbeef";
+ NSString *const kGoodVersionV1 = @"1:";
+ XCTAssertTrue([FIRApp validateAppIDFormat:kGoodAppIDV1 withVersion:kGoodVersionV1]);
+
+ NSString *const kGoodAppIDV2 = @"2:1337:ios:5e18052ab54fbfec";
+ NSString *const kGoodVersionV2 = @"2:";
+ XCTAssertTrue([FIRApp validateAppIDFormat:kGoodAppIDV2 withVersion:kGoodVersionV2]);
+
+ // Version mismatch.
+ XCTAssertFalse([FIRApp validateAppIDFormat:kGoodAppIDV2 withVersion:kGoodVersionV1]);
+ XCTAssertFalse([FIRApp validateAppIDFormat:kGoodAppIDV1 withVersion:kGoodVersionV2]);
+ XCTAssertFalse([FIRApp validateAppIDFormat:kGoodAppIDV1 withVersion:@"999:"]);
+
+ // Nil or empty strings.
+ XCTAssertFalse([FIRApp validateAppIDFormat:kGoodAppIDV1 withVersion:nil]);
+ XCTAssertFalse([FIRApp validateAppIDFormat:kGoodAppIDV1 withVersion:@""]);
+ XCTAssertFalse([FIRApp validateAppIDFormat:nil withVersion:kGoodVersionV1]);
+ XCTAssertFalse([FIRApp validateAppIDFormat:@"" withVersion:kGoodVersionV1]);
+ XCTAssertFalse([FIRApp validateAppIDFormat:nil withVersion:nil]);
+ XCTAssertFalse([FIRApp validateAppIDFormat:@"" withVersion:@""]);
+
+ // App ID contains only the version prefix.
+ XCTAssertFalse([FIRApp validateAppIDFormat:kGoodVersionV1 withVersion:kGoodVersionV1]);
+ // The version is the entire app ID.
+ XCTAssertFalse([FIRApp validateAppIDFormat:kGoodAppIDV1 withVersion:kGoodAppIDV1]);
+
+ // Versions digits that may make a partial match.
+ XCTAssertFalse([FIRApp validateAppIDFormat:@"01:1337:ios:deadbeef" withVersion:kGoodVersionV1]);
+ XCTAssertFalse([FIRApp validateAppIDFormat:@"10:1337:ios:deadbeef" withVersion:kGoodVersionV1]);
+ XCTAssertFalse([FIRApp validateAppIDFormat:@"11:1337:ios:deadbeef" withVersion:kGoodVersionV1]);
+ XCTAssertFalse([FIRApp validateAppIDFormat:@"21:1337:ios:5e18052ab54fbfec"
+ withVersion:kGoodVersionV2]);
+ XCTAssertFalse([FIRApp validateAppIDFormat:@"22:1337:ios:5e18052ab54fbfec"
+ withVersion:kGoodVersionV2]);
+ XCTAssertFalse([FIRApp validateAppIDFormat:@"02:1337:ios:5e18052ab54fbfec"
+ withVersion:kGoodVersionV2]);
+ XCTAssertFalse([FIRApp validateAppIDFormat:@"20:1337:ios:5e18052ab54fbfec"
+ withVersion:kGoodVersionV2]);
+
+ // Extra fields.
+ XCTAssertFalse([FIRApp validateAppIDFormat:@"ab:1:1337:ios:deadbeef" withVersion:kGoodVersionV1]);
+ XCTAssertFalse([FIRApp validateAppIDFormat:@"1:ab:1337:ios:deadbeef" withVersion:kGoodVersionV1]);
+ XCTAssertFalse([FIRApp validateAppIDFormat:@"1:1337:ab:ios:deadbeef" withVersion:kGoodVersionV1]);
+ XCTAssertFalse([FIRApp validateAppIDFormat:@"1:1337:ios:ab:deadbeef" withVersion:kGoodVersionV1]);
+ XCTAssertFalse([FIRApp validateAppIDFormat:@"1:1337:ios:deadbeef:ab" withVersion:kGoodVersionV1]);
+}
+
+- (void)testAppIDFingerprintInvalid {
+ OCMStub([self.appClassMock actualBundleID]).andReturn(@"com.google.bundleID");
+ // Some direct tests of the validateAppIDFingerprint:withVersion: method.
+ // Sanity checks first.
+ NSString *const kGoodAppIDV1 = @"1:1337:ios:deadbeef";
+ NSString *const kGoodVersionV1 = @"1:";
+ XCTAssertTrue([FIRApp validateAppIDFingerprint:kGoodAppIDV1 withVersion:kGoodVersionV1]);
+
+ NSString *const kGoodAppIDV2 = @"2:1337:ios:5e18052ab54fbfec";
+ NSString *const kGoodVersionV2 = @"2:";
+ XCTAssertTrue([FIRApp validateAppIDFormat:kGoodAppIDV2 withVersion:kGoodVersionV2]);
+
+ // Version mismatch.
+ XCTAssertFalse([FIRApp validateAppIDFingerprint:kGoodAppIDV2 withVersion:kGoodVersionV1]);
+ XCTAssertFalse([FIRApp validateAppIDFingerprint:kGoodAppIDV1 withVersion:kGoodVersionV2]);
+ XCTAssertFalse([FIRApp validateAppIDFingerprint:kGoodAppIDV1 withVersion:@"999:"]);
+
+ // Nil or empty strings.
+ XCTAssertFalse([FIRApp validateAppIDFingerprint:kGoodAppIDV1 withVersion:nil]);
+ XCTAssertFalse([FIRApp validateAppIDFingerprint:kGoodAppIDV1 withVersion:@""]);
+ XCTAssertFalse([FIRApp validateAppIDFingerprint:nil withVersion:kGoodVersionV1]);
+ XCTAssertFalse([FIRApp validateAppIDFingerprint:@"" withVersion:kGoodVersionV1]);
+ XCTAssertFalse([FIRApp validateAppIDFingerprint:nil withVersion:nil]);
+ XCTAssertFalse([FIRApp validateAppIDFingerprint:@"" withVersion:@""]);
+
+ // App ID contains only the version prefix.
+ XCTAssertFalse([FIRApp validateAppIDFingerprint:kGoodVersionV1 withVersion:kGoodVersionV1]);
+ // The version is the entire app ID.
+ XCTAssertFalse([FIRApp validateAppIDFingerprint:kGoodAppIDV1 withVersion:kGoodAppIDV1]);
+
+ // Versions digits that may make a partial match.
+ XCTAssertFalse([FIRApp validateAppIDFingerprint:@"01:1337:ios:deadbeef"
+ withVersion:kGoodVersionV1]);
+ XCTAssertFalse([FIRApp validateAppIDFingerprint:@"10:1337:ios:deadbeef"
+ withVersion:kGoodVersionV1]);
+ XCTAssertFalse([FIRApp validateAppIDFingerprint:@"11:1337:ios:deadbeef"
+ withVersion:kGoodVersionV1]);
+ XCTAssertFalse([FIRApp validateAppIDFingerprint:@"21:1337:ios:5e18052ab54fbfec"
+ withVersion:kGoodVersionV2]);
+ XCTAssertFalse([FIRApp validateAppIDFingerprint:@"22:1337:ios:5e18052ab54fbfec"
+ withVersion:kGoodVersionV2]);
+ XCTAssertFalse([FIRApp validateAppIDFingerprint:@"02:1337:ios:5e18052ab54fbfec"
+ withVersion:kGoodVersionV2]);
+ XCTAssertFalse([FIRApp validateAppIDFingerprint:@"20:1337:ios:5e18052ab54fbfec"
+ withVersion:kGoodVersionV2]);
+ // Extra fields.
+ XCTAssertFalse([FIRApp validateAppIDFingerprint:@"ab:1:1337:ios:deadbeef"
+ withVersion:kGoodVersionV1]);
+ XCTAssertFalse([FIRApp validateAppIDFingerprint:@"1:ab:1337:ios:deadbeef"
+ withVersion:kGoodVersionV1]);
+ XCTAssertFalse([FIRApp validateAppIDFingerprint:@"1:1337:ab:ios:deadbeef"
+ withVersion:kGoodVersionV1]);
+ XCTAssertFalse([FIRApp validateAppIDFingerprint:@"1:1337:ios:ab:deadbeef"
+ withVersion:kGoodVersionV1]);
+ XCTAssertFalse([FIRApp validateAppIDFingerprint:@"1:1337:ios:deadbeef:ab"
+ withVersion:kGoodVersionV1]);
+}
+
+#pragma mark - Internal Methods
+
+- (void)testAuthGetUID {
+ [FIRApp configure];
+
+ [FIRApp defaultApp].getUIDImplementation = ^NSString *{ return @"highlander"; };
+ XCTAssertEqual([[FIRApp defaultApp] getUID], @"highlander");
+}
+
+#pragma mark - private
+
+- (NSDictionary <NSString *, NSObject *> *)expectedUserInfoWithAppName:(NSString *)name
+ isDefaultApp:(BOOL)isDefaultApp {
+ return @{
+ kFIRAppNameKey : name,
+ kFIRAppIsDefaultAppKey : [NSNumber numberWithBool:isDefaultApp],
+ kFIRGoogleAppIDKey : kGoogleAppID
+ };
+}
+
+@end
diff --git a/Example/Core/Tests/FIRBundleUtilTest.m b/Example/Core/Tests/FIRBundleUtilTest.m
new file mode 100644
index 0000000..6a3e20a
--- /dev/null
+++ b/Example/Core/Tests/FIRBundleUtilTest.m
@@ -0,0 +1,86 @@
+// Copyright 2017 Google
+//
+// 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 "FIRBundleUtil.h"
+
+#import "FIRTestCase.h"
+
+static NSString *const kResultPath = @"resultPath";
+static NSString *const kResourceName = @"resourceName";
+static NSString *const kFileType = @"fileType";
+
+@interface FIRBundleUtilTest : FIRTestCase
+
+@property(nonatomic, strong) id mockBundle;
+
+@end
+
+@implementation FIRBundleUtilTest
+
+- (void)setUp {
+ [super setUp];
+ self.mockBundle = OCMClassMock([NSBundle class]);
+}
+
+- (void)testRelevantBundles_mainIsFirst {
+ // Pointer compare to same instance of main bundle.
+ XCTAssertEqual([NSBundle mainBundle], [FIRBundleUtil relevantBundles][0]);
+}
+
+// TODO: test that adding a bundle appears in "all bundles"
+// once the use-case is understood.
+
+- (void)testFindOptionsDictionaryPath {
+ [OCMStub([self.mockBundle pathForResource:kResourceName ofType:kFileType]) andReturn:kResultPath];
+ XCTAssertEqualObjects(
+ [FIRBundleUtil optionsDictionaryPathWithResourceName:kResourceName
+ andFileType:kFileType
+ inBundles:@[ self.mockBundle ]],
+ kResultPath);
+}
+
+- (void)testFindOptionsDictionaryPath_notFound {
+ XCTAssertNil([FIRBundleUtil optionsDictionaryPathWithResourceName:kResourceName
+ andFileType:kFileType
+ inBundles:@[ self.mockBundle ]]);
+}
+
+- (void)testFindOptionsDictionaryPath_secondBundle {
+ NSBundle *mockBundleEmpty = OCMClassMock([NSBundle class]);
+ [OCMStub([self.mockBundle pathForResource:kResourceName ofType:kFileType]) andReturn:kResultPath];
+
+ NSArray *bundles = @[ mockBundleEmpty, self.mockBundle ];
+ XCTAssertEqualObjects(
+ [FIRBundleUtil optionsDictionaryPathWithResourceName:kResourceName
+ andFileType:kFileType
+ inBundles:bundles],
+ kResultPath);
+}
+
+- (void)testBundleIdentifierExistsInBundles {
+ NSString *bundleID = @"com.google.test";
+ [OCMStub([self.mockBundle bundleIdentifier]) andReturn:bundleID];
+ XCTAssertTrue([FIRBundleUtil hasBundleIdentifier:bundleID inBundles:@[ self.mockBundle ]]);
+}
+
+- (void)testBundleIdentifierExistsInBundles_notExist {
+ [OCMStub([self.mockBundle bundleIdentifier]) andReturn:@"com.google.test"];
+ XCTAssertFalse([FIRBundleUtil hasBundleIdentifier:@"not-exist" inBundles:@[ self.mockBundle ]]);
+}
+
+- (void)testBundleIdentifierExistsInBundles_emptyBundlesArray {
+ XCTAssertFalse([FIRBundleUtil hasBundleIdentifier:@"com.google.test" inBundles:@[ ]]);
+}
+
+@end
diff --git a/Example/Core/Tests/FIRConfigurationTest.m b/Example/Core/Tests/FIRConfigurationTest.m
new file mode 100644
index 0000000..2b3ff46
--- /dev/null
+++ b/Example/Core/Tests/FIRConfigurationTest.m
@@ -0,0 +1,31 @@
+// Copyright 2017 Google
+//
+// 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 "FIRConfiguration.h"
+
+#import "FIRTestCase.h"
+
+@interface FIRConfigurationTest : FIRTestCase
+
+@end
+
+@implementation FIRConfigurationTest
+
+- (void)testSharedInstance {
+ FIRConfiguration *config = [FIRConfiguration sharedInstance];
+ XCTAssertNotNil(config);
+ XCTAssertNotNil(config.analyticsConfiguration);
+}
+
+@end
diff --git a/Example/Core/Tests/FIRLoggerTest.m b/Example/Core/Tests/FIRLoggerTest.m
new file mode 100644
index 0000000..e7031a7
--- /dev/null
+++ b/Example/Core/Tests/FIRLoggerTest.m
@@ -0,0 +1,265 @@
+// Copyright 2017 Google
+//
+// 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 "FIRLogger.h"
+#import "FIRTestCase.h"
+
+#import <asl.h>
+
+// The following constants are exposed from FIRLogger for unit tests.
+extern NSString *const kFIRDisableDebugModeApplicationArgument;
+extern NSString *const kFIREnableDebugModeApplicationArgument;
+
+extern NSString *const kFIRPersistedDebugModeKey;
+
+extern const char *kFIRLoggerASLClientFacilityName;
+
+extern const char *kFIRLoggerCustomASLMessageFormat;
+
+extern void FIRResetLogger();
+
+extern aslclient getFIRLoggerClient();
+
+extern dispatch_queue_t getFIRClientQueue();
+
+extern BOOL getFIRLoggerDebugMode();
+
+// Define the message format again to make sure the format doesn't accidentally change.
+static NSString *const kCorrectASLMessageFormat =
+ @"$((Time)(J.3)) $(Sender)[$(PID)] <$((Level)(str))> $Message";
+
+static NSString *const kMessageCode = @"I-COR000001";
+
+@interface FIRLoggerTest : FIRTestCase
+
+@property(nonatomic) NSString *randomLogString;
+
+@end
+
+@implementation FIRLoggerTest
+
+- (void)setUp {
+ [super setUp];
+ FIRResetLogger();
+}
+
+// Test some stable variables to make sure they weren't accidently changed.
+- (void)testStableVariables {
+ // kFIRLoggerCustomASLMessageFormat.
+ XCTAssertEqualObjects(kCorrectASLMessageFormat,
+ [NSString stringWithUTF8String:kFIRLoggerCustomASLMessageFormat]);
+
+ // Strings of type FIRLoggerServices.
+ XCTAssertEqualObjects(kFIRLoggerABTesting, @"[Firebase/ABTesting]");
+ XCTAssertEqualObjects(kFIRLoggerAdMob, @"[Firebase/AdMob]");
+ XCTAssertEqualObjects(kFIRLoggerAnalytics, @"[Firebase/Analytics]");
+ XCTAssertEqualObjects(kFIRLoggerAuth, @"[Firebase/Auth]");
+ XCTAssertEqualObjects(kFIRLoggerCore, @"[Firebase/Core]");
+ XCTAssertEqualObjects(kFIRLoggerCrash, @"[Firebase/Crash]");
+ XCTAssertEqualObjects(kFIRLoggerDatabase, @"[Firebase/Database]");
+ XCTAssertEqualObjects(kFIRLoggerDynamicLinks, @"[Firebase/DynamicLinks]");
+ XCTAssertEqualObjects(kFIRLoggerInstanceID, @"[Firebase/InstanceID]");
+ XCTAssertEqualObjects(kFIRLoggerInvites, @"[Firebase/Invites]");
+ XCTAssertEqualObjects(kFIRLoggerMessaging, @"[Firebase/Messaging]");
+ XCTAssertEqualObjects(kFIRLoggerRemoteConfig, @"[Firebase/RemoteConfig]");
+ XCTAssertEqualObjects(kFIRLoggerStorage, @"[Firebase/Storage]");
+}
+
+- (void)testInitializeASLForNonDebugMode {
+ // Stub.
+ id processInfoMock = [OCMockObject partialMockForObject:[NSProcessInfo processInfo]];
+ NSArray *arguments = @[ kFIRDisableDebugModeApplicationArgument ];
+ [[[processInfoMock stub] andReturn:arguments] arguments];
+
+ // Test.
+ FIRLogError(kFIRLoggerCore, kMessageCode, @"Some error.");
+
+ // Assert.
+ NSNumber *debugMode =
+ [[NSUserDefaults standardUserDefaults] objectForKey:kFIRPersistedDebugModeKey];
+ XCTAssertNil(debugMode);
+ XCTAssertFalse(getFIRLoggerDebugMode());
+
+ // Stop.
+ [processInfoMock stopMocking];
+}
+
+- (void)testInitializeASLForDebugModeWithArgument {
+ // Stub.
+ id processInfoMock = [OCMockObject partialMockForObject:[NSProcessInfo processInfo]];
+ NSArray *arguments = @[ kFIREnableDebugModeApplicationArgument ];
+ [[[processInfoMock stub] andReturn:arguments] arguments];
+
+ // Test.
+ FIRLogError(kFIRLoggerCore, kMessageCode, @"Some error.");
+
+ // Assert.
+ NSNumber *debugMode =
+ [[NSUserDefaults standardUserDefaults] objectForKey:kFIRPersistedDebugModeKey];
+ XCTAssertTrue(debugMode.boolValue);
+ XCTAssertTrue(getFIRLoggerDebugMode());
+
+ // Stop.
+ [processInfoMock stopMocking];
+}
+
+- (void)testInitializeASLForDebugModeWithUserDefaults {
+ // Stub.
+ id userDefaultsMock = [OCMockObject partialMockForObject:[NSUserDefaults standardUserDefaults]];
+ NSNumber *debugMode = @YES;
+ [[[userDefaultsMock stub] andReturnValue:debugMode] boolForKey:kFIRPersistedDebugModeKey];
+
+ // Test.
+ FIRLogError(kFIRLoggerCore, kMessageCode, @"Some error.");
+
+ // Assert.
+ debugMode = [[NSUserDefaults standardUserDefaults] objectForKey:kFIRPersistedDebugModeKey];
+ XCTAssertTrue(debugMode.boolValue);
+ XCTAssertTrue(getFIRLoggerDebugMode());
+
+ // Stop.
+ [userDefaultsMock stopMocking];
+}
+
+- (void)testMessageCodeFormat {
+ // Valid case.
+ XCTAssertNoThrow(FIRLogError(kFIRLoggerCore, @"I-APP000001", @"Message."));
+
+ // An extra dash or missing dash should fail.
+ XCTAssertThrows(FIRLogError(kFIRLoggerCore, @"I-APP-000001", @"Message."));
+ XCTAssertThrows(FIRLogError(kFIRLoggerCore, @"IAPP000001", @"Message."));
+
+ // Wrong number of digits should fail.
+ XCTAssertThrows(FIRLogError(kFIRLoggerCore, @"I-APP00001", @"Message."));
+ XCTAssertThrows(FIRLogError(kFIRLoggerCore, @"I-APP0000001", @"Message."));
+
+ // Lowercase should fail.
+ XCTAssertThrows(FIRLogError(kFIRLoggerCore, @"I-app000001", @"Message."));
+
+ // nil or empty message code should fail.
+ XCTAssertThrows(FIRLogError(kFIRLoggerCore, nil, @"Message."));
+ XCTAssertThrows(FIRLogError(kFIRLoggerCore, @"", @"Message."));
+
+ // Android message code should fail.
+ XCTAssertThrows(FIRLogError(kFIRLoggerCore, @"A-APP000001", @"Message."));
+}
+
+
+- (void)testLoggerInterface {
+ XCTAssertNoThrow(FIRLogError(kFIRLoggerCore, kMessageCode, @"Message."));
+ XCTAssertNoThrow(FIRLogError(kFIRLoggerCore, kMessageCode, @"Configure %@.", @"blah"));
+
+ XCTAssertNoThrow(FIRLogWarning(kFIRLoggerCore, kMessageCode, @"Message."));
+ XCTAssertNoThrow(FIRLogWarning(kFIRLoggerCore, kMessageCode, @"Configure %@.", @"blah"));
+
+ XCTAssertNoThrow(FIRLogNotice(kFIRLoggerCore, kMessageCode, @"Message."));
+ XCTAssertNoThrow(FIRLogNotice(kFIRLoggerCore, kMessageCode, @"Configure %@.", @"blah"));
+
+ XCTAssertNoThrow(FIRLogInfo(kFIRLoggerCore, kMessageCode, @"Message."));
+ XCTAssertNoThrow(FIRLogInfo(kFIRLoggerCore, kMessageCode, @"Configure %@.", @"blah"));
+
+ XCTAssertNoThrow(FIRLogDebug(kFIRLoggerCore, kMessageCode, @"Message."));
+ XCTAssertNoThrow(FIRLogDebug(kFIRLoggerCore, kMessageCode, @"Configure %@.", @"blah"));
+}
+
+
+// asl_set_filter does not perform as expected in unit test environment with simulator. The
+// following test only checks whether the logs have been sent to system with the default settings in
+// the unit test environment.
+- (void)testSystemLogWithDefaultStatus {
+#if !(TARGET_OS_SIMULATOR)
+ // Test fails on device - b/38130372
+ return;
+#else
+ // Sets the time interval that we need to wait in order to fetch all the logs.
+ NSTimeInterval timeInterval = 0.1f;
+ // Generates a random string each time and check whether it has been logged.
+ // Log messages with Notice level and below should be logged to system/device by default.
+ self.randomLogString = [NSUUID UUID].UUIDString;
+ FIRLogError(kFIRLoggerCore, kMessageCode, @"%@", self.randomLogString);
+ [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:timeInterval]];
+ XCTAssertTrue([self logExists]);
+
+ self.randomLogString = [NSUUID UUID].UUIDString;
+ FIRLogWarning(kFIRLoggerCore, kMessageCode, @"%@", self.randomLogString);
+ [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:timeInterval]];
+ XCTAssertTrue([self logExists]);
+
+ self.randomLogString = [NSUUID UUID].UUIDString;
+ FIRLogNotice(kFIRLoggerCore, kMessageCode, @"%@", self.randomLogString);
+ [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:timeInterval]];
+ XCTAssertTrue([self logExists]);
+
+ // Log messages with Info level and above should NOT be logged to system/device by default.
+ self.randomLogString = [NSUUID UUID].UUIDString;
+ FIRLogInfo(kFIRLoggerCore, kMessageCode, @"%@", self.randomLogString);
+ [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:timeInterval]];
+ XCTAssertFalse([self logExists]);
+
+ self.randomLogString = [NSUUID UUID].UUIDString;
+ FIRLogDebug(kFIRLoggerCore, kMessageCode, @"%@", self.randomLogString);
+ [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:timeInterval]];
+ XCTAssertFalse([self logExists]);
+#endif
+}
+
+// The FIRLoggerLevel enum must match the ASL_LEVEL_* constants, but we manually redefine
+// them in FIRLoggerLevel.h since we cannot include <asl.h> (see b/34976089 for more details).
+// This test ensures the constants match.
+- (void)testFIRLoggerLevelValues {
+ XCTAssertEqual(FIRLoggerLevelError, ASL_LEVEL_ERR);
+ XCTAssertEqual(FIRLoggerLevelWarning, ASL_LEVEL_WARNING);
+ XCTAssertEqual(FIRLoggerLevelNotice, ASL_LEVEL_NOTICE);
+ XCTAssertEqual(FIRLoggerLevelInfo, ASL_LEVEL_INFO);
+ XCTAssertEqual(FIRLoggerLevelDebug, ASL_LEVEL_DEBUG);
+}
+
+
+// Helper functions.
+- (BOOL)logExists {
+ [self drainFIRClientQueue];
+ NSString *correctMsg = [NSString stringWithFormat:@"%@[%@] %@", kFIRLoggerCore, kMessageCode,
+ self.randomLogString];
+ return [self messageWasLogged:correctMsg];
+}
+
+
+- (void)drainFIRClientQueue {
+ dispatch_semaphore_t workerSemaphore = dispatch_semaphore_create(0);
+ dispatch_async(getFIRClientQueue(), ^{
+ dispatch_semaphore_signal(workerSemaphore);
+ });
+ dispatch_semaphore_wait(workerSemaphore, DISPATCH_TIME_FOREVER);
+}
+
+- (BOOL)messageWasLogged:(NSString *)message {
+ aslmsg query = asl_new(ASL_TYPE_QUERY);
+ asl_set_query(query, ASL_KEY_FACILITY, kFIRLoggerASLClientFacilityName, ASL_QUERY_OP_EQUAL);
+ aslresponse r = asl_search(getFIRLoggerClient(), query);
+ asl_free(query);
+ aslmsg m;
+ const char *val;
+ NSMutableArray *allMsg = [[NSMutableArray alloc] init];
+ while ((m = asl_next(r)) != NULL) {
+ val = asl_get(m, ASL_KEY_MSG);
+ if (val) {
+ [allMsg addObject:[NSString stringWithUTF8String:val]];
+ }
+ }
+ asl_free(m);
+ asl_release(r);
+ return [allMsg containsObject:message];
+}
+
+@end
diff --git a/Example/Core/Tests/FIROptionsTest.m b/Example/Core/Tests/FIROptionsTest.m
new file mode 100644
index 0000000..62b6294
--- /dev/null
+++ b/Example/Core/Tests/FIROptionsTest.m
@@ -0,0 +1,468 @@
+// Copyright 2017 Google
+//
+// 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 "FIRAppInternal.h"
+#import "FIRBundleUtil.h"
+#import "FIROptionsInternal.h"
+
+#import "FIRTestCase.h"
+
+extern NSString *const kFIRIsMeasurementEnabled;
+extern NSString *const kFIRIsAnalyticsCollectionEnabled;
+extern NSString *const kFIRIsAnalyticsCollectionDeactivated;
+extern NSString *const kFIRLibraryVersionID;
+
+@interface FIROptions (Test)
+
+@property(nonatomic, readonly) NSDictionary *analyticsOptionsDictionary;
+
+@end
+
+@interface FIROptionsTest : FIRTestCase
+
+@end
+
+@implementation FIROptionsTest
+
+- (void)setUp {
+ [super setUp];
+ [FIROptions resetDefaultOptions];
+}
+
+- (void)testInit {
+ NSDictionary *optionsDictionary = [FIROptions defaultOptionsDictionary];
+ FIROptions *options =
+ [[FIROptions alloc] initInternalWithOptionsDictionary:optionsDictionary];
+ [self assertOptionsMatchDefaults:options andProjectID:YES];
+ XCTAssertNil(options.deepLinkURLScheme);
+ XCTAssertTrue(options.usingOptionsFromDefaultPlist);
+
+ options.deepLinkURLScheme = kDeepLinkURLScheme;
+ XCTAssertEqualObjects(options.deepLinkURLScheme, kDeepLinkURLScheme);
+}
+
+- (void)testDefaultOptionsDictionaryWithNilFilePath {
+ id mockBundleUtil = OCMClassMock([FIRBundleUtil class]);
+ [OCMStub([mockBundleUtil optionsDictionaryPathWithResourceName:kServiceInfoFileName
+ andFileType:kServiceInfoFileType
+ inBundles:[FIRBundleUtil relevantBundles]])
+ andReturn:nil];
+ XCTAssertNil([FIROptions defaultOptionsDictionary]);
+}
+
+- (void)testDefaultOptionsDictionaryWithInvalidSourceFile {
+ id mockBundleUtil = OCMClassMock([FIRBundleUtil class]);
+ [OCMStub([mockBundleUtil optionsDictionaryPathWithResourceName:kServiceInfoFileName
+ andFileType:kServiceInfoFileType
+ inBundles:[FIRBundleUtil relevantBundles]])
+ andReturn:@"invalid.plist"];
+ XCTAssertNil([FIROptions defaultOptionsDictionary]);
+}
+
+- (void)testDefaultOptions {
+ FIROptions *options = [FIROptions defaultOptions];
+ [self assertOptionsMatchDefaults:options andProjectID:YES];
+ XCTAssertNil(options.deepLinkURLScheme);
+ XCTAssertTrue(options.usingOptionsFromDefaultPlist);
+
+ options.deepLinkURLScheme = kDeepLinkURLScheme;
+ XCTAssertEqualObjects(options.deepLinkURLScheme, kDeepLinkURLScheme);
+}
+
+- (void)testInitCustomizedOptions {
+ FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:kGoogleAppID
+ bundleID:kBundleID
+ GCMSenderID:kGCMSenderID
+ APIKey:kAPIKey
+ clientID:kClientID
+ trackingID:kTrackingID
+ androidClientID:kAndroidClientID
+ databaseURL:kDatabaseURL
+ storageBucket:kStorageBucket
+ deepLinkURLScheme:kDeepLinkURLScheme];
+ [self assertOptionsMatchDefaults:options andProjectID:NO];
+ XCTAssertEqualObjects(options.deepLinkURLScheme, kDeepLinkURLScheme);
+ XCTAssertFalse(options.usingOptionsFromDefaultPlist);
+
+ FIROptions *options2 = [[FIROptions alloc] initWithGoogleAppID:kGoogleAppID
+ GCMSenderID:kGCMSenderID];
+ options2.androidClientID = kAndroidClientID;
+ options2.APIKey = kAPIKey;
+ options2.bundleID = kBundleID;
+ options2.clientID = kClientID;
+ options2.databaseURL = kDatabaseURL;
+ options2.deepLinkURLScheme = kDeepLinkURLScheme;
+ options2.projectID = kProjectID;
+ options2.storageBucket = kStorageBucket;
+ options2.trackingID = kTrackingID;
+ [self assertOptionsMatchDefaults:options2 andProjectID:YES];
+ XCTAssertEqualObjects(options2.deepLinkURLScheme, kDeepLinkURLScheme);
+ XCTAssertFalse(options.usingOptionsFromDefaultPlist);
+
+ // nil GoogleAppID should throw an exception
+ XCTAssertThrows([[FIROptions alloc] initWithGoogleAppID:nil
+ bundleID:kBundleID
+ GCMSenderID:kGCMSenderID
+ APIKey:kCustomizedAPIKey
+ clientID:nil
+ trackingID:nil
+ androidClientID:nil
+ databaseURL:nil
+ storageBucket:nil
+ deepLinkURLScheme:nil]);
+}
+
+- (void)testinitWithContentsOfFile {
+ NSString *filePath =
+ [[NSBundle mainBundle] pathForResource:@"GoogleService-Info" ofType:@"plist"];
+ FIROptions *options = [[FIROptions alloc] initWithContentsOfFile:filePath];
+ [self assertOptionsMatchDefaults:options andProjectID:YES];
+ XCTAssertNil(options.deepLinkURLScheme);
+ XCTAssertFalse(options.usingOptionsFromDefaultPlist);
+
+ FIROptions *emptyOptions = [[FIROptions alloc] initWithContentsOfFile:nil];
+ XCTAssertNil(emptyOptions);
+
+ FIROptions *invalidOptions = [[FIROptions alloc] initWithContentsOfFile:@"invalid.plist"];
+ XCTAssertNil(invalidOptions);
+}
+
+- (void)assertOptionsMatchDefaults:(FIROptions *)options andProjectID:(BOOL)matchProjectID {
+ XCTAssertEqualObjects(options.googleAppID, kGoogleAppID);
+ XCTAssertEqualObjects(options.APIKey, kAPIKey);
+ XCTAssertEqualObjects(options.clientID, kClientID);
+ XCTAssertEqualObjects(options.trackingID, kTrackingID);
+ XCTAssertEqualObjects(options.GCMSenderID, kGCMSenderID);
+ XCTAssertEqualObjects(options.androidClientID, kAndroidClientID);
+ XCTAssertEqualObjects(options.libraryVersionID, kFIRLibraryVersionID);
+ XCTAssertEqualObjects(options.databaseURL, kDatabaseURL);
+ XCTAssertEqualObjects(options.storageBucket, kStorageBucket);
+ XCTAssertEqualObjects(options.bundleID, kBundleID);
+
+ // Custom `matchProjectID` parameter to be removed once the deprecated `FIROptions` constructor is
+ // removed.
+ if (matchProjectID) {
+ XCTAssertEqualObjects(options.projectID, kProjectID);
+ }
+}
+
+- (void)testCopyingProperties {
+ NSMutableString *mutableString;
+ FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:kGoogleAppID
+ GCMSenderID:kGCMSenderID];
+ mutableString = [[NSMutableString alloc] initWithString:@"1"];
+ options.APIKey = mutableString;
+ [mutableString appendString:@"2"];
+ XCTAssertEqualObjects(options.APIKey, @"1");
+
+ mutableString = [[NSMutableString alloc] initWithString:@"1"];
+ options.bundleID = mutableString;
+ [mutableString appendString:@"2"];
+ XCTAssertEqualObjects(options.bundleID, @"1");
+
+ mutableString = [[NSMutableString alloc] initWithString:@"1"];
+ options.clientID = mutableString;
+ [mutableString appendString:@"2"];
+ XCTAssertEqualObjects(options.clientID, @"1");
+
+ mutableString = [[NSMutableString alloc] initWithString:@"1"];
+ options.trackingID = mutableString;
+ [mutableString appendString:@"2"];
+ XCTAssertEqualObjects(options.trackingID, @"1");
+
+ mutableString = [[NSMutableString alloc] initWithString:@"1"];
+ options.GCMSenderID = mutableString;
+ [mutableString appendString:@"2"];
+ XCTAssertEqualObjects(options.GCMSenderID, @"1");
+
+ mutableString = [[NSMutableString alloc] initWithString:@"1"];
+ options.projectID = mutableString;
+ [mutableString appendString:@"2"];
+ XCTAssertEqualObjects(options.projectID, @"1");
+
+ mutableString = [[NSMutableString alloc] initWithString:@"1"];
+ options.androidClientID = mutableString;
+ [mutableString appendString:@"2"];
+ XCTAssertEqualObjects(options.androidClientID, @"1");
+
+ mutableString = [[NSMutableString alloc] initWithString:@"1"];
+ options.googleAppID = mutableString;
+ [mutableString appendString:@"2"];
+ XCTAssertEqualObjects(options.googleAppID, @"1");
+
+ mutableString = [[NSMutableString alloc] initWithString:@"1"];
+ options.databaseURL = mutableString;
+ [mutableString appendString:@"2"];
+ XCTAssertEqualObjects(options.databaseURL, @"1");
+
+ mutableString = [[NSMutableString alloc] initWithString:@"1"];
+ options.deepLinkURLScheme = mutableString;
+ [mutableString appendString:@"2"];
+ XCTAssertEqualObjects(options.deepLinkURLScheme, @"1");
+
+ mutableString = [[NSMutableString alloc] initWithString:@"1"];
+ options.storageBucket = mutableString;
+ [mutableString appendString:@"2"];
+ XCTAssertEqualObjects(options.storageBucket, @"1");
+}
+
+- (void)testCopyWithZone {
+ // default options
+ FIROptions *options = [FIROptions defaultOptions];
+ options.deepLinkURLScheme = kDeepLinkURLScheme;
+ XCTAssertEqualObjects(options.deepLinkURLScheme, kDeepLinkURLScheme);
+
+ FIROptions *newOptions = [options copy];
+ XCTAssertEqualObjects(newOptions.deepLinkURLScheme, kDeepLinkURLScheme);
+
+ [options setDeepLinkURLScheme:kNewDeepLinkURLScheme];
+ XCTAssertEqualObjects(options.deepLinkURLScheme, kNewDeepLinkURLScheme);
+ XCTAssertEqualObjects(newOptions.deepLinkURLScheme, kDeepLinkURLScheme);
+
+ // customized options
+ FIROptions *customizedOptions = [[FIROptions alloc] initWithGoogleAppID:kGoogleAppID
+ bundleID:kBundleID
+ GCMSenderID:kGCMSenderID
+ APIKey:kAPIKey
+ clientID:kClientID
+ trackingID:kTrackingID
+ androidClientID:kAndroidClientID
+ databaseURL:kDatabaseURL
+ storageBucket:kStorageBucket
+ deepLinkURLScheme:kDeepLinkURLScheme];
+ FIROptions *copyCustomizedOptions = [customizedOptions copy];
+ [copyCustomizedOptions setDeepLinkURLScheme:kNewDeepLinkURLScheme];
+ XCTAssertEqualObjects(customizedOptions.deepLinkURLScheme, kDeepLinkURLScheme);
+ XCTAssertEqualObjects(copyCustomizedOptions.deepLinkURLScheme, kNewDeepLinkURLScheme);
+}
+
+- (void)testAnalyticsConstants {
+ // The keys are public values and should never change.
+ XCTAssertEqualObjects(kFIRIsMeasurementEnabled, @"IS_MEASUREMENT_ENABLED");
+ XCTAssertEqualObjects(kFIRIsAnalyticsCollectionEnabled, @"FIREBASE_ANALYTICS_COLLECTION_ENABLED");
+ XCTAssertEqualObjects(kFIRIsAnalyticsCollectionDeactivated,
+ @"FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED");
+}
+
+- (void)testAnalyticsOptions {
+ id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
+
+ // No keys anywhere.
+ NSDictionary *optionsDictionary = nil;
+ FIROptions *options = [[FIROptions alloc] initInternalWithOptionsDictionary:optionsDictionary];
+ NSDictionary *mainDictionary = nil;
+ OCMExpect([mainBundleMock infoDictionary]).andReturn(mainDictionary);
+ NSDictionary *expectedAnalyticsOptions = @{};
+ XCTAssertEqualObjects(options.analyticsOptionsDictionary, expectedAnalyticsOptions);
+
+ optionsDictionary = @{};
+ options = [[FIROptions alloc] initInternalWithOptionsDictionary:optionsDictionary];
+ mainDictionary = @{};
+ OCMExpect([mainBundleMock infoDictionary]).andReturn(mainDictionary);
+ expectedAnalyticsOptions = @{};
+ XCTAssertEqualObjects(options.analyticsOptionsDictionary, expectedAnalyticsOptions);
+
+ // Main has no keys.
+ optionsDictionary = @{
+ kFIRIsAnalyticsCollectionDeactivated : @YES,
+ kFIRIsAnalyticsCollectionEnabled : @YES,
+ kFIRIsMeasurementEnabled : @YES
+ };
+ options = [[FIROptions alloc] initInternalWithOptionsDictionary:optionsDictionary];
+ mainDictionary = @{};
+ OCMExpect([mainBundleMock infoDictionary]).andReturn(mainDictionary);
+ expectedAnalyticsOptions = optionsDictionary;
+ XCTAssertEqualObjects(options.analyticsOptionsDictionary, expectedAnalyticsOptions);
+
+ // Main overrides all the keys.
+ optionsDictionary = @{
+ kFIRIsAnalyticsCollectionDeactivated : @YES,
+ kFIRIsAnalyticsCollectionEnabled : @YES,
+ kFIRIsMeasurementEnabled : @YES
+ };
+ options = [[FIROptions alloc] initInternalWithOptionsDictionary:optionsDictionary];
+ mainDictionary = @{
+ kFIRIsAnalyticsCollectionDeactivated : @NO,
+ kFIRIsAnalyticsCollectionEnabled : @NO,
+ kFIRIsMeasurementEnabled : @NO
+ };
+ OCMExpect([mainBundleMock infoDictionary]).andReturn(mainDictionary);
+ expectedAnalyticsOptions = mainDictionary;
+ XCTAssertEqualObjects(options.analyticsOptionsDictionary, expectedAnalyticsOptions);
+
+ // Keys exist only in main.
+ optionsDictionary = @{};
+ options = [[FIROptions alloc] initInternalWithOptionsDictionary:optionsDictionary];
+ mainDictionary = @{
+ kFIRIsAnalyticsCollectionDeactivated : @YES,
+ kFIRIsAnalyticsCollectionEnabled : @YES,
+ kFIRIsMeasurementEnabled : @YES
+ };
+ OCMExpect([mainBundleMock infoDictionary]).andReturn(mainDictionary);
+ expectedAnalyticsOptions = mainDictionary;
+ XCTAssertEqualObjects(options.analyticsOptionsDictionary, expectedAnalyticsOptions);
+
+ // Main overrides single keys.
+ optionsDictionary = @{
+ kFIRIsAnalyticsCollectionDeactivated : @YES,
+ kFIRIsAnalyticsCollectionEnabled : @YES,
+ kFIRIsMeasurementEnabled : @YES
+ };
+ options = [[FIROptions alloc] initInternalWithOptionsDictionary:optionsDictionary];
+ mainDictionary = @{ kFIRIsAnalyticsCollectionDeactivated : @NO };
+ OCMExpect([mainBundleMock infoDictionary]).andReturn(mainDictionary);
+ expectedAnalyticsOptions = @{
+ kFIRIsAnalyticsCollectionDeactivated : @NO, // override
+ kFIRIsAnalyticsCollectionEnabled : @YES,
+ kFIRIsMeasurementEnabled : @YES
+ };
+ XCTAssertEqualObjects(options.analyticsOptionsDictionary, expectedAnalyticsOptions);
+
+ optionsDictionary = @{
+ kFIRIsAnalyticsCollectionDeactivated : @YES,
+ kFIRIsAnalyticsCollectionEnabled : @YES,
+ kFIRIsMeasurementEnabled : @YES
+ };
+ options = [[FIROptions alloc] initInternalWithOptionsDictionary:optionsDictionary];
+ mainDictionary = @{ kFIRIsAnalyticsCollectionEnabled : @NO };
+ OCMExpect([mainBundleMock infoDictionary]).andReturn(mainDictionary);
+ expectedAnalyticsOptions = @{
+ kFIRIsAnalyticsCollectionDeactivated : @YES,
+ kFIRIsAnalyticsCollectionEnabled : @NO, // override
+ kFIRIsMeasurementEnabled : @YES
+ };
+ XCTAssertEqualObjects(options.analyticsOptionsDictionary, expectedAnalyticsOptions);
+
+ optionsDictionary = @{
+ kFIRIsAnalyticsCollectionDeactivated : @YES,
+ kFIRIsAnalyticsCollectionEnabled : @YES,
+ kFIRIsMeasurementEnabled : @YES
+ };
+ options = [[FIROptions alloc] initInternalWithOptionsDictionary:optionsDictionary];
+ mainDictionary = @{ kFIRIsMeasurementEnabled : @NO };
+ OCMExpect([mainBundleMock infoDictionary]).andReturn(mainDictionary);
+ expectedAnalyticsOptions = @{
+ kFIRIsAnalyticsCollectionDeactivated : @YES,
+ kFIRIsAnalyticsCollectionEnabled : @YES,
+ kFIRIsMeasurementEnabled : @NO // override
+ };
+ XCTAssertEqualObjects(options.analyticsOptionsDictionary, expectedAnalyticsOptions);
+}
+
+- (void)testAnalyticsOptions_combinatorial {
+ // Complete combinatorial test.
+ id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
+ // Possible values for the flags in the plist, where NSNull means the flag is not present.
+ NSArray *values = @[ [NSNull null], @NO, @YES ];
+
+ // Sanity checks for the combination generation.
+ int combinationCount = 0;
+ NSMutableArray *uniqueMainCombinations = [[NSMutableArray alloc] init];
+ NSMutableArray *uniqueOptionsCombinations = [[NSMutableArray alloc] init];
+
+ // Generate all optout flag combinations for { main plist X GoogleService-info options plist }.
+ // Options present in the main plist should override options of the same key in the service plist.
+ for (id mainDeactivated in values) {
+ for (id mainAnalyticsEnabled in values) {
+ for (id mainMeasurementEnabled in values) {
+ for (id optionsDeactivated in values) {
+ for (id optionsAnalyticsEnabled in values) {
+ for (id optionsMeasurementEnabled in values) {
+ @autoreleasepool {
+ // Fill the GoogleService-info options plist dictionary.
+ NSMutableDictionary *optionsDictionary = [[NSMutableDictionary alloc] init];
+ if (![optionsDeactivated isEqual:[NSNull null]]) {
+ optionsDictionary[kFIRIsAnalyticsCollectionDeactivated] = optionsDeactivated;
+ }
+ if (![optionsAnalyticsEnabled isEqual:[NSNull null]]) {
+ optionsDictionary[kFIRIsAnalyticsCollectionEnabled] = optionsAnalyticsEnabled;
+ }
+ if (![optionsMeasurementEnabled isEqual:[NSNull null]]) {
+ optionsDictionary[kFIRIsMeasurementEnabled] = optionsMeasurementEnabled;
+ }
+ FIROptions *options =
+ [[FIROptions alloc] initInternalWithOptionsDictionary:optionsDictionary];
+ if (![uniqueOptionsCombinations containsObject:optionsDictionary]) {
+ [uniqueOptionsCombinations addObject:optionsDictionary];
+ }
+
+ // Fill the main plist dictionary.
+ NSMutableDictionary *mainDictionary = [[NSMutableDictionary alloc] init];
+ if (![mainDeactivated isEqual:[NSNull null]]) {
+ mainDictionary[kFIRIsAnalyticsCollectionDeactivated] = mainDeactivated;
+ }
+ if (![mainAnalyticsEnabled isEqual:[NSNull null]]) {
+ mainDictionary[kFIRIsAnalyticsCollectionEnabled] = mainAnalyticsEnabled;
+ }
+ if (![mainMeasurementEnabled isEqual:[NSNull null]]) {
+ mainDictionary[kFIRIsMeasurementEnabled] = mainMeasurementEnabled;
+ }
+ OCMExpect([mainBundleMock infoDictionary]).andReturn(mainDictionary);
+ if (![uniqueMainCombinations containsObject:mainDictionary]) {
+ [uniqueMainCombinations addObject:mainDictionary];
+ }
+
+ // Generate the expected options by adding main values on top of the service options
+ // values. The main values will replace any existing options values with the same
+ // key. This is a different way of combining the two sets of flags from the actual
+ // implementation in FIROptions, with equivalent output.
+ NSMutableDictionary *expectedAnalyticsOptions =
+ [[NSMutableDictionary alloc] initWithDictionary:optionsDictionary];
+ [expectedAnalyticsOptions addEntriesFromDictionary:mainDictionary];
+ XCTAssertEqualObjects(options.analyticsOptionsDictionary, expectedAnalyticsOptions);
+
+ combinationCount++;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Verify the sanity checks.
+ XCTAssertEqual(combinationCount, 729); // = 3^6.
+
+ XCTAssertEqual(uniqueOptionsCombinations.count, 27);
+ int optionsSizeCount[4] = {0};
+ for (NSDictionary *dictionary in uniqueOptionsCombinations) {
+ optionsSizeCount[dictionary.count]++;
+ }
+ XCTAssertEqual(optionsSizeCount[0], 1);
+ XCTAssertEqual(optionsSizeCount[1], 6);
+ XCTAssertEqual(optionsSizeCount[2], 12);
+ XCTAssertEqual(optionsSizeCount[3], 8);
+
+ XCTAssertEqual(uniqueMainCombinations.count, 27);
+ int mainSizeCount[4] = {0};
+ for (NSDictionary *dictionary in uniqueMainCombinations) {
+ mainSizeCount[dictionary.count]++;
+ }
+ XCTAssertEqual(mainSizeCount[0], 1);
+ XCTAssertEqual(mainSizeCount[1], 6);
+ XCTAssertEqual(mainSizeCount[2], 12);
+ XCTAssertEqual(mainSizeCount[3], 8);
+}
+
+- (void)testVersionFormat {
+ NSRegularExpression *sLibraryVersionRegex =
+ [NSRegularExpression regularExpressionWithPattern:@"^[0-9]{8,}$" options:0 error:NULL];
+ NSUInteger numberOfMatches =
+ [sLibraryVersionRegex numberOfMatchesInString:kFIRLibraryVersionID
+ options:0
+ range:NSMakeRange(0, kFIRLibraryVersionID.length)];
+ XCTAssertEqual(numberOfMatches, 1, @"Incorrect library version format.");
+}
+
+@end
diff --git a/Example/Core/Tests/FIRTestCase.h b/Example/Core/Tests/FIRTestCase.h
new file mode 100644
index 0000000..acc7b16
--- /dev/null
+++ b/Example/Core/Tests/FIRTestCase.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 <OCMock/OCMock.h>
+#import <XCTest/XCTest.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+extern NSString *const kAPIKey;
+extern NSString *const kCustomizedAPIKey;
+extern NSString *const kClientID;
+extern NSString *const kTrackingID;
+extern NSString *const kGCMSenderID;
+extern NSString *const kAndroidClientID;
+extern NSString *const kGoogleAppID;
+extern NSString *const kDatabaseURL;
+extern NSString *const kStorageBucket;
+
+extern NSString *const kDeepLinkURLScheme;
+extern NSString *const kNewDeepLinkURLScheme;
+
+extern NSString *const kBundleID;
+extern NSString *const kProjectID;
+
+/**
+ * Base test case for Firebase Core SDK tests.
+ */
+@interface FIRTestCase : XCTestCase
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Example/Core/Tests/FIRTestCase.m b/Example/Core/Tests/FIRTestCase.m
new file mode 100644
index 0000000..2c68b8d
--- /dev/null
+++ b/Example/Core/Tests/FIRTestCase.m
@@ -0,0 +1,47 @@
+// Copyright 2017 Google
+//
+// 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 "FIRTestCase.h"
+
+NSString *const kAPIKey = @"correct_api_key";
+NSString *const kCustomizedAPIKey = @"customized_api_key";
+NSString *const kClientID = @"correct_client_id";
+NSString *const kTrackingID = @"correct_tracking_id";
+NSString *const kGCMSenderID = @"correct_gcm_sender_id";
+NSString *const kAndroidClientID = @"correct_android_client_id";
+NSString *const kGoogleAppID = @"1:123:ios:123abc";
+NSString *const kDatabaseURL = @"https://abc-xyz-123.firebaseio.com";
+NSString *const kStorageBucket = @"project-id-123.storage.firebase.com";
+
+NSString *const kDeepLinkURLScheme = @"comgoogledeeplinkurl";
+NSString *const kNewDeepLinkURLScheme = @"newdeeplinkurlfortest";
+
+NSString *const kBundleID = @"com.google.FirebaseSDKTests";
+NSString *const kProjectID = @"abc-xyz-123";
+
+@interface FIRTestCase ()
+
+@end
+
+@implementation FIRTestCase
+
+- (void)setUp {
+ [super setUp];
+}
+
+- (void)tearDown {
+ [super tearDown];
+}
+
+@end
diff --git a/Example/Core/Tests/Tests-Info.plist b/Example/Core/Tests/Tests-Info.plist
new file mode 100644
index 0000000..169b6f7
--- /dev/null
+++ b/Example/Core/Tests/Tests-Info.plist
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundlePackageType</key>
+ <string>BNDL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+</dict>
+</plist>