aboutsummaryrefslogtreecommitdiffhomepage
path: root/Example/Auth
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/Auth
parent32461366c9e204a527ca05e6e9b9404a2454ac51 (diff)
Initial
Diffstat (limited to 'Example/Auth')
-rw-r--r--Example/Auth/App/Auth-Info.plist49
-rw-r--r--Example/Auth/App/Base.lproj/LaunchScreen.storyboard27
-rw-r--r--Example/Auth/App/Base.lproj/Main.storyboard27
-rw-r--r--Example/Auth/App/FIRAppDelegate.h23
-rw-r--r--Example/Auth/App/FIRAppDelegate.m52
-rw-r--r--Example/Auth/App/FIRViewController.h21
-rw-r--r--Example/Auth/App/FIRViewController.m35
-rw-r--r--Example/Auth/App/GoogleService-Info.plist30
-rw-r--r--Example/Auth/App/main.m23
-rw-r--r--Example/Auth/Tests/FIRAdditionalUserInfoTests.m124
-rw-r--r--Example/Auth/Tests/FIRApp+FIRAuthUnitTests.h36
-rw-r--r--Example/Auth/Tests/FIRApp+FIRAuthUnitTests.m45
-rw-r--r--Example/Auth/Tests/FIRAuthAPNSTokenManagerTests.m225
-rw-r--r--Example/Auth/Tests/FIRAuthAPNSTokenTests.m43
-rw-r--r--Example/Auth/Tests/FIRAuthAppCredentialManagerTests.m307
-rw-r--r--Example/Auth/Tests/FIRAuthAppCredentialTests.m67
-rw-r--r--Example/Auth/Tests/FIRAuthAppDelegateProxyTests.m450
-rw-r--r--Example/Auth/Tests/FIRAuthBackendCreateAuthURITests.m104
-rw-r--r--Example/Auth/Tests/FIRAuthBackendRPCImplementationTests.m989
-rw-r--r--Example/Auth/Tests/FIRAuthDispatcherTests.m105
-rw-r--r--Example/Auth/Tests/FIRAuthGlobalWorkQueueTests.m35
-rw-r--r--Example/Auth/Tests/FIRAuthKeychainTests.m314
-rw-r--r--Example/Auth/Tests/FIRAuthNotificationManagerTests.m291
-rw-r--r--Example/Auth/Tests/FIRAuthSerialTaskQueueTests.m113
-rw-r--r--Example/Auth/Tests/FIRAuthTests.m1743
-rw-r--r--Example/Auth/Tests/FIRAuthUserDefaultsStorageTests.m155
-rw-r--r--Example/Auth/Tests/FIRCreateAuthURIRequestTests.m103
-rw-r--r--Example/Auth/Tests/FIRCreateAuthURIResponseTests.m205
-rw-r--r--Example/Auth/Tests/FIRDeleteAccountRequestTests.m98
-rw-r--r--Example/Auth/Tests/FIRDeleteAccountResponseTests.m172
-rw-r--r--Example/Auth/Tests/FIRFakeBackendRPCIssuer.h100
-rw-r--r--Example/Auth/Tests/FIRFakeBackendRPCIssuer.m86
-rw-r--r--Example/Auth/Tests/FIRGetAccountInfoRequestTests.m87
-rw-r--r--Example/Auth/Tests/FIRGetAccountInfoResponseTests.m248
-rw-r--r--Example/Auth/Tests/FIRGetOOBConfirmationCodeRequestTests.m149
-rw-r--r--Example/Auth/Tests/FIRGetOOBConfirmationCodeResponseTests.m320
-rw-r--r--Example/Auth/Tests/FIRGitHubAuthProviderTests.m52
-rw-r--r--Example/Auth/Tests/FIRPhoneAuthProviderTests.m550
-rw-r--r--Example/Auth/Tests/FIRResetPasswordRequestTests.m101
-rw-r--r--Example/Auth/Tests/FIRResetPasswordResponseTests.m257
-rw-r--r--Example/Auth/Tests/FIRSendVerificationCodeRequestTests.m119
-rw-r--r--Example/Auth/Tests/FIRSendVerificationCodeResponseTests.m221
-rw-r--r--Example/Auth/Tests/FIRSetAccountInfoRequestTests.m285
-rw-r--r--Example/Auth/Tests/FIRSetAccountInfoResponseTests.m530
-rw-r--r--Example/Auth/Tests/FIRSignUpNewUserRequestTests.m140
-rw-r--r--Example/Auth/Tests/FIRSignUpNewUserResponseTests.m291
-rw-r--r--Example/Auth/Tests/FIRTwitterAuthProviderTests.m60
-rw-r--r--Example/Auth/Tests/FIRUserTests.m1801
-rw-r--r--Example/Auth/Tests/FIRVerifyAssertionRequestTests.m232
-rw-r--r--Example/Auth/Tests/FIRVerifyAssertionResponseTests.m426
-rw-r--r--Example/Auth/Tests/FIRVerifyClientRequestTest.m94
-rw-r--r--Example/Auth/Tests/FIRVerifyClientResponseTests.m178
-rw-r--r--Example/Auth/Tests/FIRVerifyCustomTokenRequestTests.m110
-rw-r--r--Example/Auth/Tests/FIRVerifyCustomTokenResponseTests.m274
-rw-r--r--Example/Auth/Tests/FIRVerifyPasswordRequestTest.m163
-rw-r--r--Example/Auth/Tests/FIRVerifyPasswordResponseTests.m454
-rw-r--r--Example/Auth/Tests/FIRVerifyPhoneNumberRequestTests.m154
-rw-r--r--Example/Auth/Tests/FIRVerifyPhoneNumberResponseTests.m271
-rw-r--r--Example/Auth/Tests/OCMStubRecorder+FIRAuthUnitTests.h112
-rw-r--r--Example/Auth/Tests/OCMStubRecorder+FIRAuthUnitTests.m103
-rw-r--r--Example/Auth/Tests/Tests-Info.plist22
61 files changed, 14001 insertions, 0 deletions
diff --git a/Example/Auth/App/Auth-Info.plist b/Example/Auth/App/Auth-Info.plist
new file mode 100644
index 0000000..7576a0d
--- /dev/null
+++ b/Example/Auth/App/Auth-Info.plist
@@ -0,0 +1,49 @@
+<?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>CFBundleDisplayName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1.0</string>
+ <key>LSRequiresIPhoneOS</key>
+ <true/>
+ <key>UILaunchStoryboardName</key>
+ <string>LaunchScreen</string>
+ <key>UIMainStoryboardFile</key>
+ <string>Main</string>
+ <key>UIRequiredDeviceCapabilities</key>
+ <array>
+ <string>armv7</string>
+ </array>
+ <key>UISupportedInterfaceOrientations</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ </array>
+ <key>UISupportedInterfaceOrientations~ipad</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ <string>UIInterfaceOrientationPortraitUpsideDown</string>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ </array>
+</dict>
+</plist>
diff --git a/Example/Auth/App/Base.lproj/LaunchScreen.storyboard b/Example/Auth/App/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 0000000..66a7681
--- /dev/null
+++ b/Example/Auth/App/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="16C67" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
+ <dependencies>
+ <deployment identifier="iOS"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
+ </dependencies>
+ <scenes>
+ <!--View Controller-->
+ <scene sceneID="EHf-IW-A2E">
+ <objects>
+ <viewController id="01J-lp-oVM" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
+ <viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
+ <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
+ </view>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="53" y="375"/>
+ </scene>
+ </scenes>
+</document>
diff --git a/Example/Auth/App/Base.lproj/Main.storyboard b/Example/Auth/App/Base.lproj/Main.storyboard
new file mode 100644
index 0000000..d164a23
--- /dev/null
+++ b/Example/Auth/App/Base.lproj/Main.storyboard
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="7706" systemVersion="14D136" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="whP-gf-Uak">
+ <dependencies>
+ <deployment identifier="iOS"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7703"/>
+ </dependencies>
+ <scenes>
+ <!--View Controller-->
+ <scene sceneID="wQg-tq-qST">
+ <objects>
+ <viewController id="whP-gf-Uak" customClass="FIRViewController" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="uEw-UM-LJ8"/>
+ <viewControllerLayoutGuide type="bottom" id="Mvr-aV-6Um"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="TpU-gO-2f1">
+ <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
+ </view>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="tc2-Qw-aMS" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="305" y="433"/>
+ </scene>
+ </scenes>
+</document>
diff --git a/Example/Auth/App/FIRAppDelegate.h b/Example/Auth/App/FIRAppDelegate.h
new file mode 100644
index 0000000..e3fba8f
--- /dev/null
+++ b/Example/Auth/App/FIRAppDelegate.h
@@ -0,0 +1,23 @@
+/*
+ * 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 UIKit;
+
+@interface FIRAppDelegate : UIResponder <UIApplicationDelegate>
+
+@property (strong, nonatomic) UIWindow *window;
+
+@end
diff --git a/Example/Auth/App/FIRAppDelegate.m b/Example/Auth/App/FIRAppDelegate.m
new file mode 100644
index 0000000..0ecfdea
--- /dev/null
+++ b/Example/Auth/App/FIRAppDelegate.m
@@ -0,0 +1,52 @@
+// 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 "FIRAppDelegate.h"
+
+@implementation FIRAppDelegate
+
+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
+{
+ // Override point for customization after application launch.
+ return YES;
+}
+
+- (void)applicationWillResignActive:(UIApplication *)application
+{
+ // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
+ // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
+}
+
+- (void)applicationDidEnterBackground:(UIApplication *)application
+{
+ // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
+ // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
+}
+
+- (void)applicationWillEnterForeground:(UIApplication *)application
+{
+ // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
+}
+
+- (void)applicationDidBecomeActive:(UIApplication *)application
+{
+ // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
+}
+
+- (void)applicationWillTerminate:(UIApplication *)application
+{
+ // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
+}
+
+@end
diff --git a/Example/Auth/App/FIRViewController.h b/Example/Auth/App/FIRViewController.h
new file mode 100644
index 0000000..64b4b74
--- /dev/null
+++ b/Example/Auth/App/FIRViewController.h
@@ -0,0 +1,21 @@
+/*
+ * 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 UIKit;
+
+@interface FIRViewController : UIViewController
+
+@end
diff --git a/Example/Auth/App/FIRViewController.m b/Example/Auth/App/FIRViewController.m
new file mode 100644
index 0000000..901accf
--- /dev/null
+++ b/Example/Auth/App/FIRViewController.m
@@ -0,0 +1,35 @@
+// 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 "FIRViewController.h"
+
+@interface FIRViewController ()
+
+@end
+
+@implementation FIRViewController
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+ // Do any additional setup after loading the view, typically from a nib.
+}
+
+- (void)didReceiveMemoryWarning
+{
+ [super didReceiveMemoryWarning];
+ // Dispose of any resources that can be recreated.
+}
+
+@end
diff --git a/Example/Auth/App/GoogleService-Info.plist b/Example/Auth/App/GoogleService-Info.plist
new file mode 100644
index 0000000..89afffe
--- /dev/null
+++ b/Example/Auth/App/GoogleService-Info.plist
@@ -0,0 +1,30 @@
+<?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>API_KEY</key>
+ <string>correct_api_key</string>
+ <key>TRACKING_ID</key>
+ <string>correct_tracking_id</string>
+ <key>CLIENT_ID</key>
+ <string>correct_client_id</string>
+ <key>REVERSED_CLIENT_ID</key>
+ <string>correct_reversed_client_id</string>
+ <key>ANDROID_CLIENT_ID</key>
+ <string>correct_android_client_id</string>
+ <key>GOOGLE_APP_ID</key>
+ <string>1:123:ios:123abc</string>
+ <key>GCM_SENDER_ID</key>
+ <string>correct_gcm_sender_id</string>
+ <key>PLIST_VERSION</key>
+ <string>1</string>
+ <key>BUNDLE_ID</key>
+ <string>com.google.FirebaseSDKTests</string>
+ <key>PROJECT_ID</key>
+ <string>abc-xyz-123</string>
+ <key>DATABASE_URL</key>
+ <string>https://abc-xyz-123.firebaseio.com</string>
+ <key>STORAGE_BUCKET</key>
+ <string>project-id-123.storage.firebase.com</string>
+</dict>
+</plist>
diff --git a/Example/Auth/App/main.m b/Example/Auth/App/main.m
new file mode 100644
index 0000000..03b5c12
--- /dev/null
+++ b/Example/Auth/App/main.m
@@ -0,0 +1,23 @@
+// 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 UIKit;
+#import "FIRAppDelegate.h"
+
+int main(int argc, char * argv[])
+{
+ @autoreleasepool {
+ return UIApplicationMain(argc, argv, nil, NSStringFromClass([FIRAppDelegate class]));
+ }
+}
diff --git a/Example/Auth/Tests/FIRAdditionalUserInfoTests.m b/Example/Auth/Tests/FIRAdditionalUserInfoTests.m
new file mode 100644
index 0000000..d50380e
--- /dev/null
+++ b/Example/Auth/Tests/FIRAdditionalUserInfoTests.m
@@ -0,0 +1,124 @@
+/*
+ * 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 "FIRAdditionalUserInfo_Internal.h"
+#import "FIRVerifyAssertionResponse.h"
+#import <OCMock/OCMock.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** @var kUserName
+ @brief The fake user name.
+ */
+static NSString *const kUserName = @"User Doe";
+
+/** @var kIsNewUser
+ @brief The fake flag that indicates the user has signed in for the first time.
+ */
+static BOOL kIsNewUser = YES;
+
+/** @var kProviderID
+ @brief The fake Provider ID.
+ */
+static NSString *const kProviderID = @"PROVIDER_ID";
+
+/** @class FIRAdditionalUserInfoTests
+ @brief Tests for @c FIRAdditionalUserInfo .
+ */
+@interface FIRAdditionalUserInfoTests : XCTestCase
+@end
+
+@implementation FIRAdditionalUserInfoTests
+
+/** @fn googleProfile
+ @brief The fake user profile under additional user data in @c FIRVerifyAssertionResponse.
+ */
++ (NSDictionary *)profile {
+ static NSDictionary *kProfile = nil;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ kProfile = @{
+ @"email": @"user@mail.com",
+ @"given_name": @"User",
+ @"family_name": @"Doe"
+ };
+ });
+ return kProfile;
+}
+
+/** @fn testAditionalUserInfoCreation
+ @brief Tests succuessful creation of @c FIRAdditionalUserInfo with
+ @c initWithProviderID:profile:username: call.
+ */
+- (void)testAditionalUserInfoCreation {
+ FIRAdditionalUserInfo *userInfo =
+ [[FIRAdditionalUserInfo alloc] initWithProviderID:kProviderID
+ profile:[[self class] profile]
+ username:kUserName
+ isNewUser:kIsNewUser];
+ XCTAssertEqualObjects(userInfo.providerID, kProviderID);
+ XCTAssertEqualObjects(userInfo.profile, [[self class] profile]);
+ XCTAssertEqualObjects(userInfo.username, kUserName);
+ XCTAssertEqual(userInfo.isNewUser, kIsNewUser);
+}
+
+/** @fn testAditionalUserInfoCreationWithStaticInitializer
+ @brief Tests succuessful creation of @c FIRAdditionalUserInfo with
+ @c userInfoWithVerifyAssertionResponse call.
+ */
+- (void)testAditionalUserInfoCreationWithStaticInitializer {
+ id mockVeriyAssertionResponse = OCMClassMock([FIRVerifyAssertionResponse class]);
+ OCMExpect([mockVeriyAssertionResponse providerID]).andReturn(kProviderID);
+ OCMExpect([mockVeriyAssertionResponse profile]).andReturn([[self class] profile]);
+ OCMExpect([mockVeriyAssertionResponse username]).andReturn(kUserName);
+ OCMExpect([mockVeriyAssertionResponse isNewUser]).andReturn(kIsNewUser);
+
+ FIRAdditionalUserInfo *userInfo =
+ [FIRAdditionalUserInfo userInfoWithVerifyAssertionResponse:mockVeriyAssertionResponse];
+ XCTAssertEqualObjects(userInfo.providerID, kProviderID);
+ XCTAssertEqualObjects(userInfo.profile, [[self class] profile]);
+ XCTAssertEqualObjects(userInfo.username, kUserName);
+ XCTAssertEqual(userInfo.isNewUser, kIsNewUser);
+ OCMVerifyAll(mockVeriyAssertionResponse);
+}
+
+/** @fn testAdditionalUserInfoCoding
+ @brief Tests successful archiving and unarchiving of @c FIRAdditionalUserInfo.
+ */
+- (void)testAdditionalUserInfoCoding {
+ FIRAdditionalUserInfo *userInfo =
+ [[FIRAdditionalUserInfo alloc] initWithProviderID:kProviderID
+ profile:[[self class] profile]
+ username:kUserName
+ isNewUser:kIsNewUser];
+ NSData *data = [NSKeyedArchiver archivedDataWithRootObject:userInfo];
+ XCTAssertNotNil(data, @"Should not be nil if archiving succeeded.");
+ XCTAssertNoThrow([NSKeyedUnarchiver unarchiveObjectWithData:data],
+ @"Unarchiving should not throw and exception.");
+ FIRAdditionalUserInfo *unarchivedUserInfo = [NSKeyedUnarchiver unarchiveObjectWithData:data];
+ XCTAssertTrue([unarchivedUserInfo isKindOfClass:[FIRAdditionalUserInfo class]],
+ @"Unarchived object must be of kind FIRAdditionalUserInfo class.");
+ XCTAssertEqualObjects(unarchivedUserInfo.providerID, userInfo.providerID);
+ XCTAssertEqualObjects(unarchivedUserInfo.profile, userInfo.profile);
+ XCTAssertEqualObjects(unarchivedUserInfo.username, userInfo.username);
+ XCTAssertEqual(unarchivedUserInfo.isNewUser, unarchivedUserInfo.isNewUser);
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Example/Auth/Tests/FIRApp+FIRAuthUnitTests.h b/Example/Auth/Tests/FIRApp+FIRAuthUnitTests.h
new file mode 100644
index 0000000..c0e6d13
--- /dev/null
+++ b/Example/Auth/Tests/FIRApp+FIRAuthUnitTests.h
@@ -0,0 +1,36 @@
+/*
+ * 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"
+
+/** @category FIRApp (FIRAuthUnitTests)
+ @brief Tests for @c FIRAuth.
+ */
+@interface FIRApp (FIRAuthUnitTests)
+
+/** @fn resetAppForAuthUnitTests
+ @brief Resets the Firebase app for unit tests.
+ */
++ (void)resetAppForAuthUnitTests;
+
+/** @fn appForAuthUnitTestsWithName:
+ @brief Creates a Firebase app with given name.
+ @param name The name for the app.
+ @return A @c FIRApp with the specified name.
+ */
++ (FIRApp *)appForAuthUnitTestsWithName:(NSString *)name;
+
+@end
diff --git a/Example/Auth/Tests/FIRApp+FIRAuthUnitTests.m b/Example/Auth/Tests/FIRApp+FIRAuthUnitTests.m
new file mode 100644
index 0000000..aba4136
--- /dev/null
+++ b/Example/Auth/Tests/FIRApp+FIRAuthUnitTests.m
@@ -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 "FIRApp+FIRAuthUnitTests.h"
+
+#import "FIROptionsInternal.h"
+
+@implementation FIRApp (FIRAuthUnitTests)
+
+/** @fn appOptions
+ @brief Gets Firebase app options to be used for tests.
+ @return A @c FIROptions instance.
+ */
++ (FIROptions *)appOptions {
+ return [[FIROptions alloc] initInternalWithOptionsDictionary:@{
+ @"GOOGLE_APP_ID" : @"1:1085102361755:ios:f790a919483d5bdf",
+ @"API_KEY" : @"FAKE_API_KEY",
+ @"GCM_SENDER_ID": @"217397612173"
+ }];
+}
+
++ (void)resetAppForAuthUnitTests {
+ [FIRApp resetApps];
+ [FIRApp configureWithOptions:[self appOptions]];
+}
+
++ (FIRApp *)appForAuthUnitTestsWithName:(NSString *)name {
+ return [[FIRApp alloc] initInstanceWithName:name options:[self appOptions]];
+}
+
+
+@end
diff --git a/Example/Auth/Tests/FIRAuthAPNSTokenManagerTests.m b/Example/Auth/Tests/FIRAuthAPNSTokenManagerTests.m
new file mode 100644
index 0000000..37d95a6
--- /dev/null
+++ b/Example/Auth/Tests/FIRAuthAPNSTokenManagerTests.m
@@ -0,0 +1,225 @@
+/*
+ * 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 "FIRAuthAPNSToken.h"
+#import "FIRAuthAPNSTokenManager.h"
+#import <OCMock/OCMock.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** @var kRegistrationTimeout
+ @brief The registration timeout used for testing.
+ */
+static const NSTimeInterval kRegistrationTimeout = .5;
+
+/** @var kExpectationTimeout
+ @brief The test expectation timeout.
+ @remarks This must be considerably greater than @c kVerificationTimeout .
+ */
+static const NSTimeInterval kExpectationTimeout = 1;
+
+/** @class FIRAuthLegacyUIApplication
+ @brief A fake legacy (< iOS 7) UIApplication class.
+ @remarks A custom class is needed because `respondsToSelector:` itself cannot be mocked.
+ */
+@interface FIRAuthLegacyUIApplication : NSObject
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+- (void)registerForRemoteNotificationTypes:(UIRemoteNotificationType)types;
+#pragma clang diagnostic pop
+
+@end
+@implementation FIRAuthLegacyUIApplication
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+- (void)registerForRemoteNotificationTypes:(UIRemoteNotificationType)types {
+}
+#pragma clang diagnostic pop
+
+@end
+
+/** @class FIRAuthAPNSTokenManagerTests
+ @brief Unit tests for @c FIRAuthAPNSTokenManager .
+ */
+@interface FIRAuthAPNSTokenManagerTests : XCTestCase
+@end
+@implementation FIRAuthAPNSTokenManagerTests {
+ /** @var _mockApplication
+ @brief The mock application for testing.
+ */
+ id _mockApplication;
+
+ /** @var _manager
+ @brief The @c FIRAuthAPNSTokenManager instance under tests.
+ */
+ FIRAuthAPNSTokenManager *_manager;
+
+ /** @var _data
+ @brief One piece of data used for testing.
+ */
+ NSData *_data;
+
+ /** @var _otherData
+ @brief Another piece of data used for testing.
+ */
+ NSData *_otherData;
+}
+
+- (void)setUp {
+ _mockApplication = OCMClassMock([UIApplication class]);
+ _manager = [[FIRAuthAPNSTokenManager alloc] initWithApplication:_mockApplication];
+ _data = [@"qwerty" dataUsingEncoding:NSUTF8StringEncoding];
+ _otherData = [@"!@#$" dataUsingEncoding:NSUTF8StringEncoding];
+}
+
+/** @fn testSetToken
+ @brief Tests setting and getting the `token` property.
+ */
+- (void)testSetToken {
+ XCTAssertNil(_manager.token);
+ _manager.token = [[FIRAuthAPNSToken alloc] initWithData:_data type:FIRAuthAPNSTokenTypeProd];
+ XCTAssertEqualObjects(_manager.token.data, _data);
+ XCTAssertEqual(_manager.token.type, FIRAuthAPNSTokenTypeProd);
+ _manager.token = nil;
+ XCTAssertNil(_manager.token);
+}
+
+/** @fn testDetectTokenType
+ @brief Tests automatic detection of token type.
+ */
+- (void)testDetectTokenType {
+ XCTAssertNil(_manager.token);
+ _manager.token = [[FIRAuthAPNSToken alloc] initWithData:_data type:FIRAuthAPNSTokenTypeUnknown];
+ XCTAssertEqualObjects(_manager.token.data, _data);
+ XCTAssertNotEqual(_manager.token.type, FIRAuthAPNSTokenTypeUnknown);
+}
+
+/** @fn testCallback
+ @brief Tests callbacks are called.
+ */
+- (void)testCallback {
+ // Add first callback, which is yet to be called.
+ OCMExpect([_mockApplication registerForRemoteNotifications]);
+ __block BOOL firstCallbackCalled = NO;
+ [_manager getTokenWithCallback:^(FIRAuthAPNSToken *_Nullable token) {
+ XCTAssertEqualObjects(token.data, _data);
+ XCTAssertEqual(token.type, FIRAuthAPNSTokenTypeSandbox);
+ firstCallbackCalled = YES;
+ }];
+ XCTAssertFalse(firstCallbackCalled);
+
+ // Add second callback, which is yet to be called either.
+ __block BOOL secondCallbackCalled = NO;
+ [_manager getTokenWithCallback:^(FIRAuthAPNSToken *_Nullable token) {
+ XCTAssertEqualObjects(token.data, _data);
+ XCTAssertEqual(token.type, FIRAuthAPNSTokenTypeSandbox);
+ secondCallbackCalled = YES;
+ }];
+ XCTAssertFalse(secondCallbackCalled);
+
+ // Setting nil token shouldn't trigger either callbacks.
+ _manager.token = nil;
+ XCTAssertFalse(firstCallbackCalled);
+ XCTAssertFalse(secondCallbackCalled);
+ XCTAssertNil(_manager.token);
+
+ // Setting a real token should trigger both callbacks.
+ _manager.token = [[FIRAuthAPNSToken alloc] initWithData:_data type:FIRAuthAPNSTokenTypeSandbox];
+ XCTAssertTrue(firstCallbackCalled);
+ XCTAssertTrue(secondCallbackCalled);
+ XCTAssertEqualObjects(_manager.token.data, _data);
+ XCTAssertEqual(_manager.token.type, FIRAuthAPNSTokenTypeSandbox);
+
+ // Add third callback, which should be called back immediately.
+ __block BOOL thirdCallbackCalled = NO;
+ [_manager getTokenWithCallback:^(FIRAuthAPNSToken *_Nullable token) {
+ XCTAssertEqualObjects(token.data, _data);
+ XCTAssertEqual(token.type, FIRAuthAPNSTokenTypeSandbox);
+ thirdCallbackCalled = YES;
+ }];
+ XCTAssertTrue(thirdCallbackCalled);
+
+ // Verify the mock in the main thread.
+ XCTestExpectation *expectation = [self expectationWithDescription:@"verify mock"];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ OCMVerifyAll(_mockApplication);
+ [expectation fulfill];
+ });
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+}
+
+/** @fn testTimeout
+ @brief Tests callbacks can be timed out.
+ */
+- (void)testTimeout {
+ // Set up timeout.
+ XCTAssertGreaterThan(_manager.timeout, 0);
+ _manager.timeout = kRegistrationTimeout;
+
+ // Add callback to time out.
+ OCMExpect([_mockApplication registerForRemoteNotifications]);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [_manager getTokenWithCallback:^(FIRAuthAPNSToken *_Nullable token) {
+ XCTAssertNil(token);
+ [expectation fulfill];
+ }];
+
+ // Time out.
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockApplication);
+}
+
+/** @fn testLegacyRegistration
+ @brief Tests remote notification registration on legacy systems.
+ */
+- (void)testLegacyRegistration {
+ // Use a custom class for `respondsToSelector:` to work.
+ _mockApplication = OCMClassMock([FIRAuthLegacyUIApplication class]);
+ _manager = [[FIRAuthAPNSTokenManager alloc] initWithApplication:_mockApplication];
+
+ // Add callback.
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ [[[_mockApplication expect] ignoringNonObjectArgs] registerForRemoteNotificationTypes:0];
+#pragma clang diagnostic pop
+ __block BOOL callbackCalled = NO;
+ [_manager getTokenWithCallback:^(FIRAuthAPNSToken *_Nullable token) {
+ XCTAssertEqualObjects(token.data, _data);
+ XCTAssertNotEqual(token.type, FIRAuthAPNSTokenTypeUnknown);
+ callbackCalled = YES;
+ }];
+ XCTAssertFalse(callbackCalled);
+
+ // Set the token.
+ _manager.token = [[FIRAuthAPNSToken alloc] initWithData:_data type:FIRAuthAPNSTokenTypeUnknown];
+ XCTAssertTrue(callbackCalled);
+
+ // Verify the mock in the main thread.
+ XCTestExpectation *expectation = [self expectationWithDescription:@"verify mock"];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ OCMVerifyAll(_mockApplication);
+ [expectation fulfill];
+ });
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Example/Auth/Tests/FIRAuthAPNSTokenTests.m b/Example/Auth/Tests/FIRAuthAPNSTokenTests.m
new file mode 100644
index 0000000..d2cd0b5
--- /dev/null
+++ b/Example/Auth/Tests/FIRAuthAPNSTokenTests.m
@@ -0,0 +1,43 @@
+/*
+ * 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 "FIRAuthAPNSToken.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** @class FIRAuthAPNSTokenTests
+ @brief Unit tests for @c FIRAuthAPNSToken .
+ */
+@interface FIRAuthAPNSTokenTests : XCTestCase
+@end
+@implementation FIRAuthAPNSTokenTests
+
+/** @fn testInitializer
+ @brief Tests the initializer of the class.
+ */
+- (void)testInitializer {
+ NSData *data = [@"asdf" dataUsingEncoding:NSUTF8StringEncoding];
+ FIRAuthAPNSToken *token = [[FIRAuthAPNSToken alloc] initWithData:data
+ type:FIRAuthAPNSTokenTypeProd];
+ XCTAssertEqualObjects(token.data, data);
+ XCTAssertEqual(token.type, FIRAuthAPNSTokenTypeProd);
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Example/Auth/Tests/FIRAuthAppCredentialManagerTests.m b/Example/Auth/Tests/FIRAuthAppCredentialManagerTests.m
new file mode 100644
index 0000000..32af8cd
--- /dev/null
+++ b/Example/Auth/Tests/FIRAuthAppCredentialManagerTests.m
@@ -0,0 +1,307 @@
+/*
+ * 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 "FIRAuthAppCredential.h"
+#import "FIRAuthAppCredentialManager.h"
+#import "FIRAuthKeychain.h"
+#import <OCMock/OCMock.h>
+
+#define ANY_ERROR_POINTER ((NSError *__autoreleasing *_Nullable)[OCMArg anyPointer])
+#define SAVE_TO(var) [OCMArg checkWithBlock:^BOOL(id arg) { var = arg; return YES; }]
+
+/** @var kReceipt
+ @brief A fake receipt used for testing.
+ */
+static NSString *const kReceipt = @"FAKE_RECEIPT";
+
+/** @var kAnotherReceipt
+ @brief Another fake receipt used for testing.
+ */
+static NSString *const kAnotherReceipt = @"OTHER_RECEIPT";
+
+/** @var kSecret
+ @brief A fake secret used for testing.
+ */
+static NSString *const kSecret = @"FAKE_SECRET";
+
+/** @var kAnotherSecret
+ @brief Another fake secret used for testing.
+ */
+static NSString *const kAnotherSecret = @"OTHER_SECRET";
+
+/** @var kVerificationTimeout
+ @brief The verification timeout used for testing.
+ */
+static const NSTimeInterval kVerificationTimeout = 1;
+
+/** @var kExpectationTimeout
+ @brief The test expectation timeout.
+ @remarks This must be considerably greater than @c kVerificationTimeout .
+ */
+static const NSTimeInterval kExpectationTimeout = 2;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** @class FIRAuthAppCredentialManagerTests
+ @brief Unit tests for @c FIRAuthAppCredentialManager .
+ */
+@interface FIRAuthAppCredentialManagerTests : XCTestCase
+@end
+@implementation FIRAuthAppCredentialManagerTests {
+ /** @var _mockKeychain
+ @brief The mock keychain for testing.
+ */
+ id _mockKeychain;
+}
+
+- (void)setUp {
+ _mockKeychain = OCMClassMock([FIRAuthKeychain class]);
+}
+
+/** @fn testCompletion
+ @brief Tests a successfully completed verification flow.
+ */
+- (void)testCompletion {
+ // Initial empty state.
+ OCMExpect([_mockKeychain dataForKey:OCMOCK_ANY error:ANY_ERROR_POINTER]).andReturn(nil);
+ FIRAuthAppCredentialManager *manager =
+ [[FIRAuthAppCredentialManager alloc] initWithKeychain:_mockKeychain];
+ XCTAssertNil(manager.credential);
+ OCMVerifyAll(_mockKeychain);
+
+ // Start verification.
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ OCMExpect([_mockKeychain setData:OCMOCK_ANY forKey:OCMOCK_ANY error:ANY_ERROR_POINTER])
+ .andReturn(YES);
+ [manager didStartVerificationWithReceipt:kReceipt
+ timeout:kVerificationTimeout
+ callback:^(FIRAuthAppCredential *credential) {
+ XCTAssertEqualObjects(credential.receipt, kReceipt);
+ XCTAssertEqualObjects(credential.secret, kSecret);
+ [expectation fulfill];
+ }];
+ XCTAssertNil(manager.credential);
+ OCMVerifyAll(_mockKeychain);
+
+ // Mismatched receipt shouldn't finish verification.
+ XCTAssertFalse([manager canFinishVerificationWithReceipt:kAnotherReceipt secret:kAnotherSecret]);
+ XCTAssertNil(manager.credential);
+
+ // Finish verification.
+ OCMExpect([_mockKeychain setData:OCMOCK_ANY forKey:OCMOCK_ANY error:ANY_ERROR_POINTER])
+ .andReturn(YES);
+ XCTAssertTrue([manager canFinishVerificationWithReceipt:kReceipt secret:kSecret]);
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ XCTAssertNotNil(manager.credential);
+ XCTAssertEqualObjects(manager.credential.receipt, kReceipt);
+ XCTAssertEqualObjects(manager.credential.secret, kSecret);
+ OCMVerifyAll(_mockKeychain);
+
+ // Repeated receipt should have no effect.
+ XCTAssertFalse([manager canFinishVerificationWithReceipt:kReceipt secret:kAnotherSecret]);
+ XCTAssertEqualObjects(manager.credential.secret, kSecret);
+}
+
+/** @fn testTimeout
+ @brief Tests a verification flow that times out.
+ */
+- (void)testTimeout {
+ // Initial empty state.
+ OCMExpect([_mockKeychain dataForKey:OCMOCK_ANY error:ANY_ERROR_POINTER]).andReturn(nil);
+ FIRAuthAppCredentialManager *manager =
+ [[FIRAuthAppCredentialManager alloc] initWithKeychain:_mockKeychain];
+ XCTAssertNil(manager.credential);
+ OCMVerifyAll(_mockKeychain);
+
+ // Start verification.
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ OCMExpect([_mockKeychain setData:OCMOCK_ANY forKey:OCMOCK_ANY error:ANY_ERROR_POINTER])
+ .andReturn(YES);
+ [manager didStartVerificationWithReceipt:kReceipt
+ timeout:kVerificationTimeout
+ callback:^(FIRAuthAppCredential *credential) {
+ XCTAssertEqualObjects(credential.receipt, kReceipt);
+ XCTAssertNil(credential.secret);
+ [expectation fulfill];
+ }];
+ XCTAssertNil(manager.credential);
+ OCMVerifyAll(_mockKeychain);
+
+ // Time-out.
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ XCTAssertNil(manager.credential);
+
+ // Completion after timeout.
+ OCMExpect([_mockKeychain setData:OCMOCK_ANY forKey:OCMOCK_ANY error:ANY_ERROR_POINTER])
+ .andReturn(YES);
+ XCTAssertTrue([manager canFinishVerificationWithReceipt:kReceipt secret:kSecret]);
+ XCTAssertNotNil(manager.credential);
+ XCTAssertEqualObjects(manager.credential.receipt, kReceipt);
+ XCTAssertEqualObjects(manager.credential.secret, kSecret);
+ OCMVerifyAll(_mockKeychain);
+}
+
+/** @fn testMaximumPendingReceipt
+ @brief Tests the maximum allowed number of pending receipt.
+ */
+- (void)testMaximumPendingReceipt {
+ // Initial empty state.
+ OCMExpect([_mockKeychain dataForKey:OCMOCK_ANY error:ANY_ERROR_POINTER]).andReturn(nil);
+ FIRAuthAppCredentialManager *manager =
+ [[FIRAuthAppCredentialManager alloc] initWithKeychain:_mockKeychain];
+ XCTAssertNil(manager.credential);
+ OCMVerifyAll(_mockKeychain);
+
+ // Start verification of the target receipt.
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ OCMExpect([_mockKeychain setData:OCMOCK_ANY forKey:OCMOCK_ANY error:ANY_ERROR_POINTER])
+ .andReturn(YES);
+ [manager didStartVerificationWithReceipt:kReceipt
+ timeout:kVerificationTimeout
+ callback:^(FIRAuthAppCredential *credential) {
+ XCTAssertEqualObjects(credential.receipt, kReceipt);
+ XCTAssertEqualObjects(credential.secret, kSecret);
+ [expectation fulfill];
+ }];
+ XCTAssertNil(manager.credential);
+ OCMVerifyAll(_mockKeychain);
+
+ // Start verification of a number of random receipts without overflowing.
+ for (NSUInteger i = 1; i < manager.maximumNumberOfPendingReceipts; i++) {
+ OCMExpect([_mockKeychain setData:OCMOCK_ANY forKey:OCMOCK_ANY error:ANY_ERROR_POINTER])
+ .andReturn(YES);
+ NSString *randomReceipt = [NSString stringWithFormat:@"RANDOM_%lu", (unsigned long)i];
+ XCTestExpectation *randomExpectation = [self expectationWithDescription:randomReceipt];
+ [manager didStartVerificationWithReceipt:randomReceipt
+ timeout:kVerificationTimeout
+ callback:^(FIRAuthAppCredential *credential) {
+ // They all should get full credential because one is available at this point.
+ XCTAssertEqualObjects(credential.receipt, kReceipt);
+ XCTAssertEqualObjects(credential.secret, kSecret);
+ [randomExpectation fulfill];
+ }];
+ }
+
+ // Finish verification of target receipt.
+ OCMExpect([_mockKeychain setData:OCMOCK_ANY forKey:OCMOCK_ANY error:ANY_ERROR_POINTER])
+ .andReturn(YES);
+ XCTAssertTrue([manager canFinishVerificationWithReceipt:kReceipt secret:kSecret]);
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ XCTAssertNotNil(manager.credential);
+ XCTAssertEqualObjects(manager.credential.receipt, kReceipt);
+ XCTAssertEqualObjects(manager.credential.secret, kSecret);
+ OCMVerifyAll(_mockKeychain);
+
+ // Clear credential to prepare for next round.
+ [manager clearCredential];
+ XCTAssertNil(manager.credential);
+
+ // Start verification of another target receipt.
+ expectation = [self expectationWithDescription:@"another callback"];
+ OCMExpect([_mockKeychain setData:OCMOCK_ANY forKey:OCMOCK_ANY error:ANY_ERROR_POINTER])
+ .andReturn(YES);
+ [manager didStartVerificationWithReceipt:kAnotherReceipt
+ timeout:kVerificationTimeout
+ callback:^(FIRAuthAppCredential *credential) {
+ XCTAssertEqualObjects(credential.receipt, kAnotherReceipt);
+ XCTAssertNil(credential.secret);
+ [expectation fulfill];
+ }];
+ XCTAssertNil(manager.credential);
+ OCMVerifyAll(_mockKeychain);
+
+ // Start verification of a number of random receipts to overflow.
+ for (NSUInteger i = 0; i < manager.maximumNumberOfPendingReceipts; i++) {
+ OCMExpect([_mockKeychain setData:OCMOCK_ANY forKey:OCMOCK_ANY error:ANY_ERROR_POINTER])
+ .andReturn(YES);
+ NSString *randomReceipt = [NSString stringWithFormat:@"RANDOM_%lu", (unsigned long)i];
+ XCTestExpectation *randomExpectation = [self expectationWithDescription:randomReceipt];
+ [manager didStartVerificationWithReceipt:randomReceipt
+ timeout:kVerificationTimeout
+ callback:^(FIRAuthAppCredential *credential) {
+ // They all should get partial credential because verification has never completed.
+ XCTAssertEqualObjects(credential.receipt, randomReceipt);
+ XCTAssertNil(credential.secret);
+ [randomExpectation fulfill];
+ }];
+ }
+
+ // Finish verification of the other target receipt.
+ XCTAssertFalse([manager canFinishVerificationWithReceipt:kAnotherReceipt secret:kAnotherSecret]);
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ XCTAssertNil(manager.credential);
+}
+
+/** @fn testKeychain
+ @brief Tests state preservation in the keychain.
+ */
+- (void)testKeychain {
+ // Initial empty state.
+ OCMExpect([_mockKeychain dataForKey:OCMOCK_ANY error:ANY_ERROR_POINTER]).andReturn(nil);
+ FIRAuthAppCredentialManager *manager =
+ [[FIRAuthAppCredentialManager alloc] initWithKeychain:_mockKeychain];
+ XCTAssertNil(manager.credential);
+ OCMVerifyAll(_mockKeychain);
+
+ // Start verification.
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ __block NSString *key;
+ __block NSString *data;
+ OCMExpect([_mockKeychain setData:SAVE_TO(data) forKey:SAVE_TO(key) error:ANY_ERROR_POINTER])
+ .andReturn(YES);
+ [manager didStartVerificationWithReceipt:kReceipt
+ timeout:kVerificationTimeout
+ callback:^(FIRAuthAppCredential *credential) {
+ XCTAssertEqualObjects(credential.receipt, kReceipt);
+ XCTAssertNil(credential.secret);
+ [expectation fulfill];
+ }];
+ XCTAssertNil(manager.credential);
+ OCMVerifyAll(_mockKeychain);
+
+ // Time-out.
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ XCTAssertNil(manager.credential);
+
+ // Start a new manager with saved data in keychain.
+ OCMExpect([_mockKeychain dataForKey:key error:ANY_ERROR_POINTER]).andReturn(data);
+ manager = [[FIRAuthAppCredentialManager alloc] initWithKeychain:_mockKeychain];
+ XCTAssertNil(manager.credential);
+ OCMVerifyAll(_mockKeychain);
+
+ // Finish verification.
+ OCMExpect([_mockKeychain setData:SAVE_TO(data) forKey:SAVE_TO(key) error:ANY_ERROR_POINTER])
+ .andReturn(YES);
+ XCTAssertTrue([manager canFinishVerificationWithReceipt:kReceipt secret:kSecret]);
+ XCTAssertNotNil(manager.credential);
+ XCTAssertEqualObjects(manager.credential.receipt, kReceipt);
+ XCTAssertEqualObjects(manager.credential.secret, kSecret);
+ OCMVerifyAll(_mockKeychain);
+
+ // Start yet another new manager with saved data in keychain.
+ OCMExpect([_mockKeychain dataForKey:key error:ANY_ERROR_POINTER]).andReturn(data);
+ manager = [[FIRAuthAppCredentialManager alloc] initWithKeychain:_mockKeychain];
+ XCTAssertNotNil(manager.credential);
+ XCTAssertEqualObjects(manager.credential.receipt, kReceipt);
+ XCTAssertEqualObjects(manager.credential.secret, kSecret);
+ OCMVerifyAll(_mockKeychain);
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Example/Auth/Tests/FIRAuthAppCredentialTests.m b/Example/Auth/Tests/FIRAuthAppCredentialTests.m
new file mode 100644
index 0000000..45dd6ef
--- /dev/null
+++ b/Example/Auth/Tests/FIRAuthAppCredentialTests.m
@@ -0,0 +1,67 @@
+/*
+ * 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 "FIRAuthAppCredential.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** @var kReceipt
+ @brief The fake receipt value for testing.
+ */
+static NSString *const kReceipt = @"RECEIPT";
+
+/** @var kSecret
+ @brief The fake secret value for testing.
+ */
+static NSString *const kSecret = @"SECRET";
+
+/** @class FIRAuthAppCredentialTests
+ @brief Unit tests for @c FIRAuthAppCredential .
+ */
+@interface FIRAuthAppCredentialTests : XCTestCase
+@end
+@implementation FIRAuthAppCredentialTests
+
+/** @fn testInitializer
+ @brief Tests the initializer of the class.
+ */
+- (void)testInitializer {
+ FIRAuthAppCredential *credential = [[FIRAuthAppCredential alloc] initWithReceipt:kReceipt
+ secret:kSecret];
+ XCTAssertEqualObjects(credential.receipt, kReceipt);
+ XCTAssertEqualObjects(credential.secret, kSecret);
+}
+
+/** @fn testSecureCoding
+ @brief Tests the implementation of NSSecureCoding protocol.
+ */
+- (void)testSecureCoding {
+ XCTAssertTrue([FIRAuthAppCredential supportsSecureCoding]);
+
+ FIRAuthAppCredential *credential = [[FIRAuthAppCredential alloc] initWithReceipt:kReceipt
+ secret:kSecret];
+ NSData *data = [NSKeyedArchiver archivedDataWithRootObject:credential];
+ XCTAssertNotNil(data);
+ FIRAuthAppCredential *otherCredential = [NSKeyedUnarchiver unarchiveObjectWithData:data];
+ XCTAssertEqualObjects(otherCredential.receipt, kReceipt);
+ XCTAssertEqualObjects(otherCredential.secret, kSecret);
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Example/Auth/Tests/FIRAuthAppDelegateProxyTests.m b/Example/Auth/Tests/FIRAuthAppDelegateProxyTests.m
new file mode 100644
index 0000000..9ff7473
--- /dev/null
+++ b/Example/Auth/Tests/FIRAuthAppDelegateProxyTests.m
@@ -0,0 +1,450 @@
+/*
+ * 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 <objc/runtime.h>
+
+#import "FIRAuthAppDelegateProxy.h"
+#import <OCMock/OCMock.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** @class FIRAuthEmptyAppDelegate
+ @brief A @c UIApplicationDelegate implementation that does nothing.
+ */
+@interface FIRAuthEmptyAppDelegate : NSObject <UIApplicationDelegate>
+@end
+@implementation FIRAuthEmptyAppDelegate
+@end
+
+/** @class FIRAuthLegacyAppDelegate
+ @brief A @c UIApplicationDelegate implementation that implements
+ `application:didReceiveRemoteNotification:`.
+ */
+@interface FIRAuthLegacyAppDelegate : NSObject <UIApplicationDelegate>
+
+/** @var notificationReceived
+ @brief The last notification received, if any.
+ */
+@property(nonatomic, copy, nullable) NSDictionary *notificationReceived;
+
+@end
+
+@implementation FIRAuthLegacyAppDelegate
+
+- (void)application:(UIApplication *)application
+ didReceiveRemoteNotification:(NSDictionary *)userInfo {
+ self.notificationReceived = userInfo;
+}
+
+@end
+
+/** @class FIRAuthModernAppDelegate
+ @brief A @c UIApplicationDelegate implementation that implements both
+ `application:didRegisterForRemoteNotificationsWithDeviceToken:` and
+ `application:didReceiveRemoteNotification:fetchCompletionHandler:`.
+ */
+@interface FIRAuthModernAppDelegate : NSObject <UIApplicationDelegate>
+
+/** @var deviceTokenReceived
+ @brief The last device token received, if any.
+ */
+@property(nonatomic, copy, nullable) NSData *deviceTokenReceived;
+
+/** @var notificationReceived
+ @brief The last notification received, if any.
+ */
+@property(nonatomic, copy, nullable) NSDictionary *notificationReceived;
+
+@end
+
+@implementation FIRAuthModernAppDelegate
+
+- (void)application:(UIApplication *)application
+ didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
+ self.deviceTokenReceived = deviceToken;
+}
+
+- (void)application:(UIApplication *)application
+ didReceiveRemoteNotification:(NSDictionary *)userInfo
+ fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
+ self.notificationReceived = userInfo;
+ completionHandler(UIBackgroundFetchResultNewData);
+}
+
+@end
+
+/** @class FIRAuthAppDelegateProxyTests
+ @brief Unit tests for @c FIRAuthAppDelegateProxy .
+ */
+@interface FIRAuthAppDelegateProxyTests : XCTestCase
+@end
+@implementation FIRAuthAppDelegateProxyTests {
+ /** @var _mockApplication
+ @brief The mock UIApplication used for testing.
+ */
+ id _mockApplication;
+
+ /** @var _deviceToken
+ @brief The fake APNs device token for testing.
+ */
+ NSData *_deviceToken;
+
+ /** @var _notification
+ @brief The fake notification for testing.
+ */
+ NSDictionary* _notification;
+}
+
+- (void)setUp {
+ [super setUp];
+ _mockApplication = OCMClassMock([UIApplication class]);
+ _deviceToken = [@"asdf" dataUsingEncoding:NSUTF8StringEncoding];
+ _notification = @{ @"zxcv" : @1234 };
+}
+
+- (void)tearDown {
+ OCMVerifyAll(_mockApplication);
+ [super tearDown];
+}
+
+/** @fn testSharedInstance
+ @brief Tests that the shared instance is the same one.
+ */
+- (void)testSharedInstance {
+ FIRAuthAppDelegateProxy *proxy1 = [FIRAuthAppDelegateProxy sharedInstance];
+ FIRAuthAppDelegateProxy *proxy2 = [FIRAuthAppDelegateProxy sharedInstance];
+ XCTAssertEqual(proxy1, proxy2);
+}
+
+/** @fn testNilApplication
+ @brief Tests that initialization fails if the application is nil.
+ */
+- (void)testNilApplication {
+ XCTAssertNil([[FIRAuthAppDelegateProxy alloc] initWithApplication:nil]);
+}
+
+/** @fn testNilDelegate
+ @brief Tests that initialization fails if the application's delegate is nil.
+ */
+- (void)testNilDelegate {
+ OCMExpect([_mockApplication delegate]).andReturn(nil);
+ XCTAssertNil([[FIRAuthAppDelegateProxy alloc] initWithApplication:_mockApplication]);
+}
+
+/** @fn testNonconformingDelegate
+ @brief Tests that initialization fails if the application's delegate does not conform to
+ @c UIApplicationDelegate protocol.
+ */
+- (void)testNonconformingDelegate {
+ OCMExpect([_mockApplication delegate]).andReturn(@"abc");
+ XCTAssertNil([[FIRAuthAppDelegateProxy alloc] initWithApplication:_mockApplication]);
+}
+
+/** @fn testDisabledByBundleEntry
+ @brief Tests that initialization fails if the proxy is disabled by a bundle entry.
+ */
+- (void)testDisabledByBundleEntry {
+ // Swizzle NSBundle's objectForInfoDictionaryKey to return @NO for the specific key.
+ Method method = class_getInstanceMethod([NSBundle class], @selector(objectForInfoDictionaryKey:));
+ __block IMP originalImplementation;
+ IMP newImplmentation = imp_implementationWithBlock(^id(id object, NSString *key) {
+ if ([key isEqualToString:@"FirebaseAppDelegateProxyEnabled"]) {
+ return @NO;
+ }
+ typedef id (*Implementation)(id object, SEL cmd, NSString *key);
+ return ((Implementation)originalImplementation)(object, @selector(objectForInfoDictionaryKey:),
+ key);
+ });
+ originalImplementation = method_setImplementation(method, newImplmentation);
+
+ // Verify that initialization fails.
+ FIRAuthEmptyAppDelegate *delegate = [[FIRAuthEmptyAppDelegate alloc] init];
+ OCMStub([_mockApplication delegate]).andReturn(delegate);
+ XCTAssertNil([[FIRAuthAppDelegateProxy alloc] initWithApplication:_mockApplication]);
+
+ // Unswizzle.
+ imp_removeBlock(method_setImplementation(method, originalImplementation));
+}
+
+/** @fn testEmptyDelegateOneHandler
+ @brief Tests that the proxy works against an empty @c UIApplicationDelegate for one handler.
+ */
+- (void)testEmptyDelegateOneHandler {
+ FIRAuthEmptyAppDelegate *delegate = [[FIRAuthEmptyAppDelegate alloc] init];
+ OCMExpect([_mockApplication delegate]).andReturn(delegate);
+ __weak id weakProxy;
+ @autoreleasepool {
+ FIRAuthAppDelegateProxy *proxy =
+ [[FIRAuthAppDelegateProxy alloc] initWithApplication:_mockApplication];
+ XCTAssertNotNil(proxy);
+
+ // Verify `application:didReceiveRemoteNotification:` is not swizzled.
+ XCTAssertFalse([delegate respondsToSelector:
+ @selector(application:didReceiveRemoteNotification:)]);
+
+ // Verify the handler is called after being added.
+ __weak id weakHandler;
+ @autoreleasepool {
+ id mockHandler = OCMProtocolMock(@protocol(FIRAuthAppDelegateHandler));
+ [proxy addHandler:mockHandler];
+
+ // Verify handling of `application:didRegisterForRemoteNotificationsWithDeviceToken:`.
+ OCMExpect([mockHandler setAPNSToken:_deviceToken]);
+ [delegate application:_mockApplication
+ didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken];
+ OCMVerifyAll(mockHandler);
+
+ // Verify handling of `application:didReceiveRemoteNotification:fetchCompletionHandler:`.
+ OCMExpect([mockHandler canHandleNotification:_notification]).andReturn(YES);
+ __block BOOL fetchCompletionHandlerCalled = NO;
+ [delegate application:_mockApplication
+ didReceiveRemoteNotification:_notification
+ fetchCompletionHandler:^(UIBackgroundFetchResult result) {
+ XCTAssertEqual(result, UIBackgroundFetchResultNoData);
+ fetchCompletionHandlerCalled = YES;
+ }];
+ OCMVerifyAll(mockHandler);
+ XCTAssertTrue(fetchCompletionHandlerCalled);
+
+ weakHandler = mockHandler;
+ XCTAssertNotNil(weakHandler);
+ }
+ // Verify the handler is not retained by the proxy.
+ XCTAssertNil(weakHandler);
+
+ // Verify nothing bad happens after the handler is released.
+ [delegate application:_mockApplication
+ didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken];
+ [delegate application:_mockApplication
+ didReceiveRemoteNotification:_notification
+ fetchCompletionHandler:^(UIBackgroundFetchResult result) {
+ XCTFail(@"Should not call completion handler.");
+ }];
+
+ weakProxy = proxy;
+ XCTAssertNotNil(weakProxy);
+ }
+ // Verify the proxy does not retain itself.
+ XCTAssertNil(weakProxy);
+ // Verify nothing bad happens after the proxy is released.
+ [delegate application:_mockApplication
+ didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken];
+ [delegate application:_mockApplication
+ didReceiveRemoteNotification:_notification
+ fetchCompletionHandler:^(UIBackgroundFetchResult result) {
+ XCTFail(@"Should not call completion handler.");
+ }];
+}
+
+/** @fn testLegacyDelegateTwoHandlers
+ @brief Tests that the proxy works against a legacy @c UIApplicationDelegate for two handlers.
+ */
+- (void)testLegacyDelegateTwoHandlers {
+ FIRAuthLegacyAppDelegate *delegate = [[FIRAuthLegacyAppDelegate alloc] init];
+ OCMExpect([_mockApplication delegate]).andReturn(delegate);
+ __weak id weakProxy;
+ @autoreleasepool {
+ FIRAuthAppDelegateProxy *proxy =
+ [[FIRAuthAppDelegateProxy alloc] initWithApplication:_mockApplication];
+ XCTAssertNotNil(proxy);
+
+ // Verify `application:didReceiveRemoteNotification:fetchCompletionHandler` is not swizzled.
+ XCTAssertFalse([delegate respondsToSelector:
+ @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:)]);
+
+ // Verify the handler is called after being added.
+ __weak id weakHandler1;
+ @autoreleasepool {
+ id mockHandler1 = OCMProtocolMock(@protocol(FIRAuthAppDelegateHandler));
+ [proxy addHandler:mockHandler1];
+ __weak id weakHandler2;
+ @autoreleasepool {
+ id mockHandler2 = OCMProtocolMock(@protocol(FIRAuthAppDelegateHandler));
+ [proxy addHandler:mockHandler2];
+
+ // Verify handling of `application:didRegisterForRemoteNotificationsWithDeviceToken:`.
+ OCMExpect([mockHandler1 setAPNSToken:_deviceToken]);
+ OCMExpect([mockHandler2 setAPNSToken:_deviceToken]);
+ [delegate application:_mockApplication
+ didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken];
+ OCMVerifyAll(mockHandler1);
+ OCMVerifyAll(mockHandler2);
+
+ // Verify handling of `application:didReceiveRemoteNotification:fetchCompletionHandler:`.
+ OCMExpect([mockHandler1 canHandleNotification:_notification]).andReturn(YES);
+ // handler2 shouldn't been invoked because it is already handled by handler1.
+ [delegate application:_mockApplication didReceiveRemoteNotification:_notification];
+ OCMVerifyAll(mockHandler1);
+ OCMVerifyAll(mockHandler2);
+ XCTAssertNil(delegate.notificationReceived);
+
+ weakHandler2 = mockHandler2;
+ XCTAssertNotNil(weakHandler2);
+ }
+ // Verify the handler2 is not retained by the proxy.
+ XCTAssertNil(weakHandler2);
+
+ // Verify handling of `application:didRegisterForRemoteNotificationsWithDeviceToken:`.
+ OCMExpect([mockHandler1 setAPNSToken:_deviceToken]);
+ [delegate application:_mockApplication
+ didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken];
+ OCMVerifyAll(mockHandler1);
+
+ // Verify NOT handling of `application:didReceiveRemoteNotification:fetchCompletionHandler:`.
+ OCMExpect([mockHandler1 canHandleNotification:_notification]).andReturn(NO);
+ [delegate application:_mockApplication didReceiveRemoteNotification:_notification];
+ OCMVerifyAll(mockHandler1);
+ XCTAssertEqualObjects(delegate.notificationReceived, _notification);
+ delegate.notificationReceived = nil;
+
+ weakHandler1 = mockHandler1;
+ XCTAssertNotNil(weakHandler1);
+ }
+ // Verify the handler1 is not retained by the proxy.
+ XCTAssertNil(weakHandler1);
+
+ // Verify the delegate still works after all handlers are released.
+ [delegate application:_mockApplication
+ didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken];
+ [delegate application:_mockApplication didReceiveRemoteNotification:_notification];
+ XCTAssertEqualObjects(delegate.notificationReceived, _notification);
+ delegate.notificationReceived = nil;
+
+ weakProxy = proxy;
+ XCTAssertNotNil(weakProxy);
+ }
+ // Verify the proxy does not retain itself.
+ XCTAssertNil(weakProxy);
+
+ // Verify the delegate still works after the proxy is released.
+ [delegate application:_mockApplication
+ didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken];
+ [delegate application:_mockApplication didReceiveRemoteNotification:_notification];
+ XCTAssertEqualObjects(delegate.notificationReceived, _notification);
+ delegate.notificationReceived = nil;
+}
+
+/** @fn testModernDelegateWithOtherInstance
+ @brief Tests that the proxy works against a modern @c UIApplicationDelegate along with
+ another unaffected instance.
+ */
+- (void)testModernDelegateWithUnaffectedInstance {
+ FIRAuthModernAppDelegate *delegate = [[FIRAuthModernAppDelegate alloc] init];
+ OCMExpect([_mockApplication delegate]).andReturn(delegate);
+ FIRAuthModernAppDelegate *unaffectedDelegate = [[FIRAuthModernAppDelegate alloc] init];
+ __weak id weakProxy;
+ @autoreleasepool {
+ FIRAuthAppDelegateProxy *proxy =
+ [[FIRAuthAppDelegateProxy alloc] initWithApplication:_mockApplication];
+ XCTAssertNotNil(proxy);
+
+ // Verify `application:didReceiveRemoteNotification:` is not swizzled.
+ XCTAssertFalse([delegate respondsToSelector:
+ @selector(application:didReceiveRemoteNotification:)]);
+
+ // Verify the handler is called after being added.
+ __weak id weakHandler;
+ @autoreleasepool {
+ id mockHandler = OCMProtocolMock(@protocol(FIRAuthAppDelegateHandler));
+ [proxy addHandler:mockHandler];
+
+ // Verify handling of `application:didRegisterForRemoteNotificationsWithDeviceToken:`.
+ OCMExpect([mockHandler setAPNSToken:_deviceToken]);
+ [delegate application:_mockApplication
+ didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken];
+ OCMVerifyAll(mockHandler);
+ XCTAssertEqualObjects(delegate.deviceTokenReceived, _deviceToken);
+ delegate.deviceTokenReceived = nil;
+
+ // Verify handling of `application:didReceiveRemoteNotification:fetchCompletionHandler:`.
+ OCMExpect([mockHandler canHandleNotification:_notification]).andReturn(YES);
+ __block BOOL fetchCompletionHandlerCalled = NO;
+ [delegate application:_mockApplication
+ didReceiveRemoteNotification:_notification
+ fetchCompletionHandler:^(UIBackgroundFetchResult result) {
+ XCTAssertEqual(result, UIBackgroundFetchResultNoData);
+ fetchCompletionHandlerCalled = YES;
+ }];
+ OCMVerifyAll(mockHandler);
+ XCTAssertTrue(fetchCompletionHandlerCalled);
+ XCTAssertNil(delegate.notificationReceived);
+
+ // Verify unaffected delegate instance.
+ [unaffectedDelegate application:_mockApplication
+ didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken];
+ XCTAssertEqualObjects(unaffectedDelegate.deviceTokenReceived, _deviceToken);
+ unaffectedDelegate.deviceTokenReceived = nil;
+ fetchCompletionHandlerCalled = NO;
+ [unaffectedDelegate application:_mockApplication
+ didReceiveRemoteNotification:_notification
+ fetchCompletionHandler:^(UIBackgroundFetchResult result) {
+ XCTAssertEqual(result, UIBackgroundFetchResultNewData);
+ fetchCompletionHandlerCalled = YES;
+ }];
+ XCTAssertTrue(fetchCompletionHandlerCalled);
+ XCTAssertEqualObjects(unaffectedDelegate.notificationReceived, _notification);
+ unaffectedDelegate.notificationReceived = nil;
+
+ weakHandler = mockHandler;
+ XCTAssertNotNil(weakHandler);
+ }
+ // Verify the handler is not retained by the proxy.
+ XCTAssertNil(weakHandler);
+
+ // Verify the delegate still works after the handler is released.
+ [delegate application:_mockApplication
+ didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken];
+ XCTAssertEqualObjects(delegate.deviceTokenReceived, _deviceToken);
+ delegate.deviceTokenReceived = nil;
+ __block BOOL fetchCompletionHandlerCalled = NO;
+ [delegate application:_mockApplication
+ didReceiveRemoteNotification:_notification
+ fetchCompletionHandler:^(UIBackgroundFetchResult result) {
+ XCTAssertEqual(result, UIBackgroundFetchResultNewData);
+ fetchCompletionHandlerCalled = YES;
+ }];
+ XCTAssertEqualObjects(delegate.notificationReceived, _notification);
+ delegate.notificationReceived = nil;
+ XCTAssertTrue(fetchCompletionHandlerCalled);
+
+ weakProxy = proxy;
+ XCTAssertNotNil(weakProxy);
+ }
+ // Verify the proxy does not retain itself.
+ XCTAssertNil(weakProxy);
+
+ // Verify the delegate still works after the proxy is released.
+ [delegate application:_mockApplication
+ didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken];
+ XCTAssertEqualObjects(delegate.deviceTokenReceived, _deviceToken);
+ delegate.deviceTokenReceived = nil;
+ __block BOOL fetchCompletionHandlerCalled = NO;
+ [delegate application:_mockApplication
+ didReceiveRemoteNotification:_notification
+ fetchCompletionHandler:^(UIBackgroundFetchResult result) {
+ XCTAssertEqual(result, UIBackgroundFetchResultNewData);
+ fetchCompletionHandlerCalled = YES;
+ }];
+ XCTAssertEqualObjects(delegate.notificationReceived, _notification);
+ delegate.notificationReceived = nil;
+ XCTAssertTrue(fetchCompletionHandlerCalled);
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Example/Auth/Tests/FIRAuthBackendCreateAuthURITests.m b/Example/Auth/Tests/FIRAuthBackendCreateAuthURITests.m
new file mode 100644
index 0000000..5d40343
--- /dev/null
+++ b/Example/Auth/Tests/FIRAuthBackendCreateAuthURITests.m
@@ -0,0 +1,104 @@
+/*
+ * 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 "FIRAuthErrorUtils.h"
+#import "FIRAuthBackend.h"
+#import "FIRCreateAuthURIRequest.h"
+#import "FIRCreateAuthURIResponse.h"
+#import "FIRFakeBackendRPCIssuer.h"
+
+/** @var kTestIdentifier
+ @brief A test value for @c FIRCreateAuthURIRequest.identifier
+ */
+static NSString *const kTestIdentifier = @"identifier_value";
+
+/** @var kTestContinueURI
+ @brief A test value for @c FIRCreateAuthURIRequest.continueURI
+ */
+static NSString *const kTestContinueURI = @"https://www.example.com/";
+
+/** @var kTestAPIKey
+ @brief A test value for @c FIRCreateAuthURIRequest.APIKey
+ */
+static NSString *const kTestAPIKey = @"apikey_value";
+
+/** @var kTestExpectedRequestURL
+ @brief The URL we are expecting should be requested by valid requests.
+ */
+static NSString *const kTestExpectedRequestURL =
+ @"https://www.googleapis.com/identitytoolkit/v3/relyingparty/createAuthUri?key=apikey_value";
+
+/** @var kTestExpectedKind
+ @brief The expected value for the "kind" parameter of a successful response.
+ */
+static NSString *const kTestExpectedKind = @"identitytoolkit#CreateAuthUriResponse";
+
+/** @var kTestProviderID1
+ @brief A valid value for a provider ID in the @c FIRCreateAuthURIResponse.allProviders array.
+ */
+static NSString *const kTestProviderID1 = @"google.com";
+
+/** @var kTestProviderID2
+ @brief A valid value for a provider ID in the @c FIRCreateAuthURIResponse.allProviders array.
+ */
+static NSString *const kTestProviderID2 = @"facebook.com";
+
+/** @class FIRAuthBackendCreateAuthURITests
+ @brief Unit tests for createAuthURI.
+ */
+@interface FIRAuthBackendCreateAuthURITests : XCTestCase
+@end
+@implementation FIRAuthBackendCreateAuthURITests
+
+- (void)testRequestAndResponseEncoding {
+ FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init];
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer];
+
+ FIRCreateAuthURIRequest *request =
+ [[FIRCreateAuthURIRequest alloc] initWithIdentifier:kTestIdentifier
+ continueURI:kTestContinueURI
+ APIKey:kTestAPIKey];
+
+ __block FIRCreateAuthURIResponse *createAuthURIResponse;
+ __block NSError *createAuthURIError;
+ __block BOOL callbackInvoked;
+ [FIRAuthBackend createAuthURI:request
+ callback:^(FIRCreateAuthURIResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ createAuthURIResponse = response;
+ createAuthURIError = error;
+ }];
+
+ XCTAssertEqualObjects(RPCIssuer.requestURL.absoluteString, kTestExpectedRequestURL);
+ XCTAssertEqualObjects(RPCIssuer.decodedRequest[@"identifier"], kTestIdentifier);
+ XCTAssertEqualObjects(RPCIssuer.decodedRequest[@"continueUri"], kTestContinueURI);
+
+ [RPCIssuer respondWithJSON:@{
+ @"kind" : kTestExpectedKind,
+ @"allProviders" : @[ kTestProviderID1, kTestProviderID2 ]
+ }];
+
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(createAuthURIError);
+ XCTAssertEqual(createAuthURIResponse.allProviders.count, 2);
+ XCTAssertEqualObjects(createAuthURIResponse.allProviders[0], kTestProviderID1);
+ XCTAssertEqualObjects(createAuthURIResponse.allProviders[1], kTestProviderID2);
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRAuthBackendRPCImplementationTests.m b/Example/Auth/Tests/FIRAuthBackendRPCImplementationTests.m
new file mode 100644
index 0000000..5930e13
--- /dev/null
+++ b/Example/Auth/Tests/FIRAuthBackendRPCImplementationTests.m
@@ -0,0 +1,989 @@
+/*
+ * 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 "FIRAuthErrorUtils.h"
+#import "FIRAuthInternalErrors.h"
+#import "FIRAuthBackend.h"
+#import "FIRAuthRPCRequest.h"
+#import "FIRAuthRPCResponse.h"
+#import "FIRFakeBackendRPCIssuer.h"
+
+/** @var kFakeRequestURL
+ @brief Used as a fake URL for a fake RPC request. We don't test this here, since it's tested
+ for the specific RPC requests in their various unit tests.
+ */
+static NSString *const kFakeRequestURL = @"https://www.google.com/";
+
+/** @var kFakeErrorDomain
+ @brief A value to use for fake @c NSErrors.
+ */
+static NSString *const kFakeErrorDomain = @"fakeDomain";
+
+/** @var kFakeErrorCode
+ @brief A value to use for fake @c NSErrors.
+ */
+static const NSUInteger kFakeErrorCode = -1;
+
+/** @var kUnknownServerErrorMessage
+ @brief A value to use for fake server errors with an unknown message.
+ */
+static NSString *const kUnknownServerErrorMessage = @"UNKNOWN_MESSAGE";
+
+/** @var kErrorMessageCaptchaRequired
+ @brief The error message in JSON responses from the server for CAPTCHA required.
+ */
+static NSString *const kErrorMessageCaptchaRequired = @"CAPTCHA_REQUIRED";
+
+/** @var kErrorMessageCaptchaRequiredInvalidPassword
+ @brief The error message in JSON responses from the server for CAPTCHA required with invalid
+ password.
+ */
+static NSString *const kErrorMessageCaptchaRequiredInvalidPassword =
+ @"CAPTCHA_REQUIRED_INVALID_PASSWORD";
+
+/** @var kErrorMessageCaptchaCheckFailed
+ @brief The error message in JSON responses from the server for CAPTCHA check failed.
+ */
+static NSString *const kErrorMessageCaptchaCheckFailed = @"CAPTCHA_CHECK_FAILED";
+
+/** @var kErrorMessageEmailExists
+ @brief The error message in JSON responses from the server for user's email already exists.
+ */
+static NSString *const kErrorMessageEmailExists = @"EMAIL_EXISTS";
+
+/** @var kErrorMessageKey
+ @brief The key for the error message in an error response.
+ */
+static NSString *const kErrorMessageKey = @"message";
+
+/** @var kTestKey
+ @brief A key to use for a successful response dictionary.
+ */
+static NSString *const kTestKey = @"TestKey";
+
+/** @var kUserDisabledErrorMessage
+ @brief This is the base error message the server will respond with if the user's account has
+ been disabled.
+ */
+static NSString *const kUserDisabledErrorMessage = @"USER_DISABLED";
+
+/** @var kFakeUserDisabledCustomErrorMessage
+ @brief This is a fake custom error message the server can respond with if the user's account has
+ been disabled.
+ */
+static NSString *const kFakeUserDisabledCustomErrorMessage = @"The user has been disabled.";
+
+/** @var kServerErrorDetailMarker
+ @brief This marker indicates that the server error message contains a detail error message which
+ should be used instead of the hardcoded client error message.
+ */
+static NSString *const kServerErrorDetailMarker = @" : ";
+
+/** @var kTestValue
+ @brief A value to use for a successful response dictionary.
+ */
+static NSString *const kTestValue = @"TestValue";
+
+/** @class FIRAuthBackendRPCImplementation
+ @brief Exposes an otherwise private class to these tests. See the real implementation for
+ documentation.
+ */
+@interface FIRAuthBackendRPCImplementation : NSObject <FIRAuthBackendImplementation>
+
+/** @fn postWithRequest:response:callback:
+ @brief Calls the RPC using HTTP POST.
+ @remarks Possible error responses:
+ @see FIRAuthInternalErrorCodeRPCRequestEncodingError
+ @see FIRAuthInternalErrorCodeJSONSerializationError
+ @see FIRAuthInternalErrorCodeNetworkError
+ @see FIRAuthInternalErrorCodeUnexpectedErrorResponse
+ @see FIRAuthInternalErrorCodeUnexpectedResponse
+ @see FIRAuthInternalErrorCodeRPCResponseDecodingError
+ @param request The request.
+ @param response The empty response to be filled.
+ @param callback The callback for both success and failure.
+ */
+- (void)postWithRequest:(id<FIRAuthRPCRequest>)request
+ response:(id<FIRAuthRPCResponse>)response
+ callback:(void (^)(NSError *error))callback;
+
+@end
+
+/** @extension FIRAuthBackend
+ @brief This class extension exposes the otherwise private @c implementation method. We use this
+ here to directly call the @c postWithRequest:response:callback: method of
+ @c FIRAuthBackendRPCImplementation in some of the tests.
+ */
+@interface FIRAuthBackend ()
+
+/** @fn implementation
+ @brief Exposes the otherwise private @c implementation method. We use this here to directly call
+ the @c postWithRequest:response:callback: method of @c FIRAuthBackendRPCImplementation in
+ some of the tests.
+ */
++ (FIRAuthBackendRPCImplementation *)implementation;
+
+@end
+
+/** @class FIRFakeRequest
+ @brief Allows us to fake a request with deterministic request bodies and encoding errors
+ returned from the @c FIRAuthRPCRequest-specified @c unencodedHTTPRequestBodyWithError:
+ method.
+ */
+@interface FIRFakeRequest : NSObject <FIRAuthRPCRequest>
+
+/** @fn fakeRequest
+ @brief A "normal" request which returns an encodable request object with no error.
+ */
++ (nullable instancetype)fakeRequest;
+
+/** @fn fakeRequestWithEncodingError
+ @brief A request which returns a fake error during the encoding process.
+ */
++ (nullable instancetype)fakeRequestWithEncodingError:(NSError *)error;
+
+/** @fn fakeRequestWithUnserializableRequestBody
+ @brief A request which returns a request object which can not be properly serialized by
+ @c NSJSONSerialization.
+ */
++ (nullable instancetype)fakeRequestWithUnserializableRequestBody;
+
+/** @fn fakeRequestWithNoBody
+ @brief A request which returns a nil request body but no error.
+ */
++ (nullable instancetype)fakeRequestWithNoBody;
+
+/** @fn init
+ @brief Please use initWithRequestBody:encodingError:
+ */
+- (nullable instancetype)init NS_UNAVAILABLE;
+
+/** @fn initWithRequestBody:encodingError:
+ @brief Designated initializer.
+ @param requestBody The fake request body to return when @c unencodedHTTPRequestBodyWithError: is
+ invoked.
+ @param encodingError The fake error to return when @c unencodedHTTPRequestBodyWithError is
+ invoked.
+ */
+- (nullable instancetype)initWithRequestBody:(nullable id)requestBody
+ encodingError:(nullable NSError *)encodingError
+ NS_DESIGNATED_INITIALIZER;
+
+@end
+
+@implementation FIRFakeRequest {
+ /** @var _requestBody
+ @brief The fake request body object we will return when @c unencodedHTTPRequestBodyWithError:
+ is invoked.
+ */
+ id _Nullable _requestBody;
+
+ /** @var _requestEncodingError
+ @brief The fake error object we will return when @c unencodedHTTPRequestBodyWithError:
+ is invoked.
+ */
+ NSError *_Nullable _requestEncodingError;
+}
+
++ (nullable instancetype)fakeRequest {
+ return [[self alloc] initWithRequestBody:@{ } encodingError:nil];
+}
+
++ (nullable instancetype)fakeRequestWithEncodingError:(NSError *)error {
+ return [[self alloc] initWithRequestBody:nil encodingError:error];
+}
+
++ (nullable instancetype)fakeRequestWithUnserializableRequestBody {
+ return [[self alloc] initWithRequestBody:@{ @"unencodableValue" : self } encodingError:nil];
+}
+
++ (nullable instancetype)fakeRequestWithNoBody {
+ return [[self alloc] initWithRequestBody:nil encodingError:nil];
+}
+
+- (nullable instancetype)initWithRequestBody:(nullable id)requestBody
+ encodingError:(nullable NSError *)encodingError {
+ self = [super init];
+ if (self) {
+ _requestBody = requestBody;
+ _requestEncodingError = encodingError;
+ }
+ return self;
+}
+
+- (NSURL *)requestURL {
+ return [NSURL URLWithString:kFakeRequestURL];
+}
+
+- (nullable id)unencodedHTTPRequestBodyWithError:(NSError *_Nullable *_Nullable)error {
+ if (error) {
+ *error = _requestEncodingError;
+ }
+ return _requestBody;
+}
+
+@end
+
+/** @class FIRFakeResponse
+ @brief Allows us to inspect the dictionaries received by @c FIRAuthRPCResponse classes, and
+ provide deterministic responses to the @c setWithDictionary:error:
+ methods.
+ */
+@interface FIRFakeResponse : NSObject <FIRAuthRPCResponse>
+
+/** @property receivedDictionary
+ @brief The dictionary passed to the @c setWithDictionary:error: method.
+ */
+@property(nonatomic, strong, readonly, nullable) NSDictionary *receivedDictionary;
+
+/** @fn fakeResponse
+ @brief A "normal" sucessful response (no error, no expected kind.)
+ */
++ (nullable instancetype)fakeResponse;
+
+/** @fn fakeResponseWithDecodingError
+ @brief A response which returns a fake error during the decoding process.
+ */
++ (nullable instancetype)fakeResponseWithDecodingError;
+
+/** @fn init
+ @brief Please use initWithDecodingError:
+ */
+- (nullable instancetype)init NS_UNAVAILABLE;
+
+- (nullable instancetype)initWithDecodingError:(nullable NSError *)decodingError
+ NS_DESIGNATED_INITIALIZER;
+
+@end
+
+@implementation FIRFakeResponse {
+ /** @var _responseDecodingError
+ @brief The value to return for an error when the @c setWithDictionary:error: method is
+ invoked.
+ */
+ NSError *_Nullable _responseDecodingError;
+}
+
++ (nullable instancetype)fakeResponse {
+ return [[self alloc] initWithDecodingError:nil];
+}
+
++ (nullable instancetype)fakeResponseWithDecodingError {
+ NSError *decodingError = [FIRAuthErrorUtils unexpectedErrorResponseWithDeserializedResponse:self];
+ return [[self alloc] initWithDecodingError:decodingError];
+}
+
+- (nullable instancetype)initWithDecodingError:(nullable NSError *)decodingError {
+ self = [super init];
+ if (self) {
+ _responseDecodingError = decodingError;
+ }
+ return self;
+}
+
+- (BOOL)setWithDictionary:(NSDictionary *)dictionary
+ error:(NSError *_Nullable *_Nullable)error {
+ if (_responseDecodingError) {
+ if (error) {
+ *error = _responseDecodingError;
+ }
+ return NO;
+ }
+ _receivedDictionary = dictionary;
+ return YES;
+}
+
+@end
+
+/** @class FIRAuthBackendRPCImplementationTests
+ @brief This set of unit tests is designed primarily to test the possible outcomes of the
+ @c FIRAuthBackendRPCImplementation.postWithRequest:response:callback: method.
+ */
+@interface FIRAuthBackendRPCImplementationTests : XCTestCase
+@end
+@implementation FIRAuthBackendRPCImplementationTests {
+ /** @var _RPCIssuer
+ @brief This backend RPC issuer is used to fake network responses for each test in the suite.
+ In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it.
+ */
+ FIRFakeBackendRPCIssuer *_RPCIssuer;
+
+ /** @var _RPCImplementation
+ @brief This backend RPC implementation is used to make fake network requests for each test in
+ the suite.
+ */
+ FIRAuthBackendRPCImplementation *_RPCImplementation;
+}
+
+- (void)setUp {
+ FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init];
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer];
+ _RPCIssuer = RPCIssuer;
+ _RPCImplementation = [FIRAuthBackend implementation];
+}
+
+- (void)tearDown {
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
+ _RPCIssuer = nil;
+ _RPCImplementation = nil;
+}
+
+/** @fn testRequestEncodingError
+ @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
+ request passed returns an error during it's unencodedHTTPRequestBodyWithError: method.
+ The error returned should be delivered to the caller without any change.
+ */
+- (void)testRequestEncodingError {
+ NSError *encodingError =
+ [NSError errorWithDomain:kFakeErrorDomain code:kFakeErrorCode userInfo:@{ }];
+ FIRFakeRequest *request = [FIRFakeRequest fakeRequestWithEncodingError:encodingError];
+ FIRFakeResponse *response = [FIRFakeResponse fakeResponse];
+
+ __block NSError *callbackError;
+ __block BOOL callbackInvoked;
+ [_RPCImplementation postWithRequest:request response:response callback:^(NSError *error) {
+ callbackInvoked = YES;
+ callbackError = error;
+ }];
+
+ // There is no need to call [_RPCIssuer respondWithError:...] in this test because a request
+ // should never have been tried - and we we know that's the case when we test @c callbackInvoked.
+
+ XCTAssert(callbackInvoked);
+
+ XCTAssertNotNil(callbackError);
+ XCTAssertEqualObjects(callbackError.domain, FIRAuthErrorDomain);
+ XCTAssertEqual(callbackError.code, FIRAuthErrorCodeInternalError);
+
+ NSError *underlyingError = callbackError.userInfo[NSUnderlyingErrorKey];
+ XCTAssertNotNil(underlyingError);
+ XCTAssertEqualObjects(underlyingError.domain, FIRAuthInternalErrorDomain);
+ XCTAssertEqual(underlyingError.code, FIRAuthInternalErrorCodeRPCRequestEncodingError);
+
+ NSError *underlyingUnderlyingError = underlyingError.userInfo[NSUnderlyingErrorKey];
+ XCTAssertNotNil(underlyingUnderlyingError);
+ XCTAssertEqualObjects(underlyingUnderlyingError.domain, kFakeErrorDomain);
+ XCTAssertEqual(underlyingUnderlyingError.code, kFakeErrorCode);
+
+ id deserializedResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDeserializedResponseKey];
+ XCTAssertNil(deserializedResponse);
+
+ id dataResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDataKey];
+ XCTAssertNil(dataResponse);
+}
+
+/** @fn testBodyDataSerializationError
+ @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
+ request returns an object which isn't serializable by @c NSJSONSerialization.
+ The error from @c NSJSONSerialization should be returned as the underlyingError for an
+ @c NSError with the code @c FIRAuthErrorCodeJSONSerializationError.
+ */
+- (void)testBodyDataSerializationError {
+ FIRFakeRequest *request = [FIRFakeRequest fakeRequestWithUnserializableRequestBody];
+ FIRFakeResponse *response = [FIRFakeResponse fakeResponse];
+
+ __block NSError *callbackError;
+ __block BOOL callbackInvoked;
+ [_RPCImplementation postWithRequest:request response:response callback:^(NSError *error) {
+ callbackInvoked = YES;
+ callbackError = error;
+ }];
+
+ // There is no need to call [_RPCIssuer respondWithError:...] in this test because a request
+ // should never have been tried - and we we know that's the case when we test @c callbackInvoked.
+
+ XCTAssert(callbackInvoked);
+
+ XCTAssertNotNil(callbackError);
+ XCTAssertEqualObjects(callbackError.domain, FIRAuthErrorDomain);
+ XCTAssertEqual(callbackError.code, FIRAuthErrorCodeInternalError);
+
+ NSError *underlyingError = callbackError.userInfo[NSUnderlyingErrorKey];
+ XCTAssertNotNil(underlyingError);
+ XCTAssertEqualObjects(underlyingError.domain, FIRAuthInternalErrorDomain);
+ XCTAssertEqual(underlyingError.code, FIRAuthInternalErrorCodeJSONSerializationError);
+
+ NSError *underlyingUnderlyingError = underlyingError.userInfo[NSUnderlyingErrorKey];
+ XCTAssertNil(underlyingUnderlyingError);
+
+ id deserializedResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDeserializedResponseKey];
+ XCTAssertNil(deserializedResponse);
+
+ id dataResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDataKey];
+ XCTAssertNil(dataResponse);
+}
+
+/** @fn testNetworkError
+ @brief This test checks to make sure a network error is properly wrapped and forwarded with the
+ correct code (FIRAuthErrorCodeNetworkError).
+ */
+- (void)testNetworkError {
+ FIRFakeRequest *request = [FIRFakeRequest fakeRequest];
+ FIRFakeResponse *response = [FIRFakeResponse fakeResponse];
+
+ __block NSError *callbackError;
+ __block BOOL callbackInvoked;
+ [_RPCImplementation postWithRequest:request response:response callback:^(NSError *error) {
+ callbackInvoked = YES;
+ callbackError = error;
+ }];
+
+ // It shouldn't matter what the error domain/code/userInfo are, any junk values are suitable. The
+ // implementation should treat any error with no response data as a network error.
+ NSError *responseError = [NSError errorWithDomain:kFakeErrorDomain
+ code:kFakeErrorCode
+ userInfo:nil];
+ [_RPCIssuer respondWithError:responseError];
+
+ XCTAssert(callbackInvoked);
+
+ XCTAssertNotNil(callbackError);
+ XCTAssertEqualObjects(callbackError.domain, FIRAuthErrorDomain);
+ XCTAssertEqual(callbackError.code, FIRAuthErrorCodeNetworkError);
+
+ NSError *underlyingError = callbackError.userInfo[NSUnderlyingErrorKey];
+ XCTAssertNotNil(underlyingError);
+ XCTAssertEqualObjects(underlyingError.domain, kFakeErrorDomain);
+ XCTAssertEqual(underlyingError.code, kFakeErrorCode);
+
+ NSError *underlyingUnderlyingError = underlyingError.userInfo[NSUnderlyingErrorKey];
+ XCTAssertNil(underlyingUnderlyingError);
+
+ id deserializedResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDeserializedResponseKey];
+ XCTAssertNil(deserializedResponse);
+
+ id dataResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDataKey];
+ XCTAssertNil(dataResponse);
+}
+
+/** @fn testUnparsableErrorResponse
+ @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
+ response isn't deserializable by @c NSJSONSerialization and an error
+ condition (with an associated error response message) was expected. We are expecting to
+ receive the original network error wrapped in an @c NSError with the code
+ @c FIRAuthErrorCodeUnexpectedHTTPResponse.
+ */
+- (void)testUnparsableErrorResponse {
+ FIRFakeRequest *request = [FIRFakeRequest fakeRequest];
+ FIRFakeResponse *response = [FIRFakeResponse fakeResponse];
+
+ __block NSError *callbackError;
+ __block BOOL callbackInvoked;
+ [_RPCImplementation postWithRequest:request response:response callback:^(NSError *error) {
+ callbackInvoked = YES;
+ callbackError = error;
+ }];
+
+ NSData *data =
+ [@"<html><body>An error occurred.</body></html>" dataUsingEncoding:NSUTF8StringEncoding];
+ NSError *error =
+ [NSError errorWithDomain:kFakeErrorDomain code:kFakeErrorCode userInfo:@{ }];
+ [_RPCIssuer respondWithData:data error:error];
+
+ XCTAssert(callbackInvoked);
+
+ XCTAssertNotNil(callbackError);
+ XCTAssertEqualObjects(callbackError.domain, FIRAuthErrorDomain);
+ XCTAssertEqual(callbackError.code, FIRAuthErrorCodeInternalError);
+
+ NSError *underlyingError = callbackError.userInfo[NSUnderlyingErrorKey];
+ XCTAssertNotNil(underlyingError);
+ XCTAssertEqualObjects(underlyingError.domain, FIRAuthInternalErrorDomain);
+ XCTAssertEqual(underlyingError.code, FIRAuthInternalErrorCodeUnexpectedErrorResponse);
+
+ NSError *underlyingUnderlyingError = underlyingError.userInfo[NSUnderlyingErrorKey];
+ XCTAssertNotNil(underlyingUnderlyingError);
+ XCTAssertEqualObjects(underlyingUnderlyingError.domain, kFakeErrorDomain);
+ XCTAssertEqual(underlyingUnderlyingError.code, kFakeErrorCode);
+
+ id deserializedResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDeserializedResponseKey];
+ XCTAssertNil(deserializedResponse);
+
+ id dataResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDataKey];
+ XCTAssertNotNil(dataResponse);
+ XCTAssertEqualObjects(dataResponse, data);
+}
+
+/** @fn testUnparsableSuccessResponse
+ @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
+ response isn't deserializable by @c NSJSONSerialization and no error
+ condition was indicated. We are expecting to
+ receive the @c NSJSONSerialization error wrapped in an @c NSError with the code
+ @c FIRAuthErrorCodeUnexpectedServerResponse.
+ */
+- (void)testUnparsableSuccessResponse {
+ FIRFakeRequest *request = [FIRFakeRequest fakeRequest];
+ FIRFakeResponse *response = [FIRFakeResponse fakeResponse];
+
+ __block NSError *callbackError;
+ __block BOOL callbackInvoked;
+ [_RPCImplementation postWithRequest:request response:response callback:^(NSError *error) {
+ callbackInvoked = YES;
+ callbackError = error;
+ }];
+
+ NSData *data =
+ [@"<xml>Some non-JSON value.</xml>" dataUsingEncoding:NSUTF8StringEncoding];
+ [_RPCIssuer respondWithData:data error:nil];
+
+ XCTAssert(callbackInvoked);
+
+ XCTAssertNotNil(callbackError);
+ XCTAssertEqualObjects(callbackError.domain, FIRAuthErrorDomain);
+ XCTAssertEqual(callbackError.code, FIRAuthErrorCodeInternalError);
+
+ NSError *underlyingError = callbackError.userInfo[NSUnderlyingErrorKey];
+ XCTAssertNotNil(underlyingError);
+ XCTAssertEqualObjects(underlyingError.domain, FIRAuthInternalErrorDomain);
+ XCTAssertEqual(underlyingError.code, FIRAuthInternalErrorCodeUnexpectedResponse);
+
+ NSError *underlyingUnderlyingError = underlyingError.userInfo[NSUnderlyingErrorKey];
+ XCTAssertNotNil(underlyingUnderlyingError);
+ XCTAssertEqualObjects(underlyingUnderlyingError.domain, NSCocoaErrorDomain);
+
+ id deserializedResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDeserializedResponseKey];
+ XCTAssertNil(deserializedResponse);
+
+ id dataResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDataKey];
+ XCTAssertNotNil(dataResponse);
+ XCTAssertEqualObjects(dataResponse, data);
+}
+
+/** @fn testNonDictionaryErrorResponse
+ @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
+ response deserialized by @c NSJSONSerialization is not a dictionary, and an error was
+ expected. We are expecting to receive an @c NSError with the code
+ @c FIRAuthErrorCodeUnexpectedErrorServerResponse with the decoded response in the
+ @c NSError.userInfo dictionary associated with the key
+ @c FIRAuthErrorUserInfoDecodedResponseKey.
+ */
+- (void)testNonDictionaryErrorResponse {
+ FIRFakeRequest *request = [FIRFakeRequest fakeRequest];
+ FIRFakeResponse *response = [FIRFakeResponse fakeResponse];
+
+ __block NSError *callbackError;
+ __block BOOL callbackInvoked;
+ [_RPCImplementation postWithRequest:request response:response callback:^(NSError *error) {
+ callbackInvoked = YES;
+ callbackError = error;
+ }];
+
+ // We are responding with a JSON-encoded string value representing an array - which is unexpected.
+ // It should normally be a dictionary, and we need to check for this sort of thing. Because we can
+ // successfully decode this value, however, we do return it in the error results. We check for
+ // this array later in the test.
+ NSData *data = [@"[]" dataUsingEncoding:NSUTF8StringEncoding];
+ NSError *error =
+ [NSError errorWithDomain:kFakeErrorDomain code:kFakeErrorCode userInfo:@{ }];
+ [_RPCIssuer respondWithData:data error:error];
+
+ XCTAssert(callbackInvoked);
+
+ XCTAssertNotNil(callbackError);
+ XCTAssertEqualObjects(callbackError.domain, FIRAuthErrorDomain);
+ XCTAssertEqual(callbackError.code, FIRAuthErrorCodeInternalError);
+
+ NSError *underlyingError = callbackError.userInfo[NSUnderlyingErrorKey];
+ XCTAssertNotNil(underlyingError);
+ XCTAssertEqualObjects(underlyingError.domain, FIRAuthInternalErrorDomain);
+ XCTAssertEqual(underlyingError.code, FIRAuthInternalErrorCodeUnexpectedErrorResponse);
+
+ NSError *underlyingUnderlyingError = underlyingError.userInfo[NSUnderlyingErrorKey];
+ XCTAssertNil(underlyingUnderlyingError);
+
+ id deserializedResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDeserializedResponseKey];
+ XCTAssertNotNil(deserializedResponse);
+ XCTAssert([deserializedResponse isKindOfClass:[NSArray class]]);
+
+ id dataResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDataKey];
+ XCTAssertNil(dataResponse);
+}
+
+/** @fn testNonDictionarySuccessResponse
+ @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
+ response deserialized by @c NSJSONSerialization is not a dictionary, and no error was
+ expected. We are expecting to receive an @c NSError with the code
+ @c FIRAuthErrorCodeUnexpectedServerResponse with the decoded response in the
+ @c NSError.userInfo dictionary associated with the key
+ @c FIRAuthErrorUserInfoDecodedResponseKey.
+ */
+- (void)testNonDictionarySuccessResponse {
+ FIRFakeRequest *request = [FIRFakeRequest fakeRequest];
+ FIRFakeResponse *response = [FIRFakeResponse fakeResponse];
+
+ __block NSError *callbackError;
+ __block BOOL callbackInvoked;
+ [_RPCImplementation postWithRequest:request response:response callback:^(NSError *error) {
+ callbackInvoked = YES;
+ callbackError = error;
+ }];
+
+ // We are responding with a JSON-encoded string value representing an array - which is unexpected.
+ // It should normally be a dictionary, and we need to check for this sort of thing. Because we can
+ // successfully decode this value, however, we do return it in the error results. We check for
+ // this array later in the test.
+ NSData *data = [@"[]" dataUsingEncoding:NSUTF8StringEncoding];
+ [_RPCIssuer respondWithData:data error:nil];
+
+ XCTAssert(callbackInvoked);
+
+ XCTAssertNotNil(callbackError);
+ XCTAssertEqualObjects(callbackError.domain, FIRAuthErrorDomain);
+ XCTAssertEqual(callbackError.code, FIRAuthErrorCodeInternalError);
+
+ NSError *underlyingError = callbackError.userInfo[NSUnderlyingErrorKey];
+ XCTAssertNotNil(underlyingError);
+ XCTAssertEqualObjects(underlyingError.domain, FIRAuthInternalErrorDomain);
+ XCTAssertEqual(underlyingError.code, FIRAuthInternalErrorCodeUnexpectedResponse);
+
+ NSError *underlyingUnderlyingError = underlyingError.userInfo[NSUnderlyingErrorKey];
+ XCTAssertNil(underlyingUnderlyingError);
+
+ id deserializedResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDeserializedResponseKey];
+ XCTAssertNotNil(deserializedResponse);
+ XCTAssert([deserializedResponse isKindOfClass:[NSArray class]]);
+
+ id dataResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDataKey];
+ XCTAssertNil(dataResponse);
+}
+
+/** @fn testCaptchaRequiredResponse
+ @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
+ we get an error message indicating captcha is required. The backend should not be returning
+ this error to mobile clients. If it does, we should wrap it in an @c NSError with the code
+ @c FIRAuthErrorCodeUnexpectedServerResponse with the decoded error message in the
+ @c NSError.userInfo dictionary associated with the key
+ @c FIRAuthErrorUserInfoDecodedErrorResponseKey.
+ */
+- (void)testCaptchaRequiredResponse {
+ FIRFakeRequest *request = [FIRFakeRequest fakeRequest];
+ FIRFakeResponse *response = [FIRFakeResponse fakeResponse];
+
+ __block NSError *callbackError;
+ __block BOOL callbackInvoked;
+ [_RPCImplementation postWithRequest:request response:response callback:^(NSError *error) {
+ callbackInvoked = YES;
+ callbackError = error;
+ }];
+
+ NSError *error =
+ [NSError errorWithDomain:kFakeErrorDomain code:kFakeErrorCode userInfo:@{ }];
+ [_RPCIssuer respondWithServerErrorMessage:kErrorMessageCaptchaRequired error:error];
+
+ XCTAssert(callbackInvoked);
+
+ XCTAssertNotNil(callbackError);
+ XCTAssertEqualObjects(callbackError.domain, FIRAuthErrorDomain);
+ XCTAssertEqual(callbackError.code, FIRAuthErrorCodeInternalError);
+
+ NSError *underlyingError = callbackError.userInfo[NSUnderlyingErrorKey];
+ XCTAssertNotNil(underlyingError);
+ XCTAssertEqualObjects(underlyingError.domain, FIRAuthInternalErrorDomain);
+ XCTAssertEqual(underlyingError.code, FIRAuthInternalErrorCodeUnexpectedErrorResponse);
+
+ NSError *underlyingUnderlyingError = underlyingError.userInfo[NSUnderlyingErrorKey];
+ XCTAssertNil(underlyingUnderlyingError);
+
+ id deserializedResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDeserializedResponseKey];
+ XCTAssertNotNil(deserializedResponse);
+ XCTAssert([deserializedResponse isKindOfClass:[NSDictionary class]]);
+ XCTAssertNotNil(deserializedResponse[@"message"]);
+
+ id dataResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDataKey];
+ XCTAssertNil(dataResponse);
+}
+
+/** @fn testCaptchaCheckFailedResponse
+ @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
+ we get an error message indicating captcha check failed. The backend should not be returning
+ this error to mobile clients. If it does, we should wrap it in an @c NSError with the code
+ @c FIRAuthErrorCodeUnexpectedServerResponse with the decoded error message in the
+ @c NSError.userInfo dictionary associated with the key
+ @c FIRAuthErrorUserInfoDecodedErrorResponseKey.
+ */
+- (void)testCaptchaCheckFailedResponse {
+ FIRFakeRequest *request = [FIRFakeRequest fakeRequest];
+ FIRFakeResponse *response = [FIRFakeResponse fakeResponse];
+
+ __block NSError *callbackError;
+ __block BOOL callbackInvoked;
+ [_RPCImplementation postWithRequest:request response:response callback:^(NSError *error) {
+ callbackInvoked = YES;
+ callbackError = error;
+ }];
+
+ NSError *error =
+ [NSError errorWithDomain:kFakeErrorDomain code:kFakeErrorCode userInfo:@{ }];
+ [_RPCIssuer respondWithServerErrorMessage:kErrorMessageCaptchaCheckFailed error:error];
+
+ XCTAssert(callbackInvoked);
+
+ XCTAssertNotNil(callbackError);
+ XCTAssertEqualObjects(callbackError.domain, FIRAuthErrorDomain);
+ XCTAssertEqual(callbackError.code, FIRAuthErrorCodeInternalError);
+
+ NSError *underlyingError = callbackError.userInfo[NSUnderlyingErrorKey];
+ XCTAssertNotNil(underlyingError);
+ XCTAssertEqualObjects(underlyingError.domain, FIRAuthInternalErrorDomain);
+ XCTAssertEqual(underlyingError.code, FIRAuthInternalErrorCodeUnexpectedErrorResponse);
+
+ NSError *underlyingUnderlyingError = underlyingError.userInfo[NSUnderlyingErrorKey];
+ XCTAssertNil(underlyingUnderlyingError);
+
+ id deserializedResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDeserializedResponseKey];
+ XCTAssertNotNil(deserializedResponse);
+ XCTAssert([deserializedResponse isKindOfClass:[NSDictionary class]]);
+ XCTAssertNotNil(deserializedResponse[@"message"]);
+
+ id dataResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDataKey];
+ XCTAssertNil(dataResponse);
+}
+
+/** @fn testCaptchaRequiredInvalidPasswordResponse
+ @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
+ we get an error message indicating captcha is required and an invalid password was entered.
+ The backend should not be returning this error to mobile clients. If it does, we should wrap
+ it in an @c NSError with the code
+ @c FIRAuthErrorCodeUnexpectedServerResponse with the decoded error message in the
+ @c NSError.userInfo dictionary associated with the key
+ @c FIRAuthErrorUserInfoDecodedErrorResponseKey.
+ */
+- (void)testCaptchaRequiredInvalidPasswordResponse {
+ FIRFakeRequest *request = [FIRFakeRequest fakeRequest];
+ FIRFakeResponse *response = [FIRFakeResponse fakeResponse];
+
+ __block NSError *callbackError;
+ __block BOOL callbackInvoked;
+ [_RPCImplementation postWithRequest:request response:response callback:^(NSError *error) {
+ callbackInvoked = YES;
+ callbackError = error;
+ }];
+
+ NSError *error =
+ [NSError errorWithDomain:kFakeErrorDomain code:kFakeErrorCode userInfo:@{ }];
+ [_RPCIssuer respondWithServerErrorMessage:kErrorMessageCaptchaRequiredInvalidPassword
+ error:error];
+
+ XCTAssert(callbackInvoked);
+
+ XCTAssertNotNil(callbackError);
+ XCTAssertEqualObjects(callbackError.domain, FIRAuthErrorDomain);
+ XCTAssertEqual(callbackError.code, FIRAuthErrorCodeInternalError);
+
+ NSError *underlyingError = callbackError.userInfo[NSUnderlyingErrorKey];
+ XCTAssertNotNil(underlyingError);
+ XCTAssertEqualObjects(underlyingError.domain, FIRAuthInternalErrorDomain);
+ XCTAssertEqual(underlyingError.code, FIRAuthInternalErrorCodeUnexpectedErrorResponse);
+
+ NSError *underlyingUnderlyingError = underlyingError.userInfo[NSUnderlyingErrorKey];
+ XCTAssertNil(underlyingUnderlyingError);
+
+ id deserializedResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDeserializedResponseKey];
+ XCTAssertNotNil(deserializedResponse);
+ XCTAssert([deserializedResponse isKindOfClass:[NSDictionary class]]);
+ XCTAssertNotNil(deserializedResponse[@"message"]);
+
+ id dataResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDataKey];
+ XCTAssertNil(dataResponse);
+}
+
+/** @fn testDecodableErrorResponseWithUnknownMessage
+ @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
+ response deserialized by @c NSJSONSerialization represents a valid error response (and an
+ error was indicated) but we didn't receive an error message we know about. We are expecting
+ an @c NSError with the code @c FIRAuthErrorCodeUnexpectedServerResponse with the decoded
+ error message in the @c NSError.userInfo dictionary associated with the key
+ @c FIRAuthErrorUserInfoDecodedErrorResponseKey.
+ */
+- (void)testDecodableErrorResponseWithUnknownMessage {
+ FIRFakeRequest *request = [FIRFakeRequest fakeRequest];
+ FIRFakeResponse *response = [FIRFakeResponse fakeResponse];
+
+ __block NSError *callbackError;
+ __block BOOL callbackInvoked;
+ [_RPCImplementation postWithRequest:request response:response callback:^(NSError *error) {
+ callbackInvoked = YES;
+ callbackError = error;
+ }];
+
+ // We need to return a valid "error" response here, but we are going to intentionally use a bogus
+ // error message.
+ NSError *error =
+ [NSError errorWithDomain:kFakeErrorDomain code:kFakeErrorCode userInfo:@{ }];
+ [_RPCIssuer respondWithServerErrorMessage:kUnknownServerErrorMessage error:error];
+
+ XCTAssert(callbackInvoked);
+
+ XCTAssertNotNil(callbackError);
+ XCTAssertEqualObjects(callbackError.domain, FIRAuthErrorDomain);
+ XCTAssertEqual(callbackError.code, FIRAuthErrorCodeInternalError);
+
+ NSError *underlyingError = callbackError.userInfo[NSUnderlyingErrorKey];
+ XCTAssertNotNil(underlyingError);
+ XCTAssertEqualObjects(underlyingError.domain, FIRAuthInternalErrorDomain);
+ XCTAssertEqual(underlyingError.code, FIRAuthInternalErrorCodeUnexpectedErrorResponse);
+
+ NSError *underlyingUnderlyingError = underlyingError.userInfo[NSUnderlyingErrorKey];
+ XCTAssertNil(underlyingUnderlyingError);
+
+ id deserializedResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDeserializedResponseKey];
+ XCTAssertNotNil(deserializedResponse);
+ XCTAssert([deserializedResponse isKindOfClass:[NSDictionary class]]);
+ XCTAssertNotNil(deserializedResponse[@"message"]);
+
+ id dataResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDataKey];
+ XCTAssertNil(dataResponse);
+}
+
+/** @fn testErrorResponseWithNoErrorMessage
+ @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
+ response deserialized by @c NSJSONSerialization is a dictionary, and an error was indicated,
+ but no error information was present in the decoded response. We are expecting an @c NSError
+ with the code @c FIRAuthErrorCodeUnexpectedServerResponse with the decoded
+ response message in the @c NSError.userInfo dictionary associated with the key
+ @c FIRAuthErrorUserInfoDecodedResponseKey.
+ */
+- (void)testErrorResponseWithNoErrorMessage {
+ FIRFakeRequest *request = [FIRFakeRequest fakeRequest];
+ FIRFakeResponse *response = [FIRFakeResponse fakeResponse];
+
+ __block NSError *callbackError;
+ __block BOOL callbackInvoked;
+ [_RPCImplementation postWithRequest:request response:response callback:^(NSError *error) {
+ callbackInvoked = YES;
+ callbackError = error;
+ }];
+
+ NSError *error =
+ [NSError errorWithDomain:kFakeErrorDomain code:kFakeErrorCode userInfo:@{ }];
+ [_RPCIssuer respondWithJSON:@{ } error:error];
+
+ XCTAssert(callbackInvoked);
+
+ XCTAssertNotNil(callbackError);
+ XCTAssertEqualObjects(callbackError.domain, FIRAuthErrorDomain);
+ XCTAssertEqual(callbackError.code, FIRAuthErrorCodeInternalError);
+
+ NSError *underlyingError = callbackError.userInfo[NSUnderlyingErrorKey];
+ XCTAssertNotNil(underlyingError);
+ XCTAssertEqualObjects(underlyingError.domain, FIRAuthInternalErrorDomain);
+ XCTAssertEqual(underlyingError.code, FIRAuthInternalErrorCodeUnexpectedErrorResponse);
+
+ NSError *underlyingUnderlyingError = underlyingError.userInfo[NSUnderlyingErrorKey];
+ XCTAssertNil(underlyingUnderlyingError);
+
+ id deserializedResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDeserializedResponseKey];
+ XCTAssertNotNil(deserializedResponse);
+ XCTAssert([deserializedResponse isKindOfClass:[NSDictionary class]]);
+
+ id dataResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDataKey];
+ XCTAssertNil(dataResponse);
+}
+
+/** @fn testClientErrorResponse
+ @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
+ response contains a client error specified by an error messsage sent from the backend.
+ */
+- (void)testClientErrorResponse {
+ FIRFakeRequest *request = [FIRFakeRequest fakeRequest];
+ FIRFakeResponse *response = [FIRFakeResponse fakeResponse];
+
+ __block NSError *callbackerror;
+ __block BOOL callBackInvoked;
+ [_RPCImplementation postWithRequest: request response:response callback:^(NSError *error) {
+ callBackInvoked = YES;
+ callbackerror = error;
+ }];
+ NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:nil];
+ NSString *customErrorMessage =[NSString stringWithFormat:@"%@%@%@",
+ kUserDisabledErrorMessage,
+ kServerErrorDetailMarker,
+ kFakeUserDisabledCustomErrorMessage];
+ [_RPCIssuer respondWithServerErrorMessage:customErrorMessage error:error];
+ XCTAssertNotNil(callbackerror, @"An error should be returned from callback.");
+ XCTAssert(callBackInvoked);
+ XCTAssertEqual(callbackerror.code, FIRAuthErrorCodeUserDisabled);
+ NSString *customMessage = callbackerror.userInfo[NSLocalizedDescriptionKey];
+ XCTAssertEqualObjects(customMessage, kFakeUserDisabledCustomErrorMessage);
+}
+
+/** @fn testUndecodableSuccessResponse
+ @brief This test checks the behaviour of @c postWithRequest:response:callback: when the
+ response isn't decodable by the response class but no error condition was expected. We are
+ expecting to receive an @c NSError with the code
+ @c FIRAuthErrorCodeUnexpectedServerResponse and the error from @c setWithDictionary:error:
+ as the value of the underlyingError.
+ */
+- (void)testUndecodableSuccessResponse {
+ FIRFakeRequest *request = [FIRFakeRequest fakeRequest];
+ FIRFakeResponse *response = [FIRFakeResponse fakeResponseWithDecodingError];
+
+ __block NSError *callbackError;
+ __block BOOL callbackInvoked;
+ [_RPCImplementation postWithRequest:request response:response callback:^(NSError *error) {
+ callbackInvoked = YES;
+ callbackError = error;
+ }];
+
+ // It doesn't matter what we respond with here, as long as it's not an error response. The fake
+ // response will deterministicly simulate a decoding error regardless of the response value it was
+ // given.
+ [_RPCIssuer respondWithJSON:@{ }];
+
+ XCTAssert(callbackInvoked);
+
+ XCTAssertNotNil(callbackError);
+ XCTAssertEqualObjects(callbackError.domain, FIRAuthErrorDomain);
+ XCTAssertEqual(callbackError.code, FIRAuthErrorCodeInternalError);
+
+ NSError *underlyingError = callbackError.userInfo[NSUnderlyingErrorKey];
+ XCTAssertNotNil(underlyingError);
+ XCTAssertEqualObjects(underlyingError.domain, FIRAuthInternalErrorDomain);
+ XCTAssertEqual(underlyingError.code, FIRAuthInternalErrorCodeRPCResponseDecodingError);
+
+ id deserializedResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDeserializedResponseKey];
+ XCTAssertNotNil(deserializedResponse);
+ XCTAssert([deserializedResponse isKindOfClass:[NSDictionary class]]);
+
+ id dataResponse = underlyingError.userInfo[FIRAuthErrorUserInfoDataKey];
+ XCTAssertNil(dataResponse);
+}
+
+/** @fn testSuccessfulResponse
+ @brief Tests that a decoded dictionary is handed to the response instance.
+ */
+- (void)testSuccessfulResponse {
+ FIRFakeRequest *request = [FIRFakeRequest fakeRequest];
+ FIRFakeResponse *response = [FIRFakeResponse fakeResponse];
+
+ __block NSError *callbackError;
+ __block BOOL callbackInvoked;
+ [_RPCImplementation postWithRequest:request response:response callback:^(NSError *error) {
+ callbackInvoked = YES;
+ callbackError = error;
+ }];
+
+ [_RPCIssuer respondWithJSON:@{ kTestKey : kTestValue }];
+
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(callbackError);
+ XCTAssertNotNil(response.receivedDictionary);
+ XCTAssertEqualObjects(response.receivedDictionary[kTestKey], kTestValue);
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRAuthDispatcherTests.m b/Example/Auth/Tests/FIRAuthDispatcherTests.m
new file mode 100644
index 0000000..9b0abc4
--- /dev/null
+++ b/Example/Auth/Tests/FIRAuthDispatcherTests.m
@@ -0,0 +1,105 @@
+/*
+ * 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 "FIRAuthDispatcher.h"
+
+/** @var kMaxDifferenceBetweenTimeIntervals
+ @brief The maximum difference between time intervals (in seconds), after which they will be
+ considered different.
+ */
+static const NSTimeInterval kMaxDifferenceBetweenTimeIntervals = 0.1;
+
+/** @var kTestDelay
+ @brief Fake time delay before tasks are dispatched.
+ */
+NSTimeInterval kTestDelay = 0.1;
+
+/** @var kExpectationTimeout
+ @brief The maximum time waiting for expectations to fulfill.
+ */
+static const NSTimeInterval kExpectationTimeout = 1;
+
+id<OS_dispatch_queue> testWorkQueue;
+
+/** @class FIRAuthDispatcherTests
+ @brief Tests for @c FIRAuthDispatcher.
+ */
+@interface FIRAuthDispatcherTests : XCTestCase
+@end
+@implementation FIRAuthDispatcherTests
+
+- (void)setUp {
+ [super setUp];
+ testWorkQueue = dispatch_queue_create("test.work.queue", NULL);
+}
+
+/** @fn testSharedInstance
+ @brief Tests @c sharedInstance returns the same object.
+ */
+- (void)testSharedInstance {
+ FIRAuthDispatcher *instance1 = [FIRAuthDispatcher sharedInstance];
+ FIRAuthDispatcher *instance2 = [FIRAuthDispatcher sharedInstance];
+ XCTAssertEqual(instance1, instance2);
+}
+
+/** @fn testDispatchAfterDelay
+ @brief Tests @c dispatchAfterDelay indeed dispatches the specified task after the provided
+ delay.
+ */
+- (void)testDispatchAfterDelay {
+ FIRAuthDispatcher *dispatcher = [FIRAuthDispatcher sharedInstance];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"dispatchAfterCallback"];
+ NSDate *dateBeforeDispatch = [NSDate date];
+ dispatcher.dispatchAfterImplementation = nil;
+ [dispatcher dispatchAfterDelay:kTestDelay
+ queue:testWorkQueue
+ task:^{
+ NSTimeInterval timeSinceDispatch = fabs([dateBeforeDispatch timeIntervalSinceNow]) - kTestDelay;
+ XCTAssert(timeSinceDispatch < kMaxDifferenceBetweenTimeIntervals);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ dispatcher = nil;
+}
+
+/** @fn testSetDispatchAfterImplementation
+ @brief Tests taht @c dispatchAfterImplementation indeed configures a custom implementation for
+ @c dispatchAfterDelay.
+ */
+- (void)testSetDispatchAfterImplementation {
+ FIRAuthDispatcher *dispatcher = [FIRAuthDispatcher sharedInstance];
+ XCTestExpectation *expectation1 = [self expectationWithDescription:@"setDispatchTokenCallback"];
+ [dispatcher setDispatchAfterImplementation:^(NSTimeInterval delay,
+ id<OS_dispatch_queue> _Nonnull queue,
+ void (^task)(void)) {
+ XCTAssertEqual(kTestDelay, delay);
+ XCTAssertEqual(testWorkQueue, queue);
+ [expectation1 fulfill];
+ }];
+ [dispatcher dispatchAfterDelay:kTestDelay
+ queue:testWorkQueue
+ task:^{
+ // Fail to ensure this code is never executed.
+ XCTFail();
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ dispatcher.dispatchAfterImplementation = nil;;
+}
+
+
+@end
diff --git a/Example/Auth/Tests/FIRAuthGlobalWorkQueueTests.m b/Example/Auth/Tests/FIRAuthGlobalWorkQueueTests.m
new file mode 100644
index 0000000..a492c3d
--- /dev/null
+++ b/Example/Auth/Tests/FIRAuthGlobalWorkQueueTests.m
@@ -0,0 +1,35 @@
+/*
+ * 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 "FIRAuthGlobalWorkQueue.h"
+
+/** @class FIRAuthGlobalWorkQueueTests
+ @brief Tests for @c FIRAuthGlobalWorkQueue .
+ */
+@interface FIRAuthGlobalWorkQueueTests : XCTestCase
+@end
+@implementation FIRAuthGlobalWorkQueueTests
+
+- (void)testSingleton {
+ dispatch_queue_t queue1 = FIRAuthGlobalWorkQueue();
+ XCTAssertNotNil(queue1);
+ dispatch_queue_t queue2 = FIRAuthGlobalWorkQueue();
+ XCTAssertEqual(queue1, queue2);
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRAuthKeychainTests.m b/Example/Auth/Tests/FIRAuthKeychainTests.m
new file mode 100644
index 0000000..374e868
--- /dev/null
+++ b/Example/Auth/Tests/FIRAuthKeychainTests.m
@@ -0,0 +1,314 @@
+/*
+ * 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 <Security/Security.h>
+#import <XCTest/XCTest.h>
+
+#import "FIRAuthKeychain.h"
+
+/** @var kAccountPrefix
+ @brief The keychain account prefix assumed by the tests.
+ */
+static NSString *const kAccountPrefix = @"firebase_auth_1_";
+
+/** @var kKey
+ @brief The key used in tests.
+ */
+static NSString *const kKey = @"ACCOUNT";
+
+/** @var kService
+ @brief The keychain service used in tests.
+ */
+static NSString *const kService = @"SERVICE";
+
+/** @var kOtherService
+ @brief Another keychain service used in tests.
+ */
+static NSString *const kOtherService = @"OTHER_SERVICE";
+
+/** @var kData
+ @brief A piece of keychain data used in tests.
+ */
+static NSString *const kData = @"DATA";
+
+/** @var kOtherData
+ @brief Another piece of keychain data used in tests.
+ */
+static NSString *const kOtherData = @"OTHER_DATA";
+
+/** @fn accountFromKey
+ @brief Converts a key string to an account string.
+ @param key The key string to be converted from.
+ @return The account string being the conversion result.
+ */
+static NSString *accountFromKey(NSString *key) {
+ return [kAccountPrefix stringByAppendingString:key];
+}
+
+/** @fn dataFromString
+ @brief Converts a NSString to NSData.
+ @param string The NSString to be converted from.
+ @return The NSData being the conversion result.
+ */
+static NSData *dataFromString(NSString *string) {
+ return [string dataUsingEncoding:NSUTF8StringEncoding];
+}
+
+/** @fn stringFromData
+ @brief Converts a NSData to NSString.
+ @param data The NSData to be converted from.
+ @return The NSString being the conversion result.
+ */
+static NSString *stringFromData(NSData *data) {
+ return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+}
+
+/** @fn fakeError
+ @brief Creates a fake error object.
+ @return a non-nil NSError instance.
+ */
+static NSError *fakeError() {
+ return [NSError errorWithDomain:@"ERROR" code:-1 userInfo:nil];
+}
+
+/** @class FIRAuthKeychainTests
+ @brief Tests for @c FIRAuthKeychainTests .
+ */
+@interface FIRAuthKeychainTests : XCTestCase
+@end
+
+@implementation FIRAuthKeychainTests
+
+/** @fn testReadNonexisting
+ @brief Tests reading non-existing keychain item.
+ */
+- (void)testReadNonexisting {
+ [self setPassword:nil account:accountFromKey(kKey) service:kService];
+ [self setPassword:nil account:kKey service:nil]; // legacy form
+ FIRAuthKeychain *keychain = [[FIRAuthKeychain alloc] initWithService:kService];
+ NSError *error = fakeError();
+ XCTAssertNil([keychain dataForKey:kKey error:&error]);
+ XCTAssertNil(error);
+}
+
+/** @fn testReadExisting
+ @brief Tests reading existing keychain item.
+ */
+- (void)testReadExisting {
+ [self setPassword:kData account:accountFromKey(kKey) service:kService];
+ FIRAuthKeychain *keychain = [[FIRAuthKeychain alloc] initWithService:kService];
+ NSError *error = fakeError();
+ XCTAssertEqualObjects([keychain dataForKey:kKey error:&error], dataFromString(kData));
+ XCTAssertNil(error);
+ [self deletePasswordWithAccount:accountFromKey(kKey) service:kService];
+}
+
+/** @fn testNotReadOtherService
+ @brief Tests not reading keychain item belonging to other service.
+ */
+- (void)testNotReadOtherService {
+ [self setPassword:nil account:accountFromKey(kKey) service:kService];
+ [self setPassword:kData account:accountFromKey(kKey) service:kOtherService];
+ FIRAuthKeychain *keychain = [[FIRAuthKeychain alloc] initWithService:kService];
+ NSError *error = fakeError();
+ XCTAssertNil([keychain dataForKey:kKey error:&error]);
+ XCTAssertNil(error);
+ [self deletePasswordWithAccount:accountFromKey(kKey) service:kOtherService];
+}
+
+/** @fn testWriteNonexisting
+ @brief Tests writing new keychain item.
+ */
+- (void)testWriteNonexisting {
+ [self setPassword:nil account:accountFromKey(kKey) service:kService];
+ FIRAuthKeychain *keychain = [[FIRAuthKeychain alloc] initWithService:kService];
+ XCTAssertTrue([keychain setData:dataFromString(kData) forKey:kKey error:NULL]);
+ XCTAssertEqualObjects([self passwordWithAccount:accountFromKey(kKey) service:kService], kData);
+ [self deletePasswordWithAccount:accountFromKey(kKey) service:kService];
+}
+
+/** @fn testWriteExisting
+ @brief Tests overwriting existing keychain item.
+ */
+- (void)testWriteExisting {
+ [self setPassword:kData account:accountFromKey(kKey) service:kService];
+ FIRAuthKeychain *keychain = [[FIRAuthKeychain alloc] initWithService:kService];
+ XCTAssertTrue([keychain setData:dataFromString(kOtherData) forKey:kKey error:NULL]);
+ XCTAssertEqualObjects([self passwordWithAccount:accountFromKey(kKey) service:kService],
+ kOtherData);
+ [self deletePasswordWithAccount:accountFromKey(kKey) service:kService];
+}
+
+/** @fn testDeleteNonexisting
+ @brief Tests deleting non-existing keychain item.
+ */
+- (void)testDeleteNonexisting {
+ [self setPassword:nil account:accountFromKey(kKey) service:kService];
+ FIRAuthKeychain *keychain = [[FIRAuthKeychain alloc] initWithService:kService];
+ XCTAssertTrue([keychain removeDataForKey:kKey error:NULL]);
+ XCTAssertNil([self passwordWithAccount:accountFromKey(kKey) service:kService]);
+}
+
+/** @fn testDeleteExisting
+ @brief Tests deleting existing keychain item.
+ */
+- (void)testDeleteExisting {
+ [self setPassword:kData account:accountFromKey(kKey) service:kService];
+ FIRAuthKeychain *keychain = [[FIRAuthKeychain alloc] initWithService:kService];
+ XCTAssertTrue([keychain removeDataForKey:kKey error:NULL]);
+ XCTAssertNil([self passwordWithAccount:accountFromKey(kKey) service:kService]);
+}
+
+/** @fn testReadLegacy
+ @brief Tests reading legacy keychain item.
+ */
+- (void)testReadLegacy {
+ [self setPassword:nil account:accountFromKey(kKey) service:kService];
+ [self setPassword:kData account:kKey service:nil]; // legacy form
+ FIRAuthKeychain *keychain = [[FIRAuthKeychain alloc] initWithService:kService];
+ NSError *error = fakeError();
+ XCTAssertEqualObjects([keychain dataForKey:kKey error:&error], dataFromString(kData));
+ XCTAssertNil(error);
+ // Legacy item should have been moved to current form.
+ XCTAssertEqualObjects([self passwordWithAccount:accountFromKey(kKey) service:kService], kData);
+ XCTAssertNil([self passwordWithAccount:kKey service:nil]);
+ [self deletePasswordWithAccount:accountFromKey(kKey) service:kService];
+}
+
+/** @fn testNotReadLegacy
+ @brief Tests not reading legacy keychain item because current keychain item exists.
+ */
+- (void)testNotReadLegacy {
+ [self setPassword:kData account:accountFromKey(kKey) service:kService];
+ [self setPassword:kOtherData account:kKey service:nil]; // legacy form
+ FIRAuthKeychain *keychain = [[FIRAuthKeychain alloc] initWithService:kService];
+ NSError *error = fakeError();
+ XCTAssertEqualObjects([keychain dataForKey:kKey error:&error], dataFromString(kData));
+ XCTAssertNil(error);
+ // Legacy item should have leave untouched.
+ XCTAssertEqualObjects([self passwordWithAccount:accountFromKey(kKey) service:kService], kData);
+ XCTAssertEqualObjects([self passwordWithAccount:kKey service:nil], kOtherData);
+ [self deletePasswordWithAccount:accountFromKey(kKey) service:kService];
+ [self deletePasswordWithAccount:kKey service:nil];
+}
+
+/** @fn testRemoveLegacy
+ @brief Tests removing keychain item also removes legacy keychain item.
+ */
+- (void)testRemoveLegacy {
+ [self setPassword:kData account:accountFromKey(kKey) service:kService];
+ [self setPassword:kOtherData account:kKey service:nil]; // legacy form
+ FIRAuthKeychain *keychain = [[FIRAuthKeychain alloc] initWithService:kService];
+ XCTAssertTrue([keychain removeDataForKey:kKey error:NULL]);
+ XCTAssertNil([self passwordWithAccount:accountFromKey(kKey) service:kService]);
+ XCTAssertNil([self passwordWithAccount:kKey service:nil]);
+}
+
+/** @fn testNullErrorParameter
+ @brief Tests that 'NULL' can be safely passed in.
+ */
+- (void)testNullErrorParameter {
+ FIRAuthKeychain *keychain = [[FIRAuthKeychain alloc] initWithService:kService];
+ [keychain dataForKey:kKey error:NULL];
+ [keychain setData:dataFromString(kData) forKey:kKey error:NULL];
+ [keychain removeDataForKey:kKey error:NULL];
+}
+
+#pragma mark - Helpers
+
+/** @fn passwordWithAccount:service:
+ @brief Reads a generic password string from the keychain.
+ @param account The account attribute of the keychain item.
+ @param service The service attribute of the keychain item, if provided.
+ @return The generic password string, if the keychain item exists.
+ */
+- (nullable NSString *)passwordWithAccount:(nonnull NSString *)account
+ service:(nullable NSString *)service {
+ NSMutableDictionary *query = [@{
+ (__bridge id)kSecReturnData : @YES,
+ (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
+ (__bridge id)kSecAttrAccount : account,
+ } mutableCopy];
+ if (service) {
+ query[(__bridge id)kSecAttrService] = service;
+ }
+ CFDataRef result;
+ OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
+ if (status == errSecItemNotFound) {
+ return nil;
+ }
+ XCTAssertEqual(status, errSecSuccess);
+ return stringFromData((__bridge NSData *)(result));
+}
+
+/** @fn addPassword:account:service:
+ @brief Adds a generic password string to the keychain.
+ @param password The value attribute for the password to write to the keychain item.
+ @param account The account attribute of the keychain item.
+ @param service The service attribute of the keychain item, if provided.
+ */
+- (void)addPassword:(nonnull NSString *)password
+ account:(nonnull NSString *)account
+ service:(nullable NSString *)service {
+ NSMutableDictionary *query = [@{
+ (__bridge id)kSecValueData : dataFromString(password),
+ (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
+ (__bridge id)kSecAttrAccount : account,
+ } mutableCopy];
+ if (service) {
+ query[(__bridge id)kSecAttrService] = service;
+ }
+ OSStatus status = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
+ XCTAssertEqual(status, errSecSuccess);
+}
+
+/** @fn deletePasswordWithAccount:service:
+ @brief Deletes a generic password string from the keychain.
+ @param account The account attribute of the keychain item.
+ @param service The service attribute of the keychain item, if provided.
+ */
+- (void)deletePasswordWithAccount:(nonnull NSString *)account
+ service:(nullable NSString *)service {
+ NSMutableDictionary *query = [@{
+ (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
+ (__bridge id)kSecAttrAccount : account,
+ } mutableCopy];
+ if (service) {
+ query[(__bridge id)kSecAttrService] = service;
+ }
+ OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
+ XCTAssertEqual(status, errSecSuccess);
+}
+
+/** @fn setPasswordWithString:account:service:
+ @brief Sets a generic password string to the keychain.
+ @param password The value attribute of the keychain item, if provided, or nil to delete the
+ existing password if any.
+ @param account The account attribute of the keychain item.
+ @param service The service attribute of the keychain item, if provided.
+ */
+- (void)setPassword:(nullable NSString *)password
+ account:(nonnull NSString *)account
+ service:(nullable NSString *)service {
+ if ([self passwordWithAccount:account service:service]) {
+ [self deletePasswordWithAccount:account service:service];
+ }
+ if (password) {
+ [self addPassword:password account:account service:service];
+ }
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRAuthNotificationManagerTests.m b/Example/Auth/Tests/FIRAuthNotificationManagerTests.m
new file mode 100644
index 0000000..c980eac
--- /dev/null
+++ b/Example/Auth/Tests/FIRAuthNotificationManagerTests.m
@@ -0,0 +1,291 @@
+/*
+ * 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 "FIRAuthAppCredential.h"
+#import "FIRAuthAppCredentialManager.h"
+#import "FIRAuthNotificationManager.h"
+#import <OCMock/OCMock.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** @var kReceipt
+ @brief A fake receipt used for testing.
+ */
+static NSString *const kReceipt = @"FAKE_RECEIPT";
+
+/** @var kSecret
+ @brief A fake secret used for testing.
+ */
+static NSString *const kSecret = @"FAKE_SECRET";
+
+/** @class FIRAuthFakeForwardingDelegate
+ @brief The base class for a fake UIApplicationDelegate that forwards remote notifications.
+ */
+@interface FIRAuthFakeForwardingDelegate : NSObject<UIApplicationDelegate>
+
+/** @property notificationManager
+ @brief The notification manager to forward.
+ */
+@property(nonatomic, strong) FIRAuthNotificationManager *notificationManager;
+
+/** @property forwardsNotification
+ @brief Whether notifications are being forwarded.
+ */
+@property(nonatomic, assign) BOOL forwardsNotification;
+
+/** @property notificationReceived
+ @brief Whether a notification has been received.
+ */
+@property(nonatomic, assign) BOOL notificationReceived;
+
+/** @property notificationhandled
+ @brief Whether a notification has been handled.
+ */
+@property(nonatomic, assign) BOOL notificationhandled;
+
+@end
+@implementation FIRAuthFakeForwardingDelegate
+@end
+
+/** @class FIRAuthFakeForwardingDelegate
+ @brief A fake UIApplicationDelegate that implements the modern deegate method to receive
+ notification.
+ */
+@interface FIRAuthModernForwardingDelegate : FIRAuthFakeForwardingDelegate
+@end
+@implementation FIRAuthModernForwardingDelegate
+
+- (void)application:(UIApplication *)application
+ didReceiveRemoteNotification:(NSDictionary *)userInfo
+ fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
+ self.notificationReceived = YES;
+ if (self.forwardsNotification) {
+ self.notificationhandled = [self.notificationManager canHandleNotification:userInfo];
+ }
+}
+
+@end
+
+/** @class FIRAuthLegacyForwardingDelegate
+ @brief A fake UIApplicationDelegate that implements the legacy deegate method to receive
+ notification.
+ */
+@interface FIRAuthLegacyForwardingDelegate : FIRAuthFakeForwardingDelegate
+@end
+@implementation FIRAuthLegacyForwardingDelegate
+
+- (void)application:(UIApplication *)application
+ didReceiveRemoteNotification:(NSDictionary *)userInfo {
+ self.notificationReceived = YES;
+ if (self.forwardsNotification) {
+ self.notificationhandled = [self.notificationManager canHandleNotification:userInfo];
+ }
+}
+
+@end
+
+/** @class FIRAuthNotificationManagerTests
+ @brief Unit tests for @c FIRAuthNotificationManager .
+ */
+@interface FIRAuthNotificationManagerTests : XCTestCase
+@end
+@implementation FIRAuthNotificationManagerTests {
+ /** @var _mockApplication
+ @brief The mock UIApplication for testing.
+ */
+ id _mockApplication;
+
+ /** @var _mockAppCredentialManager
+ @brief The mock FIRAuthAppCredentialManager for testing.
+ */
+ id _mockAppCredentialManager;
+
+ /** @var _notificationManager
+ @brief The FIRAuthNotificationManager to be tested.
+ */
+ FIRAuthNotificationManager *_notificationManager;
+
+ /** @var _modernDelegate
+ @brief The modern fake UIApplicationDelegate for testing.
+ */
+ FIRAuthModernForwardingDelegate *_modernDelegate;
+
+ /** @var _legacyDelegate
+ @brief The legacy fake UIApplicationDelegate for testing.
+ */
+ FIRAuthLegacyForwardingDelegate *_legacyDelegate;
+}
+
+- (void)setUp {
+ _mockApplication = OCMClassMock([UIApplication class]);
+ _mockAppCredentialManager = OCMClassMock([FIRAuthAppCredentialManager class]);
+ _notificationManager =
+ [[FIRAuthNotificationManager alloc] initWithApplication:_mockApplication
+ appCredentialManager:_mockAppCredentialManager];
+ _modernDelegate = [[FIRAuthModernForwardingDelegate alloc] init];
+ _modernDelegate.notificationManager = _notificationManager;
+ _legacyDelegate = [[FIRAuthLegacyForwardingDelegate alloc] init];
+ _legacyDelegate.notificationManager = _notificationManager;
+}
+
+/** @fn testForwardingModernDelegate
+ @brief Tests checking notification forwarding on modern fake delegate.
+ */
+- (void)testForwardingModernDelegate {
+ [self verifyForwarding:YES delegate:_modernDelegate];
+}
+
+/** @fn testForwardingLegacyDelegate
+ @brief Tests checking notification forwarding on legacy fake delegate.
+ */
+- (void)testForwardingLegacyDelegate {
+ [self verifyForwarding:YES delegate:_legacyDelegate];
+}
+
+/** @fn testNotForwardingModernDelegate
+ @brief Tests checking notification not forwarding on modern fake delegate.
+ */
+- (void)testNotForwardingModernDelegate {
+ [self verifyForwarding:NO delegate:_modernDelegate];
+}
+
+/** @fn testNotForwardingLegacyDelegate
+ @brief Tests checking notification not forwarding on legacy fake delegate.
+ */
+- (void)testNotForwardingLegacyDelegate {
+ [self verifyForwarding:NO delegate:_legacyDelegate];
+}
+
+/** @fn verifyForwarding:delegate:
+ @brief Tests checking notification forwarding on a particular delegate.
+ @param forwarding Whether the notification is being forwarded or not.
+ @param delegate The fake UIApplicationDelegate used for testing.
+ */
+- (void)verifyForwarding:(BOOL)forwarding
+ delegate:(FIRAuthFakeForwardingDelegate *)delegate {
+ delegate.forwardsNotification = forwarding;
+ OCMStub([_mockApplication delegate]).andReturn(delegate);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [_notificationManager
+ checkNotificationForwardingWithCallback:^(BOOL isNotificationBeingForwarded) {
+ XCTAssertEqual(isNotificationBeingForwarded, forwarding);
+ [expectation fulfill];
+ }];
+ XCTAssertFalse(delegate.notificationReceived);
+ NSTimeInterval timeout = _notificationManager.timeout * (1.5 - forwarding);
+ [self waitForExpectationsWithTimeout:timeout handler:nil];
+ XCTAssertTrue(delegate.notificationReceived);
+ XCTAssertEqual(delegate.notificationhandled, forwarding);
+}
+
+/** @fn testCachedResult
+ @brief Test notification forwarding is only checked once.
+ */
+- (void)testCachedResult {
+ FIRAuthFakeForwardingDelegate *delegate = _modernDelegate;
+ [self verifyForwarding:NO delegate:delegate];
+ delegate.notificationReceived = NO;
+ __block BOOL calledBack = NO;
+ [_notificationManager
+ checkNotificationForwardingWithCallback:^(BOOL isNotificationBeingForwarded) {
+ XCTAssertFalse(isNotificationBeingForwarded);
+ calledBack = YES;
+ }];
+ XCTAssertTrue(calledBack);
+ XCTAssertFalse(delegate.notificationReceived);
+}
+
+/** @fn testMultipleCallbacks
+ @brief Test multiple callbacks are handled correctly.
+ */
+- (void)testMultipleCallbacks {
+ FIRAuthFakeForwardingDelegate *delegate = _legacyDelegate;
+ delegate.forwardsNotification = YES;
+ OCMStub([_mockApplication delegate]).andReturn(delegate);
+ XCTestExpectation *expectation1 = [self expectationWithDescription:@"callback1"];
+ [_notificationManager
+ checkNotificationForwardingWithCallback:^(BOOL isNotificationBeingForwarded) {
+ XCTAssertTrue(isNotificationBeingForwarded);
+ [expectation1 fulfill];
+ }];
+ XCTestExpectation *expectation2 = [self expectationWithDescription:@"callback2"];
+ [_notificationManager
+ checkNotificationForwardingWithCallback:^(BOOL isNotificationBeingForwarded) {
+ XCTAssertTrue(isNotificationBeingForwarded);
+ [expectation2 fulfill];
+ }];
+ XCTAssertFalse(delegate.notificationReceived);
+ [self waitForExpectationsWithTimeout:_notificationManager.timeout * .5 handler:nil];
+ XCTAssertTrue(delegate.notificationReceived);
+ XCTAssertTrue(delegate.notificationhandled);
+}
+
+/** @fn testPassingToCredentialManager
+ @brief Test notification with the right structure is passed to credential manager.
+ */
+- (void)testPassingToCredentialManager {
+ NSDictionary *payload = @{ @"receipt" : kReceipt, @"secret" : kSecret };
+ NSDictionary *notification = @{ @"com.google.firebase.auth" : payload };
+ OCMExpect([_mockAppCredentialManager canFinishVerificationWithReceipt:kReceipt secret:kSecret])
+ .andReturn(YES);
+ XCTAssertTrue([_notificationManager canHandleNotification:notification]);
+ OCMVerifyAll(_mockAppCredentialManager);
+
+ // JSON string form
+ NSData *data = [NSJSONSerialization dataWithJSONObject:payload options:0 error:NULL];
+ NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+ notification = @{ @"com.google.firebase.auth" : string };
+ OCMExpect([_mockAppCredentialManager canFinishVerificationWithReceipt:kReceipt secret:kSecret])
+ .andReturn(YES);
+ XCTAssertTrue([_notificationManager canHandleNotification:notification]);
+ OCMVerifyAll(_mockAppCredentialManager);
+}
+
+/** @fn testNotHandling
+ @brief Test unrecognized notifications are not handled.
+ */
+- (void)testNotHandling {
+ XCTAssertFalse([_notificationManager canHandleNotification:@{
+ @"random" : @"string"
+ }]);
+ XCTAssertFalse([_notificationManager canHandleNotification:@{
+ @"com.google.firebase.auth" : @"something wrong"
+ }]);
+ XCTAssertFalse([_notificationManager canHandleNotification:@{
+ @"com.google.firebase.auth" : @{
+ @"receipt" : kReceipt
+ // missing secret
+ }
+ }]);
+ XCTAssertFalse([_notificationManager canHandleNotification:@{
+ @"com.google.firebase.auth" : @{
+ // missing receipt
+ @"secret" : kSecret
+ }
+ }]);
+ XCTAssertFalse([_notificationManager canHandleNotification:@{
+ @"com.google.firebase.auth" : @{
+ // probing notification does not belong to this instance
+ @"warning" : @"asdf"
+ }
+ }]);
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Example/Auth/Tests/FIRAuthSerialTaskQueueTests.m b/Example/Auth/Tests/FIRAuthSerialTaskQueueTests.m
new file mode 100644
index 0000000..26164d6
--- /dev/null
+++ b/Example/Auth/Tests/FIRAuthSerialTaskQueueTests.m
@@ -0,0 +1,113 @@
+/*
+ * 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 "FIRAuthGlobalWorkQueue.h"
+#import "FIRAuthSerialTaskQueue.h"
+
+/** @var kTimeout
+ @brief Time-out in seconds waiting for tasks to be executed.
+ */
+static const NSTimeInterval kTimeout = 1;
+
+/** @class FIRAuthSerialTaskQueueTests
+ @brief Tests for @c FIRAuthSerialTaskQueue .
+ */
+@interface FIRAuthSerialTaskQueueTests : XCTestCase
+@end
+@implementation FIRAuthSerialTaskQueueTests
+
+- (void)testExecution {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"executed"];
+ FIRAuthSerialTaskQueue *queue = [[FIRAuthSerialTaskQueue alloc] init];
+ [queue enqueueTask:^(FIRAuthSerialTaskCompletionBlock completionArg) {
+ completionArg();
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kTimeout handler:nil];
+}
+
+- (void)testCompletion {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"executed"];
+ FIRAuthSerialTaskQueue *queue = [[FIRAuthSerialTaskQueue alloc] init];
+ __block FIRAuthSerialTaskCompletionBlock completion = nil;
+ [queue enqueueTask:^(FIRAuthSerialTaskCompletionBlock completionArg) {
+ completion = completionArg;
+ [expectation fulfill];
+ }];
+ __block XCTestExpectation *nextExpectation = nil;
+ __block BOOL executed = NO;
+ [queue enqueueTask:^(FIRAuthSerialTaskCompletionBlock completionArg) {
+ executed = YES;
+ completionArg();
+ [nextExpectation fulfill];
+ }];
+ // The second task should not be executed until the first is completed.
+ [self waitForExpectationsWithTimeout:kTimeout handler:nil];
+ XCTAssertNotNil(completion);
+ XCTAssertFalse(executed);
+ nextExpectation = [self expectationWithDescription:@"executed next"];
+ completion();
+ [self waitForExpectationsWithTimeout:kTimeout handler:nil];
+ XCTAssertTrue(executed);
+}
+
+- (void)testTargetQueue {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"executed"];
+ FIRAuthSerialTaskQueue *queue = [[FIRAuthSerialTaskQueue alloc] init];
+ __block BOOL executed = NO;
+ dispatch_suspend(FIRAuthGlobalWorkQueue());
+ [queue enqueueTask:^(FIRAuthSerialTaskCompletionBlock completionArg) {
+ executed = YES;
+ completionArg();
+ [expectation fulfill];
+ }];
+ // The task should not executed until the global work queue is resumed.
+ usleep(kTimeout * USEC_PER_SEC);
+ XCTAssertFalse(executed);
+ dispatch_resume(FIRAuthGlobalWorkQueue());
+ [self waitForExpectationsWithTimeout:kTimeout handler:nil];
+}
+
+- (void)testTaskQueueNoAffectTargetQueue {
+ FIRAuthSerialTaskQueue *queue = [[FIRAuthSerialTaskQueue alloc] init];
+ __block FIRAuthSerialTaskCompletionBlock completion = nil;
+ [queue enqueueTask:^(FIRAuthSerialTaskCompletionBlock completionArg) {
+ completion = completionArg;
+ }];
+ __block XCTestExpectation *nextExpectation = nil;
+ __block BOOL executed = NO;
+ [queue enqueueTask:^(FIRAuthSerialTaskCompletionBlock completionArg) {
+ executed = YES;
+ completionArg();
+ [nextExpectation fulfill];
+ }];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"executed"];
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^{
+ [expectation fulfill];
+ });
+ // The task queue waiting for completion should not affect the global work queue.
+ [self waitForExpectationsWithTimeout:kTimeout handler:nil];
+ XCTAssertNotNil(completion);
+ XCTAssertFalse(executed);
+ nextExpectation = [self expectationWithDescription:@"executed next"];
+ completion();
+ [self waitForExpectationsWithTimeout:kTimeout handler:nil];
+ XCTAssertTrue(executed);
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRAuthTests.m b/Example/Auth/Tests/FIRAuthTests.m
new file mode 100644
index 0000000..3a6f717
--- /dev/null
+++ b/Example/Auth/Tests/FIRAuthTests.m
@@ -0,0 +1,1743 @@
+/*
+ * 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 "FIRAppInternal.h"
+#import "EmailPassword/FIREmailAuthProvider.h"
+#import "Google/FIRGoogleAuthProvider.h"
+#import "Phone/FIRPhoneAuthCredential.h"
+#import "Phone/FIRPhoneAuthProvider.h"
+#import "FIRAdditionalUserInfo.h"
+#import "FIRAuth_Internal.h"
+#import "FIRAuthErrorUtils.h"
+#import "FIRAuthDispatcher.h"
+#import "FIRAuthGlobalWorkQueue.h"
+#import "FIRUser_Internal.h"
+#import "FIRAuthBackend.h"
+#import "FIRCreateAuthURIRequest.h"
+#import "FIRCreateAuthURIResponse.h"
+#import "FIRGetAccountInfoRequest.h"
+#import "FIRGetAccountInfoResponse.h"
+#import "FIRGetOOBConfirmationCodeRequest.h"
+#import "FIRGetOOBConfirmationCodeResponse.h"
+#import "FIRSecureTokenRequest.h"
+#import "FIRSecureTokenResponse.h"
+#import "FIRResetPasswordRequest.h"
+#import "FIRResetPasswordResponse.h"
+#import "FIRSetAccountInfoRequest.h"
+#import "FIRSetAccountInfoResponse.h"
+#import "FIRSignUpNewUserRequest.h"
+#import "FIRSignUpNewUserResponse.h"
+#import "FIRVerifyCustomTokenRequest.h"
+#import "FIRVerifyCustomTokenResponse.h"
+#import "FIRVerifyAssertionRequest.h"
+#import "FIRVerifyAssertionResponse.h"
+#import "FIRVerifyPasswordRequest.h"
+#import "FIRVerifyPasswordResponse.h"
+#import "FIRVerifyPhoneNumberRequest.h"
+#import "FIRVerifyPhoneNumberResponse.h"
+#import "FIRApp+FIRAuthUnitTests.h"
+#import "OCMStubRecorder+FIRAuthUnitTests.h"
+#import <OCMock/OCMock.h>
+
+/** @var kFirebaseAppName1
+ @brief A fake Firebase app name.
+ */
+static NSString *const kFirebaseAppName1 = @"FIREBASE_APP_NAME_1";
+
+/** @var kFirebaseAppName2
+ @brief Another fake Firebase app name.
+ */
+static NSString *const kFirebaseAppName2 = @"FIREBASE_APP_NAME_2";
+
+/** @var kAPIKey
+ @brief The fake API key.
+ */
+static NSString *const kAPIKey = @"FAKE_API_KEY";
+
+/** @var kAccessToken
+ @brief The fake access token.
+ */
+static NSString *const kAccessToken = @"ACCESS_TOKEN";
+
+/** @var kNewAccessToken
+ @brief Another fake access token used to simulate token refreshed via automatic token refresh.
+ */
+NSString *kNewAccessToken = @"NewAccessToken";
+
+/** @var kAccessTokenValidInterval
+ @brief The time to live for the fake access token.
+ */
+static const NSTimeInterval kAccessTokenTimeToLive = 60 * 60;
+
+/** @var kTestTokenExpirationTimeInterval
+ @brief The fake time interval that it takes a token to expire.
+ */
+static const NSTimeInterval kTestTokenExpirationTimeInterval = 55 * 60;
+
+/** @var kRefreshToken
+ @brief The fake refresh token.
+ */
+static NSString *const kRefreshToken = @"REFRESH_TOKEN";
+
+/** @var kEmail
+ @brief The fake user email.
+ */
+static NSString *const kEmail = @"user@company.com";
+
+/** @var kPassword
+ @brief The fake user password.
+ */
+static NSString *const kPassword = @"!@#$%^";
+
+/** @var kPasswordHash
+ @brief The fake user password hash.
+ */
+static NSString *const kPasswordHash = @"UkVEQUNURUQ=";
+
+/** @var kLocalID
+ @brief The fake local user ID.
+ */
+static NSString *const kLocalID = @"LOCAL_ID";
+
+/** @var kDisplayName
+ @brief The fake user display name.
+ */
+static NSString *const kDisplayName = @"User Doe";
+
+/** @var kGoogleUD
+ @brief The fake user ID under Google Sign-In.
+ */
+static NSString *const kGoogleID = @"GOOGLE_ID";
+
+/** @var kGoogleEmail
+ @brief The fake user email under Google Sign-In.
+ */
+static NSString *const kGoogleEmail = @"user@gmail.com";
+
+/** @var kGoogleDisplayName
+ @brief The fake user display name under Google Sign-In.
+ */
+static NSString *const kGoogleDisplayName = @"Google Doe";
+
+/** @var kGoogleAccessToken
+ @brief The fake access token from Google Sign-In.
+ */
+static NSString *const kGoogleAccessToken = @"GOOGLE_ACCESS_TOKEN";
+
+/** @var kGoogleIDToken
+ @brief The fake ID token from Google Sign-In.
+ */
+static NSString *const kGoogleIDToken = @"GOOGLE_ID_TOKEN";
+
+/** @var kCustomToken
+ @brief The fake custom token to sign in.
+ */
+static NSString *const kCustomToken = @"CUSTOM_TOKEN";
+
+/** @var kVerificationCode
+ @brief Fake verification code used for testing.
+ */
+static NSString *const kVerificationCode = @"12345678";
+
+/** @var kVerificationID
+ @brief Fake verification ID for testing.
+ */
+static NSString *const kVerificationID = @"55432";
+
+/** @var kExpectationTimeout
+ @brief The maximum time waiting for expectations to fulfill.
+ */
+static const NSTimeInterval kExpectationTimeout = 1;
+
+/** @var kWaitInterval
+ @brief The time waiting for background tasks to finish before continue when necessary.
+ */
+static const NSTimeInterval kWaitInterval = .5;
+
+/** @class FIRAuthTests
+ @brief Tests for @c FIRAuth.
+ */
+@interface FIRAuthTests : XCTestCase
+@end
+@implementation FIRAuthTests {
+
+ /** @var _mockBackend
+ @brief The mock @c FIRAuthBackendImplementation .
+ */
+ id _mockBackend;
+
+ /** @var _FIRAuthDispatcherCallback
+ @brief Used to save a task from FIRAuthDispatcher to be executed later.
+ */
+ __block void (^_Nonnull _FIRAuthDispatcherCallback)(void);
+}
+
+/** @fn googleProfile
+ @brief The fake user profile under additional user data in @c FIRVerifyAssertionResponse.
+ */
++ (NSDictionary *)googleProfile {
+ static NSDictionary *kGoogleProfile = nil;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ kGoogleProfile = @{
+ @"iss": @"https://accounts.google.com\\",
+ @"email": kGoogleEmail,
+ @"given_name": @"User",
+ @"family_name": @"Doe"
+ };
+ });
+ return kGoogleProfile;
+}
+
+- (void)setUp {
+ [super setUp];
+ _mockBackend = OCMProtocolMock(@protocol(FIRAuthBackendImplementation));
+ [FIRAuthBackend setBackendImplementation:_mockBackend];
+ [FIRApp resetAppForAuthUnitTests];
+
+ // Set FIRAuthDispatcher implementation in order to save the token refresh task for later
+ // execution.
+ [[FIRAuthDispatcher sharedInstance]
+ setDispatchAfterImplementation:^(NSTimeInterval delay,
+ dispatch_queue_t _Nonnull queue,
+ void (^task)(void)) {
+ XCTAssertNotNil(task);
+ XCTAssert(delay > 0);
+ XCTAssertEqualObjects(FIRAuthGlobalWorkQueue(), queue);
+ _FIRAuthDispatcherCallback = task;
+ }];
+}
+
+- (void)tearDown {
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
+ [[FIRAuthDispatcher sharedInstance] setDispatchAfterImplementation:nil];
+ [super tearDown];
+}
+
+#pragma mark - Life Cycle Tests
+
+/** @fn testSingleton
+ @brief Verifies the @c auth method behaves like a singleton.
+ */
+- (void)testSingleton {
+ FIRAuth *auth1 = [FIRAuth auth];
+ XCTAssertNotNil(auth1);
+ FIRAuth *auth2 = [FIRAuth auth];
+ XCTAssertEqual(auth1, auth2);
+}
+
+/** @fn testDefaultAuth
+ @brief Verifies the @c auth method associates with the default Firebase app.
+ */
+- (void)testDefaultAuth {
+ FIRAuth *auth1 = [FIRAuth auth];
+ FIRAuth *auth2 = [FIRAuth authWithApp:[FIRApp defaultApp]];
+ XCTAssertEqual(auth1, auth2);
+ XCTAssertEqual(auth1.app, [FIRApp defaultApp]);
+}
+
+/** @fn testNilAppException
+ @brief Verifies the @c auth method raises an exception if the default FIRApp is not configured.
+ */
+- (void)testNilAppException {
+ [FIRApp resetApps];
+ XCTAssertThrows([FIRAuth auth]);
+}
+
+/** @fn testAppAPIkey
+ @brief Verifies the API key is correctly copied from @c FIRApp to @c FIRAuth .
+ */
+- (void)testAppAPIkey {
+ FIRAuth *auth = [FIRAuth auth];
+ XCTAssertEqualObjects(auth.APIKey, kAPIKey);
+}
+
+/** @fn testAppAssociation
+ @brief Verifies each @c FIRApp instance associates with a @c FIRAuth .
+ */
+- (void)testAppAssociation {
+ FIRApp *app1 = [self app1];
+ FIRAuth *auth1 = [FIRAuth authWithApp:app1];
+ XCTAssertNotNil(auth1);
+ XCTAssertEqual(auth1.app, app1);
+
+ FIRApp *app2 = [self app2];
+ FIRAuth *auth2 = [FIRAuth authWithApp:app2];
+ XCTAssertNotNil(auth2);
+ XCTAssertEqual(auth2.app, app2);
+
+ XCTAssertNotEqual(auth1, auth2);
+}
+
+/** @fn testLifeCycle
+ @brief Verifies the life cycle of @c FIRAuth is the same as its associated @c FIRApp .
+ */
+- (void)testLifeCycle {
+ __weak FIRApp *app;
+ __weak FIRAuth *auth;
+ @autoreleasepool {
+ FIRApp *app1 = [self app1];
+ app = app1;
+ auth = [FIRAuth authWithApp:app1];
+ // Verify that neither the app nor the auth is released yet, i.e., the app owns the auth
+ // because nothing else retains the auth.
+ XCTAssertNotNil(app);
+ XCTAssertNotNil(auth);
+ }
+ [self waitForTimeIntervel:kWaitInterval];
+ // Verify that both the app and the auth are released upon exit of the autorelease pool,
+ // i.e., the app is the sole owner of the auth.
+ XCTAssertNil(app);
+ XCTAssertNil(auth);
+}
+
+/** @fn testGetUID
+ @brief Verifies that FIRApp's getUIDImplementation is correctly set by FIRAuth.
+ */
+- (void)testGetUID {
+ FIRApp *app = [FIRApp defaultApp];
+ XCTAssertNotNil(app.getUIDImplementation);
+ [[FIRAuth auth] signOut:NULL];
+ XCTAssertNil(app.getUIDImplementation());
+ [self waitForSignIn];
+ XCTAssertEqualObjects(app.getUIDImplementation(), kLocalID);
+}
+
+#pragma mark - Server API Tests
+
+/** @fn testFetchProvidersForEmailSuccess
+ @brief Tests the flow of a successful @c fetchProvidersForEmail:completion: call.
+ */
+- (void)testFetchProvidersForEmailSuccess {
+ NSArray<NSString *> *allProviders =
+ @[ FIRGoogleAuthProviderID, FIREmailAuthProviderID ];
+ OCMExpect([_mockBackend createAuthURI:[OCMArg any]
+ callback:[OCMArg any]])
+ .andCallBlock2(^(FIRCreateAuthURIRequest *_Nullable request,
+ FIRCreateAuthURIResponseCallback callback) {
+ XCTAssertEqualObjects(request.identifier, kEmail);
+ XCTAssertNotNil(request.endpoint);
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockCreateAuthURIResponse = OCMClassMock([FIRCreateAuthURIResponse class]);
+ OCMStub([mockCreateAuthURIResponse allProviders]).andReturn(allProviders);
+ callback(mockCreateAuthURIResponse, nil);
+ });
+ });
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] fetchProvidersForEmail:kEmail
+ completion:^(NSArray<NSString *> *_Nullable providers,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertEqualObjects(providers, allProviders);
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testFetchProvidersForEmailSuccessDeprecatedProviderID
+ @brief Tests the flow of a successful @c fetchProvidersForEmail:completion: call using the
+ deprecated FIREmailPasswordAuthProviderID.
+ */
+- (void)testFetchProvidersForEmailSuccessDeprecatedProviderID {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ NSArray<NSString *> *allProviders =
+ @[ FIRGoogleAuthProviderID, FIREmailPasswordAuthProviderID ];
+#pragma clang diagnostic pop
+ OCMExpect([_mockBackend createAuthURI:[OCMArg any]
+ callback:[OCMArg any]])
+ .andCallBlock2(^(FIRCreateAuthURIRequest *_Nullable request,
+ FIRCreateAuthURIResponseCallback callback) {
+ XCTAssertEqualObjects(request.identifier, kEmail);
+ XCTAssertNotNil(request.endpoint);
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockCreateAuthURIResponse = OCMClassMock([FIRCreateAuthURIResponse class]);
+ OCMStub([mockCreateAuthURIResponse allProviders]).andReturn(allProviders);
+ callback(mockCreateAuthURIResponse, nil);
+ });
+ });
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] fetchProvidersForEmail:kEmail
+ completion:^(NSArray<NSString *> *_Nullable providers,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertEqualObjects(providers, allProviders);
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testFetchProvidersForEmailFailure
+ @brief Tests the flow of a failed @c fetchProvidersForEmail:completion: call.
+ */
+- (void)testFetchProvidersForEmailFailure {
+ OCMExpect([_mockBackend createAuthURI:[OCMArg any] callback:[OCMArg any]])
+ .andDispatchError2([FIRAuthErrorUtils tooManyRequestsErrorWithMessage:nil]);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] fetchProvidersForEmail:kEmail
+ completion:^(NSArray<NSString *> *_Nullable providers,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(providers);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeTooManyRequests);
+ XCTAssertNotNil(error.userInfo[NSLocalizedDescriptionKey]);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testPhoneAuthSuccess
+ @brief Tests the flow of a successful @c signInWithCredential:completion for phone auth.
+ */
+- (void)testPhoneAuthSuccess {
+ OCMExpect([_mockBackend verifyPhoneNumber:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRVerifyPhoneNumberRequest *_Nullable request,
+ FIRVerifyPhoneNumberResponseCallback callback) {
+ XCTAssertEqualObjects(request.verificationCode, kVerificationCode);
+ XCTAssertEqualObjects(request.verificationID, kVerificationID);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockVerifyPhoneResponse = OCMClassMock([FIRVerifyPhoneNumberResponse class]);
+ [self stubTokensWithMockResponse:mockVerifyPhoneResponse];
+ callback(mockVerifyPhoneResponse, nil);
+ });
+ });
+ [self expectGetAccountInfo];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ FIRAuthCredential *credential =
+ [[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID
+ verificationCode:kVerificationCode];
+
+ [[FIRAuth auth] signInWithCredential:credential completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ [self assertUser:user];
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ [self assertUser:[FIRAuth auth].currentUser];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testPhoneAuthMissingVerificationCode
+ @brief Tests the flow of an unsuccessful @c signInWithCredential:completion for phone auth due
+ to an empty verification code
+ */
+- (void)testPhoneAuthMissingVerificationCode {
+ [self expectGetAccountInfo];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ FIRAuthCredential *credential =
+ [[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID
+ verificationCode:@""];
+
+ [[FIRAuth auth] signInWithCredential:credential completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(user);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeMissingVerificationCode);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+}
+
+/** @fn testPhoneAuthMissingVerificationID
+ @brief Tests the flow of an unsuccessful @c signInWithCredential:completion for phone auth due
+ to an empty verification ID.
+ */
+- (void)testPhoneAuthMissingVerificationID {
+ [self expectGetAccountInfo];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ FIRAuthCredential *credential =
+ [[FIRPhoneAuthProvider provider] credentialWithVerificationID:@""
+ verificationCode:kVerificationCode];
+
+ [[FIRAuth auth] signInWithCredential:credential completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(user);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeMissingVerificationID);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+}
+
+/** @fn testSignInWithEmailPasswordSuccess
+ @brief Tests the flow of a successful @c signInWithEmail:password:completion: call.
+ */
+- (void)testSignInWithEmailPasswordSuccess {
+ OCMExpect([_mockBackend verifyPassword:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRVerifyPasswordRequest *_Nullable request,
+ FIRVerifyPasswordResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.email, kEmail);
+ XCTAssertEqualObjects(request.password, kPassword);
+ XCTAssertTrue(request.returnSecureToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockVerifyPasswordResponse = OCMClassMock([FIRVerifyPasswordResponse class]);
+ [self stubTokensWithMockResponse:mockVerifyPasswordResponse];
+ callback(mockVerifyPasswordResponse, nil);
+ });
+ });
+ [self expectGetAccountInfo];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] signInWithEmail:kEmail password:kPassword completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ [self assertUser:user];
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ [self assertUser:[FIRAuth auth].currentUser];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testSignInWithEmailPasswordFailure
+ @brief Tests the flow of a failed @c signInWithEmail:password:completion: call.
+ */
+- (void)testSignInWithEmailPasswordFailure {
+ OCMExpect([_mockBackend verifyPassword:[OCMArg any] callback:[OCMArg any]])
+ .andDispatchError2([FIRAuthErrorUtils wrongPasswordErrorWithMessage:nil]);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] signInWithEmail:kEmail password:kPassword completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(user);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeWrongPassword);
+ XCTAssertNotNil(error.userInfo[NSLocalizedDescriptionKey]);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ XCTAssertNil([FIRAuth auth].currentUser);
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testResetPasswordSuccess
+ @brief Tests the flow of a successful @c confirmPasswordResetWithCode:newPassword:completion:
+ call.
+ */
+- (void)testResetPasswordSuccess {
+ NSString *fakeEmail = @"fakeEmail";
+ NSString *fakeCode = @"fakeCode";
+ NSString *fakeNewPassword = @"fakeNewPassword";
+ OCMExpect([_mockBackend resetPassword:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRResetPasswordRequest *_Nullable request,
+ FIRResetPasswordCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.oobCode, fakeCode);
+ XCTAssertEqualObjects(request.updatedPassword, fakeNewPassword);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockResetPasswordResponse = OCMClassMock([FIRResetPasswordResponse class]);
+ OCMStub([mockResetPasswordResponse email]).andReturn(fakeEmail);
+ callback(mockResetPasswordResponse, nil);
+ });
+ });
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] confirmPasswordResetWithCode:fakeCode
+ newPassword:fakeNewPassword
+ completion:^(NSError *_Nullable error) {
+
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testResetPasswordFailure
+ @brief Tests the flow of a failed @c confirmPasswordResetWithCode:newPassword:completion:
+ call.
+ */
+- (void)testResetPasswordFailure {
+ NSString *fakeCode = @"fakeCode";
+ NSString *fakeNewPassword = @"fakeNewPassword";
+ OCMExpect([_mockBackend resetPassword:[OCMArg any] callback:[OCMArg any]])
+ ._andDispatchError2([FIRAuthErrorUtils invalidActionCodeErrorWithMessage:nil]);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] confirmPasswordResetWithCode:fakeCode
+ newPassword:fakeNewPassword
+ completion:^(NSError *_Nullable error) {
+
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidActionCode);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testCheckActionCodeSuccess
+ @brief Tests the flow of a successful @c checkActionCode:completion call.
+ */
+- (void)testCheckActionCodeSuccess {
+ NSString *verifyEmailRequestType = @"VERIFY_EMAIL";
+ NSString *fakeEmail = @"fakeEmail";
+ NSString *fakeNewEmail = @"fakeNewEmail";
+ NSString *fakeCode = @"fakeCode";
+ OCMExpect([_mockBackend resetPassword:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRResetPasswordRequest *_Nullable request,
+ FIRResetPasswordCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.oobCode, fakeCode);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockResetPasswordResponse = OCMClassMock([FIRResetPasswordResponse class]);
+ OCMStub([mockResetPasswordResponse email]).andReturn(fakeEmail);
+ OCMStub([mockResetPasswordResponse verifiedEmail]).andReturn(fakeNewEmail);
+ OCMStubRecorder *stub =
+ OCMStub([(FIRResetPasswordResponse *) mockResetPasswordResponse requestType]);
+ stub.andReturn(verifyEmailRequestType);
+ callback(mockResetPasswordResponse, nil);
+ });
+ });
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] checkActionCode:fakeCode completion:^(FIRActionCodeInfo *_Nullable info,
+ NSError *_Nullable error) {
+
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(error);
+ XCTAssertEqual(info.operation, FIRActionCodeOperationVerifyEmail);
+ XCTAssert([fakeNewEmail isEqualToString:[info dataForKey:FIRActionCodeEmailKey]]);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testCheckActionCodeFailure
+ @brief Tests the flow of a failed @c checkActionCode:completion call.
+ */
+- (void)testCheckActionCodeFailure {
+ NSString *fakeCode = @"fakeCode";
+ OCMExpect([_mockBackend resetPassword:[OCMArg any] callback:[OCMArg any]])
+ ._andDispatchError2([FIRAuthErrorUtils expiredActionCodeErrorWithMessage:nil]);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] checkActionCode:fakeCode completion:^(FIRActionCodeInfo *_Nullable info,
+ NSError *_Nullable error) {
+
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNotNil(error);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeExpiredActionCode);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testApplyActionCodeSuccess
+ @brief Tests the flow of a successful @c applyActionCode:completion call.
+ */
+- (void)testApplyActionCodeSuccess {
+ NSString *fakeCode = @"fakeCode";
+ OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRSetAccountInfoRequest *_Nullable request,
+ FIRSetAccountInfoResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.OOBCode, fakeCode);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockSetAccountInfoResponse = OCMClassMock([FIRSetAccountInfoResponse class]);
+ callback(mockSetAccountInfoResponse, nil);
+ });
+ });
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] applyActionCode:fakeCode completion:^(NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testApplyActionCodeFailure
+ @brief Tests the flow of a failed @c checkActionCode:completion call.
+ */
+- (void)testApplyActionCodeFailure {
+ NSString *fakeCode = @"fakeCode";
+ OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]])
+ ._andDispatchError2([FIRAuthErrorUtils invalidActionCodeErrorWithMessage:nil]);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] applyActionCode:fakeCode completion:^(NSError *_Nullable error) {
+
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNotNil(error);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidActionCode);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testVerifyPasswordResetCodeSuccess
+ @brief Tests the flow of a successful @c verifyPasswordResetCode:completion call.
+ */
+- (void)testVerifyPasswordResetCodeSuccess {
+ NSString *passwordResetRequestType = @"PASSWORD_RESET";
+ NSString *fakeEmail = @"fakeEmail";
+ NSString *fakeCode = @"fakeCode";
+ OCMExpect([_mockBackend resetPassword:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRResetPasswordRequest *_Nullable request,
+ FIRResetPasswordCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.oobCode, fakeCode);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockResetPasswordResponse = OCMClassMock([FIRResetPasswordResponse class]);
+ OCMStub([mockResetPasswordResponse email]).andReturn(fakeEmail);
+ OCMStubRecorder *stub =
+ OCMStub([(FIRResetPasswordResponse *) mockResetPasswordResponse requestType]);
+ stub.andReturn(passwordResetRequestType);
+ callback(mockResetPasswordResponse, nil);
+ });
+ });
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] verifyPasswordResetCode:fakeCode completion:^(NSString *_Nullable email,
+ NSError *_Nullable error) {
+
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(error);
+ XCTAssertEqual(email, fakeEmail);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testVerifyPasswordResetCodeFailure
+ @brief Tests the flow of a failed @c verifyPasswordResetCode:completion call.
+ */
+- (void)testVeridyPasswordResetCodeFailure {
+ NSString *fakeCode = @"fakeCode";
+ OCMExpect([_mockBackend resetPassword:[OCMArg any] callback:[OCMArg any]])
+ ._andDispatchError2([FIRAuthErrorUtils invalidActionCodeErrorWithMessage:nil]);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] verifyPasswordResetCode:fakeCode completion:^(NSString *_Nullable email,
+ NSError *_Nullable error) {
+
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNotNil(error);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidActionCode);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testSignInWithEmailCredentialSuccess
+ @brief Tests the flow of a successfully @c signInWithCredential:completion: call with an
+ email-password credential.
+ */
+- (void)testSignInWithEmailCredentialSuccess {
+ OCMExpect([_mockBackend verifyPassword:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRVerifyPasswordRequest *_Nullable request,
+ FIRVerifyPasswordResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.email, kEmail);
+ XCTAssertEqualObjects(request.password, kPassword);
+ XCTAssertTrue(request.returnSecureToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockVeriyPasswordResponse = OCMClassMock([FIRVerifyPasswordResponse class]);
+ [self stubTokensWithMockResponse:mockVeriyPasswordResponse];
+ callback(mockVeriyPasswordResponse, nil);
+ });
+ });
+ [self expectGetAccountInfo];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ FIRAuthCredential *emailCredential =
+ [FIREmailAuthProvider credentialWithEmail:kEmail password:kPassword];
+ [[FIRAuth auth] signInWithCredential:emailCredential completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ [self assertUser:user];
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ [self assertUser:[FIRAuth auth].currentUser];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testSignInWithEmailCredentialSuccess
+ @brief Tests the flow of a successfully @c signInWithCredential:completion: call with an
+ email-password credential using the deprecated FIREmailPasswordAuthProvider.
+ */
+- (void)testSignInWithEmailCredentialSuccessWithDepricatedProvider {
+ OCMExpect([_mockBackend verifyPassword:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRVerifyPasswordRequest *_Nullable request,
+ FIRVerifyPasswordResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.email, kEmail);
+ XCTAssertEqualObjects(request.password, kPassword);
+ XCTAssertTrue(request.returnSecureToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockVeriyPasswordResponse = OCMClassMock([FIRVerifyPasswordResponse class]);
+ [self stubTokensWithMockResponse:mockVeriyPasswordResponse];
+ callback(mockVeriyPasswordResponse, nil);
+ });
+ });
+ [self expectGetAccountInfo];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ FIRAuthCredential *emailCredential =
+ [FIREmailPasswordAuthProvider credentialWithEmail:kEmail password:kPassword];
+#pragma clang diagnostic pop
+ [[FIRAuth auth] signInWithCredential:emailCredential completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ [self assertUser:user];
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ [self assertUser:[FIRAuth auth].currentUser];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testSignInWithEmailCredentialFailure
+ @brief Tests the flow of a failed @c signInWithCredential:completion: call with an
+ email-password credential.
+ */
+- (void)testSignInWithEmailCredentialFailure {
+ OCMExpect([_mockBackend verifyPassword:[OCMArg any] callback:[OCMArg any]])
+ .andDispatchError2([FIRAuthErrorUtils userDisabledErrorWithMessage:nil]);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ FIRAuthCredential *emailCredential =
+ [FIREmailAuthProvider credentialWithEmail:kEmail password:kPassword];
+ [[FIRAuth auth] signInWithCredential:emailCredential completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(user);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeUserDisabled);
+ XCTAssertNotNil(error.userInfo[NSLocalizedDescriptionKey]);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ XCTAssertNil([FIRAuth auth].currentUser);
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testSignInWithEmailCredentialEmptyPassword
+ @brief Tests the flow of a failed @c signInWithCredential:completion: call with an
+ email-password credential using an empty password. This error occurs on the client side,
+ so there is no need to fake an RPC response.
+ */
+- (void)testSignInWithEmailCredentialEmptyPassword {
+ NSString *emptyString = @"";
+ [self expectGetAccountInfo];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ FIRAuthCredential *emailCredential =
+ [FIREmailAuthProvider credentialWithEmail:kEmail password:emptyString];
+ [[FIRAuth auth] signInWithCredential:emailCredential completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeWrongPassword);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+}
+
+/** @fn testSignInWithGoogleAccountExistsError
+ @brief Tests the flow of a failed @c signInWithCredential:completion: with a Google credential
+ where the backend returns a needs @needConfirmation equal to true. An
+ FIRAuthErrorCodeAccountExistsWithDifferentCredential error should be thrown.
+ */
+- (void)testSignInWithGoogleAccountExistsError {
+ OCMExpect([_mockBackend verifyAssertion:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRVerifyAssertionRequest *_Nullable request,
+ FIRVerifyAssertionResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.providerID, FIRGoogleAuthProviderID);
+ XCTAssertEqualObjects(request.providerIDToken, kGoogleIDToken);
+ XCTAssertEqualObjects(request.providerAccessToken, kGoogleAccessToken);
+ XCTAssertTrue(request.returnSecureToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockVeriyAssertionResponse = OCMClassMock([FIRVerifyAssertionResponse class]);
+ OCMStub([mockVeriyAssertionResponse needConfirmation]).andReturn(YES);
+ OCMStub([mockVeriyAssertionResponse email]).andReturn(kEmail);
+ [self stubTokensWithMockResponse:mockVeriyAssertionResponse];
+ callback(mockVeriyAssertionResponse, nil);
+ });
+ });
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ FIRAuthCredential *googleCredential =
+ [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken];
+ [[FIRAuth auth] signInWithCredential:googleCredential completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeAccountExistsWithDifferentCredential);
+ XCTAssertEqualObjects(error.userInfo[FIRAuthErrorUserInfoEmailKey], kEmail);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testSignInWithGoogleCredentialSuccess
+ @brief Tests the flow of a successful @c signInWithCredential:completion: call with an
+ Google Sign-In credential.
+ */
+- (void)testSignInWithGoogleCredentialSuccess {
+ OCMExpect([_mockBackend verifyAssertion:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRVerifyAssertionRequest *_Nullable request,
+ FIRVerifyAssertionResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.providerID, FIRGoogleAuthProviderID);
+ XCTAssertEqualObjects(request.providerIDToken, kGoogleIDToken);
+ XCTAssertEqualObjects(request.providerAccessToken, kGoogleAccessToken);
+ XCTAssertTrue(request.returnSecureToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockVeriyAssertionResponse = OCMClassMock([FIRVerifyAssertionResponse class]);
+ OCMStub([mockVeriyAssertionResponse federatedID]).andReturn(kGoogleID);
+ OCMStub([mockVeriyAssertionResponse providerID]).andReturn(FIRGoogleAuthProviderID);
+ OCMStub([mockVeriyAssertionResponse localID]).andReturn(kLocalID);
+ OCMStub([mockVeriyAssertionResponse displayName]).andReturn(kGoogleDisplayName);
+ [self stubTokensWithMockResponse:mockVeriyAssertionResponse];
+ callback(mockVeriyAssertionResponse, nil);
+ });
+ });
+ [self expectGetAccountInfoGoogle];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ FIRAuthCredential *googleCredential =
+ [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken];
+ [[FIRAuth auth] signInWithCredential:googleCredential completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ [self assertUserGoogle:user];
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ [self assertUserGoogle:[FIRAuth auth].currentUser];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testSignInAndRetrieveDataWithCredentialSuccess
+ @brief Tests the flow of a successful @c signInAndRetrieveDataWithCredential:completion: call
+ with an Google Sign-In credential.
+ */
+- (void)testSignInAndRetrieveDataWithCredentialSuccess {
+ OCMExpect([_mockBackend verifyAssertion:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRVerifyAssertionRequest *_Nullable request,
+ FIRVerifyAssertionResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.providerID, FIRGoogleAuthProviderID);
+ XCTAssertEqualObjects(request.providerIDToken, kGoogleIDToken);
+ XCTAssertEqualObjects(request.providerAccessToken, kGoogleAccessToken);
+ XCTAssertTrue(request.returnSecureToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockVeriyAssertionResponse = OCMClassMock([FIRVerifyAssertionResponse class]);
+ OCMStub([mockVeriyAssertionResponse federatedID]).andReturn(kGoogleID);
+ OCMStub([mockVeriyAssertionResponse providerID]).andReturn(FIRGoogleAuthProviderID);
+ OCMStub([mockVeriyAssertionResponse localID]).andReturn(kLocalID);
+ OCMStub([mockVeriyAssertionResponse displayName]).andReturn(kGoogleDisplayName);
+ OCMStub([mockVeriyAssertionResponse profile]).andReturn([[self class] googleProfile]);
+ OCMStub([mockVeriyAssertionResponse username]).andReturn(kDisplayName);
+ [self stubTokensWithMockResponse:mockVeriyAssertionResponse];
+ callback(mockVeriyAssertionResponse, nil);
+ });
+ });
+ [self expectGetAccountInfoGoogle];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ FIRAuthCredential *googleCredential =
+ [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken];
+ [[FIRAuth auth] signInAndRetrieveDataWithCredential:googleCredential
+ completion:^(FIRAuthDataResult *_Nullable authResult,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ [self assertUserGoogle:authResult.user];
+ XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]);
+ XCTAssertEqualObjects(authResult.additionalUserInfo.username, kDisplayName);
+ XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRGoogleAuthProviderID);
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ [self assertUserGoogle:[FIRAuth auth].currentUser];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testSignInWithGoogleCredentialFailure
+ @brief Tests the flow of a failed @c signInWithCredential:completion: call with an
+ Google Sign-In credential.
+ */
+- (void)testSignInWithGoogleCredentialFailure {
+ OCMExpect([_mockBackend verifyAssertion:[OCMArg any] callback:[OCMArg any]])
+ .andDispatchError2([FIRAuthErrorUtils emailAlreadyInUseErrorWithEmail:kGoogleEmail]);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ FIRAuthCredential *googleCredential =
+ [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken];
+ [[FIRAuth auth] signInWithCredential:googleCredential completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(user);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeEmailAlreadyInUse);
+ XCTAssertNotNil(error.userInfo[NSLocalizedDescriptionKey]);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ XCTAssertNil([FIRAuth auth].currentUser);
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testSignInAnonymouslySuccess
+ @brief Tests the flow of a successful @c signInAnonymously:completion: call.
+ */
+- (void)testSignInAnonymouslySuccess {
+ OCMExpect([_mockBackend signUpNewUser:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRSignUpNewUserRequest *_Nullable request,
+ FIRSignupNewUserCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertNil(request.email);
+ XCTAssertNil(request.password);
+ XCTAssertTrue(request.returnSecureToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockSignUpNewUserResponse = OCMClassMock([FIRSignUpNewUserResponse class]);
+ [self stubTokensWithMockResponse:mockSignUpNewUserResponse];
+ callback(mockSignUpNewUserResponse, nil);
+ });
+ });
+ [self expectGetAccountInfoAnonymous];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] signInAnonymouslyWithCompletion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ [self assertUserAnonymous:user];
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ [self assertUserAnonymous:[FIRAuth auth].currentUser];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testSignInAnonymouslyFailure
+ @brief Tests the flow of a failed @c signInAnonymously:completion: call.
+ */
+- (void)testSignInAnonymouslyFailure {
+ OCMExpect([_mockBackend signUpNewUser:[OCMArg any] callback:[OCMArg any]])
+ .andDispatchError2([FIRAuthErrorUtils operationNotAllowedErrorWithMessage:nil]);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] signInAnonymouslyWithCompletion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(user);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeOperationNotAllowed);
+ XCTAssertNotNil(error.userInfo[NSLocalizedDescriptionKey]);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ XCTAssertNil([FIRAuth auth].currentUser);
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testSignInWithCustomTokenSuccess
+ @brief Tests the flow of a successful @c signInWithCustomToken:completion: call.
+ */
+- (void)testSignInWithCustomTokenSuccess {
+ OCMExpect([_mockBackend verifyCustomToken:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRVerifyCustomTokenRequest *_Nullable request,
+ FIRVerifyCustomTokenResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.token, kCustomToken);
+ XCTAssertTrue(request.returnSecureToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockVeriyCustomTokenResponse = OCMClassMock([FIRVerifyCustomTokenResponse class]);
+ [self stubTokensWithMockResponse:mockVeriyCustomTokenResponse];
+ callback(mockVeriyCustomTokenResponse, nil);
+ });
+ });
+ [self expectGetAccountInfo];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] signInWithCustomToken:kCustomToken completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ [self assertUser:user];
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ [self assertUser:[FIRAuth auth].currentUser];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testSignInWithCustomTokenFailure
+ @brief Tests the flow of a failed @c signInWithCustomToken:completion: call.
+ */
+- (void)testSignInWithCustomTokenFailure {
+ OCMExpect([_mockBackend verifyCustomToken:[OCMArg any] callback:[OCMArg any]])
+ .andDispatchError2([FIRAuthErrorUtils invalidCustomTokenErrorWithMessage:nil]);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] signInWithCustomToken:kCustomToken completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(user);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidCustomToken);
+ XCTAssertNotNil(error.userInfo[NSLocalizedDescriptionKey]);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ XCTAssertNil([FIRAuth auth].currentUser);
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testCreateUserWithEmailPasswordSuccess
+ @brief Tests the flow of a successful @c createUserWithEmail:password:completion: call.
+ */
+- (void)testCreateUserWithEmailPasswordSuccess {
+ OCMExpect([_mockBackend signUpNewUser:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRSignUpNewUserRequest *_Nullable request,
+ FIRSignupNewUserCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.email, kEmail);
+ XCTAssertEqualObjects(request.password, kPassword);
+ XCTAssertTrue(request.returnSecureToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockSignUpNewUserResponse = OCMClassMock([FIRSignUpNewUserResponse class]);
+ [self stubTokensWithMockResponse:mockSignUpNewUserResponse];
+ callback(mockSignUpNewUserResponse, nil);
+ });
+ });
+ [self expectGetAccountInfo];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] createUserWithEmail:kEmail
+ password:kPassword
+ completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ [self assertUser:user];
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ [self assertUser:[FIRAuth auth].currentUser];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testCreateUserWithEmailPasswordFailure
+ @brief Tests the flow of a failed @c createUserWithEmail:password:completion: call.
+ */
+- (void)testCreateUserWithEmailPasswordFailure {
+ NSString *reason = @"Password shouldn't be a common word.";
+ OCMExpect([_mockBackend signUpNewUser:[OCMArg any] callback:[OCMArg any]])
+ .andDispatchError2([FIRAuthErrorUtils weakPasswordErrorWithServerResponseReason:reason]);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] createUserWithEmail:kEmail
+ password:kPassword
+ completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(user);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeWeakPassword);
+ XCTAssertNotNil(error.userInfo[NSLocalizedDescriptionKey]);
+ XCTAssertEqualObjects(error.userInfo[NSLocalizedFailureReasonErrorKey], reason);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ XCTAssertNil([FIRAuth auth].currentUser);
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testCreateUserEmptyPasswordFailure
+ @brief Tests the flow of a failed @c createUserWithEmail:password:completion: call due to an
+ empty password. This error occurs on the client side, so there is no need to fake an RPC
+ response.
+ */
+- (void)testCreateUserEmptyPasswordFailure {
+ [self expectGetAccountInfo];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] createUserWithEmail:kEmail
+ password:@""
+ completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeWeakPassword);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+}
+
+/** @fn testSendPasswordResetEmailSuccess
+ @brief Tests the flow of a successful @c sendPasswordResetWithEmail:completion: call.
+ */
+- (void)testSendPasswordResetEmailSuccess {
+ OCMExpect([_mockBackend getOOBConfirmationCode:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRGetOOBConfirmationCodeRequest *_Nullable request,
+ FIRGetOOBConfirmationCodeResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.email, kEmail);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ callback([[FIRGetOOBConfirmationCodeResponse alloc] init], nil);
+ });
+ });
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] sendPasswordResetWithEmail:kEmail completion:^(NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(error);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testSendPasswordResetEmailFailure
+ @brief Tests the flow of a failed @c sendPasswordResetWithEmail:completion: call.
+ */
+- (void)testSendPasswordResetEmailFailure {
+ OCMExpect([_mockBackend getOOBConfirmationCode:[OCMArg any] callback:[OCMArg any]])
+ .andDispatchError2([FIRAuthErrorUtils appNotAuthorizedError]);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] sendPasswordResetWithEmail:kEmail completion:^(NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeAppNotAuthorized);
+ XCTAssertNotNil(error.userInfo[NSLocalizedDescriptionKey]);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testSignOut
+ @brief Tests the @c signOut: method.
+ */
+- (void)testSignOut {
+ [self waitForSignIn];
+ // Verify signing out succeeds and clears the current user.
+ NSError *error;
+ XCTAssertTrue([[FIRAuth auth] signOut:&error]);
+ XCTAssertNil([FIRAuth auth].currentUser);
+}
+
+/** @fn testAuthStateChanges
+ @brief Tests @c addAuthStateDidChangeListener: and @c removeAuthStateDidChangeListener: methods.
+ */
+- (void)testAuthStateChanges {
+ // Set up listener.
+ __block XCTestExpectation *expectation;
+ __block BOOL shouldHaveUser;
+ FIRAuthStateDidChangeListenerBlock listener = ^(FIRAuth *auth, FIRUser *_Nullable user) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertEqual(auth, [FIRAuth auth]);
+ XCTAssertEqual(user, [FIRAuth auth].currentUser);
+ if (shouldHaveUser) {
+ XCTAssertNotNil(user);
+ } else {
+ XCTAssertNil(user);
+ }
+ // `expectation` being nil means the listener is not expected to be fired at this moment.
+ XCTAssertNotNil(expectation);
+ [expectation fulfill];
+ };
+ [[FIRAuth auth] signOut:NULL];
+ [self waitForTimeIntervel:kWaitInterval]; // Wait until dust settled from previous tests.
+
+ // Listener should fire immediately when attached.
+ expectation = [self expectationWithDescription:@"initial"];
+ shouldHaveUser = NO;
+ FIRAuthStateDidChangeListenerHandle handle =
+ [[FIRAuth auth] addAuthStateDidChangeListener:listener];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+
+ // Listener should fire for signing in.
+ expectation = [self expectationWithDescription:@"sign-in"];
+ shouldHaveUser = YES;
+ [self waitForSignIn];
+
+ // Listener should not fire for signing in again.
+ shouldHaveUser = YES;
+ [self waitForSignIn];
+ [self waitForTimeIntervel:kWaitInterval]; // make sure listener is not called
+
+ // Listener should fire for signing out.
+ expectation = [self expectationWithDescription:@"sign-out"];
+ shouldHaveUser = NO;
+ [[FIRAuth auth] signOut:NULL];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+
+ // Listener should no longer fire once detached.
+ expectation = nil;
+ [[FIRAuth auth] removeAuthStateDidChangeListener:handle];
+ [self waitForSignIn];
+ [self waitForTimeIntervel:kWaitInterval]; // make sure listener is no longer called
+}
+
+/** @fn testIDTokenChanges
+ @brief Tests @c addIDTokenDidChangeListener: and @c removeIDTokenDidChangeListener: methods.
+ */
+- (void)testIDTokenChanges {
+ // Set up listener.
+ __block XCTestExpectation *expectation;
+ __block BOOL shouldHaveUser;
+ FIRIDTokenDidChangeListenerBlock listener = ^(FIRAuth *auth, FIRUser *_Nullable user) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertEqual(auth, [FIRAuth auth]);
+ XCTAssertEqual(user, [FIRAuth auth].currentUser);
+ if (shouldHaveUser) {
+ XCTAssertNotNil(user);
+ } else {
+ XCTAssertNil(user);
+ }
+ // `expectation` being nil means the listener is not expected to be fired at this moment.
+ XCTAssertNotNil(expectation);
+ [expectation fulfill];
+ };
+ [[FIRAuth auth] signOut:NULL];
+ [self waitForTimeIntervel:kWaitInterval]; // Wait until dust settled from previous tests.
+
+ // Listener should fire immediately when attached.
+ expectation = [self expectationWithDescription:@"initial"];
+ shouldHaveUser = NO;
+ FIRIDTokenDidChangeListenerHandle handle =
+ [[FIRAuth auth] addIDTokenDidChangeListener:listener];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+
+ // Listener should fire for signing in.
+ expectation = [self expectationWithDescription:@"sign-in"];
+ shouldHaveUser = YES;
+ [self waitForSignIn];
+
+ // Listener should fire for signing in again as the same user.
+ expectation = [self expectationWithDescription:@"sign-in again"];
+ shouldHaveUser = YES;
+ [self waitForSignIn];
+
+ // Listener should fire for signing out.
+ expectation = [self expectationWithDescription:@"sign-out"];
+ shouldHaveUser = NO;
+ [[FIRAuth auth] signOut:NULL];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+
+ // Listener should no longer fire once detached.
+ expectation = nil;
+ [[FIRAuth auth] removeIDTokenDidChangeListener:handle];
+ [self waitForSignIn];
+ [self waitForTimeIntervel:kWaitInterval]; // make sure listener is no longer called
+}
+
+#pragma mark - Automatic Token Refresh Tests.
+
+/** @fn testAutomaticTokenRefresh
+ @brief Tests a successful flow to automatically refresh tokens for a signed in user.
+ */
+- (void)testAutomaticTokenRefresh {
+ [[FIRAuth auth] signOut:NULL];
+
+ // Enable auto refresh
+ [self enableAutoTokenRefresh];
+
+ // Sign in a user.
+ [self waitForSignIn];
+
+ // Set up expectation for secureToken RPC made by token refresh task.
+ [self mockSecureTokenResponseWithError:nil];
+
+ // Verify that the current user's access token is the "old" access token before automatic token
+ // refresh.
+ XCTAssertEqualObjects(kAccessToken, [FIRAuth auth].currentUser.rawAccessToken);
+
+ // Execute saved token refresh task.
+ XCTestExpectation *dispatchAfterExpectation =
+ [self expectationWithDescription:@"dispatchAfterExpectation"];
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ XCTAssertNotNil(_FIRAuthDispatcherCallback);
+ _FIRAuthDispatcherCallback();
+ [dispatchAfterExpectation fulfill];
+ });
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+
+ // Verify that current user's access token is the "new" access token provided in the mock secure
+ // token response during automatic token refresh.
+ XCTAssertEqualObjects(kNewAccessToken, [FIRAuth auth].currentUser.rawAccessToken);
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testAutomaticTokenRefreshInvalidTokenFailure
+ @brief Tests an unsuccessful flow to auto refresh tokens with an "invalid token" error.
+ This error should cause the user to be signed out.
+ */
+- (void)testAutomaticTokenRefreshInvalidTokenFailure {
+ [[FIRAuth auth] signOut:NULL];
+ // Enable auto refresh
+ [self enableAutoTokenRefresh];
+
+ // Sign in a user.
+ [self waitForSignIn];
+
+ // Set up expectation for secureToken RPC made by a failed attempt to refresh tokens.
+ [self mockSecureTokenResponseWithError:[FIRAuthErrorUtils invalidUserTokenErrorWithMessage:nil]];
+
+ // Verify that current user is still valid.
+ XCTAssertEqualObjects(kAccessToken, [FIRAuth auth].currentUser.rawAccessToken);
+
+ // Execute saved token refresh task.
+ XCTestExpectation *dispatchAfterExpectation =
+ [self expectationWithDescription:@"dispatchAfterExpectation"];
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ XCTAssertNotNil(_FIRAuthDispatcherCallback);
+ _FIRAuthDispatcherCallback();
+ [dispatchAfterExpectation fulfill];
+ });
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+
+ //Verify that the user is nil after failed attempt to refresh tokens caused signed out.
+ XCTAssertNil([FIRAuth auth].currentUser);
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testAutomaticTokenRefreshRetry
+ @brief Tests that a retry is attempted for a automatic token refresh task (which is not due to
+ invalid tokens). The initial attempt to refresh the access token fails, but the second
+ attempt is successful.
+ */
+- (void)testAutomaticTokenRefreshRetry {
+ [[FIRAuth auth] signOut:NULL];
+ // Enable auto refresh
+ [self enableAutoTokenRefresh];
+
+ // Sign in a user.
+ [self waitForSignIn];
+
+ // Set up expectation for secureToken RPC made by a failed attempt to refresh tokens.
+ [self mockSecureTokenResponseWithError:[NSError errorWithDomain:@"ERROR" code:-1 userInfo:nil]];
+
+ // Execute saved token refresh task.
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ XCTAssertNotNil(_FIRAuthDispatcherCallback);
+ _FIRAuthDispatcherCallback();
+ _FIRAuthDispatcherCallback = nil;
+ [expectation fulfill];
+ });
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+
+ // The old access token should still be the current user's access token and not the new access
+ // token (kNewAccessToken).
+ XCTAssertEqualObjects(kAccessToken, [FIRAuth auth].currentUser.rawAccessToken);
+
+ // Set up expectation for secureToken RPC made by a successful attempt to refresh tokens.
+ [self mockSecureTokenResponseWithError:nil];
+
+ // Execute saved token refresh task.
+ XCTestExpectation *dispatchAfterExpectation =
+ [self expectationWithDescription:@"dispatchAfterExpectation"];
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ XCTAssertNotNil(_FIRAuthDispatcherCallback);
+ _FIRAuthDispatcherCallback();
+ [dispatchAfterExpectation fulfill];
+ });
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+
+ // Verify that current user's access token is the "new" access token provided in the mock secure
+ // token response during automatic token refresh.
+ XCTAssertEqualObjects([FIRAuth auth].currentUser.rawAccessToken, kNewAccessToken);
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testAutomaticTokenRefreshInvalidTokenFailure
+ @brief Tests that app foreground notification triggers the scheduling of an automatic token
+ refresh task.
+ */
+- (void)testAutoRefreshAppForegroundedNotification {
+ [[FIRAuth auth] signOut:NULL];
+ // Enable auto refresh
+ [self enableAutoTokenRefresh];
+
+ // Sign in a user.
+ [self waitForSignIn];
+
+ // Post "UIApplicationDidBecomeActiveNotification" to trigger scheduling token refresh task.
+ [[NSNotificationCenter defaultCenter]
+ postNotificationName:UIApplicationDidBecomeActiveNotification object:nil];
+
+ // Verify that current user is still valid with old access token.
+ XCTAssertEqualObjects(kAccessToken, [FIRAuth auth].currentUser.rawAccessToken);
+
+ // Set up expectation for secureToken RPC made by a successful attempt to refresh tokens.
+ [self mockSecureTokenResponseWithError:nil];
+
+ // Execute saved token refresh task.
+ XCTestExpectation *dispatchAfterExpectation =
+ [self expectationWithDescription:@"dispatchAfterExpectation"];
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ XCTAssertNotNil(_FIRAuthDispatcherCallback);
+ _FIRAuthDispatcherCallback();
+ [dispatchAfterExpectation fulfill];
+ });
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ // Verify that current user is still valid with new access token.
+ XCTAssertEqualObjects(kNewAccessToken, [FIRAuth auth].currentUser.rawAccessToken);
+ OCMVerifyAll(_mockBackend);
+}
+
+#pragma mark - Helpers
+
+/** @fn mockSecureTokenResponseWithError:
+ @brief Set up expectation for secureToken RPC.
+ @param error The error that the mock should return if any.
+ */
+- (void)mockSecureTokenResponseWithError:(nullable NSError *)error {
+ // Set up expectation for secureToken RPC made by a successful attempt to refresh tokens.
+ XCTestExpectation *secureTokenResponseExpectation =
+ [self expectationWithDescription:@"secureTokenResponseExpectation"];
+ OCMExpect([_mockBackend secureToken:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRSecureTokenRequest *_Nullable request,
+ FIRSecureTokenResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.refreshToken, kRefreshToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ if (error) {
+ callback(nil, error);
+ [secureTokenResponseExpectation fulfill];
+ return;
+ }
+ id mockSecureTokenResponse = OCMClassMock([FIRSecureTokenResponse class]);
+ OCMStub([mockSecureTokenResponse accessToken]).andReturn(kNewAccessToken);
+ NSDate *futureDate =
+ [[NSDate date] dateByAddingTimeInterval:kTestTokenExpirationTimeInterval];
+ OCMStub([mockSecureTokenResponse approximateExpirationDate]).andReturn(futureDate);
+ callback(mockSecureTokenResponse, nil);
+ [secureTokenResponseExpectation fulfill];
+ });
+ });
+}
+
+/** @fn enableAutoTokenRefresh
+ @brief Enables automatic token refresh by invoking FIRAuth's implementation of FIRApp's
+ |getTokenWithImplementation|.
+ */
+- (void)enableAutoTokenRefresh {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"autoTokenRefreshcallback"];
+ [[FIRAuth auth].app getTokenForcingRefresh:NO withCallback:^(NSString *_Nullable token,
+ NSError *_Nullable error) {
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+}
+
+/** @fn app1
+ @brief Creates a Firebase app.
+ @return A @c FIRApp with some name.
+ */
+- (FIRApp *)app1 {
+ return [FIRApp appForAuthUnitTestsWithName:kFirebaseAppName1];
+}
+
+/** @fn app2
+ @brief Creates another Firebase app.
+ @return A @c FIRApp with some other name.
+ */
+- (FIRApp *)app2 {
+ return [FIRApp appForAuthUnitTestsWithName:kFirebaseAppName2];
+}
+
+/** @fn stubSecureTokensWithMockResponse
+ @brief Creates stubs on the mock response object with access and refresh tokens
+ @param mockResponse The mock response object.
+ */
+- (void)stubTokensWithMockResponse:(id)mockResponse {
+ OCMStub([mockResponse IDToken]).andReturn(kAccessToken);
+ OCMStub([mockResponse approximateExpirationDate])
+ .andReturn([NSDate dateWithTimeIntervalSinceNow:kAccessTokenTimeToLive]);
+ OCMStub([mockResponse refreshToken]).andReturn(kRefreshToken);
+}
+
+/** @fn expectGetAccountInfo
+ @brief Expects a GetAccountInfo request on the mock backend and calls back with fake account
+ data.
+ */
+- (void)expectGetAccountInfo {
+ OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRGetAccountInfoRequest *_Nullable request,
+ FIRGetAccountInfoResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.accessToken, kAccessToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+ OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kDisplayName);
+ OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
+ OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
+ id mockGetAccountInfoResponse = OCMClassMock([FIRGetAccountInfoResponse class]);
+ OCMStub([mockGetAccountInfoResponse users]).andReturn(@[ mockGetAccountInfoResponseUser ]);
+ callback(mockGetAccountInfoResponse, nil);
+ });
+ });
+}
+
+/** @fn assertUser
+ @brief Asserts the given FIRUser matching the fake data returned by @c expectGetAccountInfo.
+ @param user The user object to be verified.
+ */
+- (void)assertUser:(FIRUser *)user {
+ XCTAssertNotNil(user);
+ XCTAssertEqualObjects(user.uid, kLocalID);
+ XCTAssertEqualObjects(user.displayName, kDisplayName);
+ XCTAssertEqualObjects(user.email, kEmail);
+ XCTAssertFalse(user.anonymous);
+ XCTAssertEqual(user.providerData.count, 0u);
+}
+
+/** @fn expectGetAccountInfoGoogle
+ @brief Expects a GetAccountInfo request on the mock backend and calls back with fake account
+ data for a Google Sign-In user.
+ */
+- (void)expectGetAccountInfoGoogle {
+ OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRGetAccountInfoRequest *_Nullable request,
+ FIRGetAccountInfoResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.accessToken, kAccessToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockGoogleUserInfo = OCMClassMock([FIRGetAccountInfoResponseProviderUserInfo class]);
+ OCMStub([mockGoogleUserInfo providerID]).andReturn(FIRGoogleAuthProviderID);
+ OCMStub([mockGoogleUserInfo displayName]).andReturn(kGoogleDisplayName);
+ OCMStub([mockGoogleUserInfo federatedID]).andReturn(kGoogleID);
+ OCMStub([mockGoogleUserInfo email]).andReturn(kGoogleEmail);
+ id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+ OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kDisplayName);
+ OCMStub([mockGetAccountInfoResponseUser providerUserInfo])
+ .andReturn((@[ mockGoogleUserInfo ]));
+ id mockGetAccountInfoResponse = OCMClassMock([FIRGetAccountInfoResponse class]);
+ OCMStub([mockGetAccountInfoResponse users]).andReturn(@[ mockGetAccountInfoResponseUser ]);
+ callback(mockGetAccountInfoResponse, nil);
+ });
+ });
+}
+
+/** @fn assertUserGoogle
+ @brief Asserts the given FIRUser matching the fake data returned by
+ @c expectGetAccountInfoGoogle.
+ @param user The user object to be verified.
+ */
+- (void)assertUserGoogle:(FIRUser *)user {
+ XCTAssertNotNil(user);
+ XCTAssertEqualObjects(user.uid, kLocalID);
+ XCTAssertEqualObjects(user.displayName, kDisplayName);
+ XCTAssertEqual(user.providerData.count, 1u);
+ id<FIRUserInfo> googleUserInfo = user.providerData[0];
+ XCTAssertEqualObjects(googleUserInfo.providerID, FIRGoogleAuthProviderID);
+ XCTAssertEqualObjects(googleUserInfo.uid, kGoogleID);
+ XCTAssertEqualObjects(googleUserInfo.displayName, kGoogleDisplayName);
+ XCTAssertEqualObjects(googleUserInfo.email, kGoogleEmail);
+}
+
+/** @fn expectGetAccountInfoAnonymous
+ @brief Expects a GetAccountInfo request on the mock backend and calls back with fake anonymous
+ account data.
+ */
+- (void)expectGetAccountInfoAnonymous {
+ OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRGetAccountInfoRequest *_Nullable request,
+ FIRGetAccountInfoResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.accessToken, kAccessToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+ id mockGetAccountInfoResponse = OCMClassMock([FIRGetAccountInfoResponse class]);
+ OCMStub([mockGetAccountInfoResponse users]).andReturn(@[ mockGetAccountInfoResponseUser ]);
+ callback(mockGetAccountInfoResponse, nil);
+ });
+ });
+}
+
+/** @fn assertUserAnonymous
+ @brief Asserts the given FIRUser matching the fake data returned by
+ @c expectGetAccountInfoAnonymous.
+ @param user The user object to be verified.
+ */
+- (void)assertUserAnonymous:(FIRUser *)user {
+ XCTAssertNotNil(user);
+ XCTAssertEqualObjects(user.uid, kLocalID);
+ XCTAssertNil(user.displayName);
+ XCTAssertTrue(user.anonymous);
+ XCTAssertEqual(user.providerData.count, 0u);
+}
+
+/** @fn waitForSignIn
+ @brief Signs in a user to prepare for tests.
+ @remarks This method also waits for all other pending @c XCTestExpectation instances.
+ */
+- (void)waitForSignIn {
+ OCMExpect([_mockBackend verifyPassword:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRVerifyPasswordRequest *_Nullable request,
+ FIRVerifyPasswordResponseCallback callback) {
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockVeriyPasswordResponse = OCMClassMock([FIRVerifyPasswordResponse class]);
+ [self stubTokensWithMockResponse:mockVeriyPasswordResponse];
+ callback(mockVeriyPasswordResponse, nil);
+ });
+ });
+ [self expectGetAccountInfo];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signInWithEmail:kEmail password:kPassword completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+ XCTAssertNotNil([FIRAuth auth].currentUser);
+}
+
+/** @fn waitForTimeInterval:
+ @brief Wait for a particular time interval.
+ @remarks This method also waits for all other pending @c XCTestExpectation instances.
+ */
+- (void)waitForTimeIntervel:(NSTimeInterval)timeInterval {
+ static dispatch_queue_t queue;
+ static dispatch_once_t onceToken;
+ XCTestExpectation *expectation = [self expectationWithDescription:@"waitForTimeIntervel:"];
+ dispatch_once(&onceToken, ^{
+ queue = dispatch_queue_create("com.google.FIRAuthUnitTests.waitForTimeIntervel", NULL);
+ });
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeInterval * NSEC_PER_SEC), queue, ^() {
+ [expectation fulfill];
+ });
+ [self waitForExpectationsWithTimeout:timeInterval + kExpectationTimeout handler:nil];
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRAuthUserDefaultsStorageTests.m b/Example/Auth/Tests/FIRAuthUserDefaultsStorageTests.m
new file mode 100644
index 0000000..07493d5
--- /dev/null
+++ b/Example/Auth/Tests/FIRAuthUserDefaultsStorageTests.m
@@ -0,0 +1,155 @@
+/*
+ * 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 "FIRAuthUserDefaultsStorage.h"
+
+#if FIRAUTH_USER_DEFAULTS_STORAGE_AVAILABLE
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** @var kKey
+ @brief The key used in tests.
+ */
+static NSString *const kKey = @"ACCOUNT";
+
+/** @var kService
+ @brief The keychain service used in tests.
+ */
+static NSString *const kService = @"SERVICE";
+
+/** @var kOtherService
+ @brief Another keychain service used in tests.
+ */
+static NSString *const kOtherService = @"OTHER_SERVICE";
+
+/** @var kData
+ @brief A piece of keychain data used in tests.
+ */
+static NSString *const kData = @"DATA";
+
+/** @var kOtherData
+ @brief Another piece of keychain data used in tests.
+ */
+static NSString *const kOtherData = @"OTHER_DATA";
+
+/** @fn dataFromString
+ @brief Converts a NSString to NSData.
+ @param string The NSString to be converted from.
+ @return The NSData being the conversion result.
+ */
+static NSData *dataFromString(NSString *string) {
+ return [string dataUsingEncoding:NSUTF8StringEncoding];
+}
+
+/** @fn fakeError
+ @brief Creates a fake error object.
+ @return a non-nil NSError instance.
+ */
+static NSError *fakeError() {
+ return [NSError errorWithDomain:@"ERROR" code:-1 userInfo:nil];
+}
+
+/** @class FIRAuthUserDefaultsStorageTests
+ @brief Tests for @c FIRAuthUserDefaultsStorage .
+ */
+@interface FIRAuthUserDefaultsStorageTests : XCTestCase
+@end
+
+@implementation FIRAuthUserDefaultsStorageTests {
+ /** @var _storage
+ @brief The @c FIRAuthUserDefaultsStorage object under test.
+ */
+ FIRAuthUserDefaultsStorage *_storage;
+}
+
+- (void)setUp {
+ [super setUp];
+ _storage = [[FIRAuthUserDefaultsStorage alloc] initWithService:kService];
+ [_storage clear];
+}
+
+/** @fn testReadNonexisting
+ @brief Tests reading non-existing storage item.
+ */
+- (void)testReadNonExisting {
+ NSError *error = fakeError();
+ XCTAssertNil([_storage dataForKey:kKey error:&error]);
+ XCTAssertNil(error);
+}
+
+/** @fn testWriteRead
+ @brief Tests writing and reading a storage item.
+ */
+- (void)testWriteRead {
+ XCTAssertTrue([_storage setData:dataFromString(kData) forKey:kKey error:NULL]);
+ NSError *error = fakeError();
+ XCTAssertEqualObjects([_storage dataForKey:kKey error:&error], dataFromString(kData));
+ XCTAssertNil(error);
+}
+
+/** @fn testOverwrite
+ @brief Tests overwriting a storage item.
+ */
+- (void)testOverwrite {
+ XCTAssertTrue([_storage setData:dataFromString(kData) forKey:kKey error:NULL]);
+ XCTAssertTrue([_storage setData:dataFromString(kOtherData) forKey:kKey error:NULL]);
+ NSError *error = fakeError();
+ XCTAssertEqualObjects([_storage dataForKey:kKey error:&error], dataFromString(kOtherData));
+ XCTAssertNil(error);
+}
+
+/** @fn testRemove
+ @brief Tests removing a storage item.
+ */
+- (void)testRemove {
+ XCTAssertTrue([_storage setData:dataFromString(kData) forKey:kKey error:NULL]);
+ XCTAssertTrue([_storage removeDataForKey:kKey error:NULL]);
+ NSError *error = fakeError();
+ XCTAssertNil([_storage dataForKey:kKey error:&error]);
+ XCTAssertNil(error);
+}
+
+/** @fn testServices
+ @brief Tests storage items belonging to different services doesn't affect each other.
+ */
+- (void)testServices {
+ XCTAssertTrue([_storage setData:dataFromString(kData) forKey:kKey error:NULL]);
+ _storage = [[FIRAuthUserDefaultsStorage alloc] initWithService:kOtherService];
+ NSError *error = fakeError();
+ XCTAssertNil([_storage dataForKey:kKey error:&error]);
+ XCTAssertNil(error);
+}
+
+/** @fn testStandardUserDefaults
+ @brief Tests standard user defaults are not affected by FIRAuthUserDefaultsStorage operations,
+ */
+- (void)testStandardUserDefaults {
+ NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
+ NSUInteger count =
+ [userDefaults persistentDomainForName:[[NSBundle mainBundle] bundleIdentifier]].count;
+ XCTAssertTrue([_storage setData:dataFromString(kData) forKey:kKey error:NULL]);
+ XCTAssertNil([userDefaults dataForKey:kKey]);
+ XCTAssertEqual([userDefaults persistentDomainForName:[[NSBundle mainBundle] bundleIdentifier]]
+ .count, count);
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
+
+#endif // FIRAUTH_USER_DEFAULTS_STORAGE_AVAILABLE
diff --git a/Example/Auth/Tests/FIRCreateAuthURIRequestTests.m b/Example/Auth/Tests/FIRCreateAuthURIRequestTests.m
new file mode 100644
index 0000000..409c232
--- /dev/null
+++ b/Example/Auth/Tests/FIRCreateAuthURIRequestTests.m
@@ -0,0 +1,103 @@
+/*
+ * 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 "FIRAuthErrors.h"
+#import "FIRAuthBackend.h"
+#import "FIRCreateAuthURIRequest.h"
+#import "FIRCreateAuthURIResponse.h"
+#import "FIRFakeBackendRPCIssuer.h"
+
+/** @var kTestAPIKey
+ @brief Fake API key used for testing.
+ */
+static NSString *const kTestAPIKey = @"APIKey";
+
+/** @var kTestAuthUri
+ @brief The test value of the "authURI" property in the json response.
+ */
+static NSString *const kTestAuthUri = @"AuthURI";
+
+/** @var kTestIdentifier
+ @brief Fake identifier key used for testing.
+ */
+static NSString *const kTestIdentifier = @"Identifier";
+
+/** @var kContinueURITestKey
+ @brief The key for the "continueUri" value in the request.
+ */
+static NSString *const kContinueURITestKey = @"continueUri";
+
+/** @var kTestContinueURI
+ @brief Fake Continue URI key used for testing.
+ */
+static NSString *const kTestContinueURI = @"ContinueUri";
+
+/** @var kExpectedAPIURL
+ @brief The expected URL for the test calls.
+ */
+static NSString *const kExpectedAPIURL =
+ @"https://www.googleapis.com/identitytoolkit/v3/relyingparty/createAuthUri?key=APIKey";
+
+/** @class FIRCreateAuthURIRequestTests
+ @brief Tests for @c CreateAuthURIRequest.
+ */
+@interface FIRCreateAuthURIRequestTests : XCTestCase
+@end
+@implementation FIRCreateAuthURIRequestTests {
+ /** @var _RPCIssuer
+ @brief This backend RPC issuer is used to fake network responses for each test in the suite.
+ In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it.
+ */
+ FIRFakeBackendRPCIssuer *_RPCIssuer;
+}
+
+- (void)setUp {
+ [super setUp];
+ FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init];
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer];
+ _RPCIssuer = RPCIssuer;
+}
+
+- (void)tearDown {
+ _RPCIssuer = nil;
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
+ [super tearDown];
+}
+
+/** @fn testCreateAuthUriRequest
+ @brief Tests the encoding of an create auth URI request.
+ */
+- (void)testEmailVerificationRequest {
+ FIRCreateAuthURIRequest *request =
+ [[FIRCreateAuthURIRequest alloc]initWithIdentifier:kTestIdentifier
+ continueURI:kTestContinueURI
+ APIKey:kTestAPIKey];
+
+ [FIRAuthBackend createAuthURI:request
+ callback:^(FIRCreateAuthURIResponse *_Nullable response,
+ NSError *_Nullable error) {
+ }];
+
+ XCTAssertEqualObjects(_RPCIssuer.requestURL.absoluteString, kExpectedAPIURL);
+ XCTAssertNotNil(_RPCIssuer.decodedRequest);
+ XCTAssert([_RPCIssuer.decodedRequest isKindOfClass:[NSDictionary class]]);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kContinueURITestKey], kTestContinueURI);
+}
+
+
+@end
diff --git a/Example/Auth/Tests/FIRCreateAuthURIResponseTests.m b/Example/Auth/Tests/FIRCreateAuthURIResponseTests.m
new file mode 100644
index 0000000..11cab9d
--- /dev/null
+++ b/Example/Auth/Tests/FIRCreateAuthURIResponseTests.m
@@ -0,0 +1,205 @@
+/*
+ * 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 "FIRAuthErrors.h"
+#import "FIRAuthBackend.h"
+#import "FIRCreateAuthURIRequest.h"
+#import "FIRCreateAuthURIResponse.h"
+#import "FIRFakeBackendRPCIssuer.h"
+
+/** @var kTestAPIKey
+ @brief Fake API key used for testing.
+ */
+static NSString *const kTestAPIKey = @"APIKey";
+
+/** @var kAuthUriKey
+ @brief The name of the "authURI" property in the json response.
+ */
+static NSString *const kAuthUriKey = @"authUri";
+
+/** @var kTestAuthUri
+ @brief The test value of the "authURI" property in the json response.
+ */
+static NSString *const kTestAuthUri = @"AuthURI";
+
+/** @var kTestIdentifier
+ @brief Fake identifier key used for testing.
+ */
+static NSString *const kTestIdentifier = @"Identifier";
+
+/** @var kTestContinueURI
+ @brief Fake Continue URI key used for testing.
+ */
+static NSString *const kTestContinueURI = @"ContinueUri";
+
+/** @var kMissingContinueURIErrorMessage
+ @brief The error returned by the server if continue Uri is invalid.
+ */
+static NSString *const kMissingContinueURIErrorMessage = @"MISSING_CONTINUE_URI:";
+
+/** @var kInvalidEmailErrorMessage
+ @brief The error returned by the server if the email is invalid.
+ */
+static NSString *const kInvalidIdentifierErrorMessage = @"INVALID_IDENTIFIER :";
+
+/** @var kInvalidEmailErrorMessage
+ @brief The error returned by the server if the email is invalid.
+ */
+static NSString *const kInvalidEmailErrorMessage = @"INVALID_EMAIL:";
+
+/** @class CreateAuthURIResponseTests
+ @brief Tests for @c FIRCreateAuthURIResponse.
+ */
+@interface FIRCreateAuthURIResponseTests : XCTestCase
+@end
+@implementation FIRCreateAuthURIResponseTests{
+ /** @var _RPCIssuer
+ @brief This backend RPC issuer is used to fake network responses for each test in the suite.
+ In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it.
+ */
+ FIRFakeBackendRPCIssuer *_RPCIssuer;
+}
+
+- (void)setUp {
+ [super setUp];
+ FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init];
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer];
+ _RPCIssuer = RPCIssuer;
+}
+
+- (void)tearDown {
+ _RPCIssuer = nil;
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
+ [super tearDown];
+}
+
+/** @fn testMissingContinueURIError
+ @brief This test checks for invalid continue URI in the response.
+ */
+- (void)testMissingContinueURIError {
+ FIRCreateAuthURIRequest *request =
+ [[FIRCreateAuthURIRequest alloc]initWithIdentifier:kTestIdentifier
+ continueURI:kTestContinueURI
+ APIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRCreateAuthURIResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend createAuthURI:request
+ callback:^(FIRCreateAuthURIResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kMissingContinueURIErrorMessage];
+
+ XCTAssert(callbackInvoked);
+ XCTAssertNotNil(RPCError);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeInternalError);
+ XCTAssertNil(RPCResponse);
+}
+
+/** @fn testInvalidIdentifierError
+ @brief This test checks for the INVALID_IDENTIFIER error message from the backend.
+ */
+- (void)testInvalidIdentifierError {
+ FIRCreateAuthURIRequest *request =
+ [[FIRCreateAuthURIRequest alloc]initWithIdentifier:kTestIdentifier
+ continueURI:kTestContinueURI
+ APIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRCreateAuthURIResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend createAuthURI:request
+ callback:^(FIRCreateAuthURIResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kInvalidIdentifierErrorMessage];
+
+ XCTAssert(callbackInvoked);
+ XCTAssertNotNil(RPCError);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeInvalidEmail);
+ XCTAssertNil(RPCResponse);
+}
+
+/** @fn testInvalidEmailError
+ @brief This test checks for INVALID_EMAIL error message from the backend.
+ */
+- (void)testInvalidEmailError {
+ FIRCreateAuthURIRequest *request =
+ [[FIRCreateAuthURIRequest alloc]initWithIdentifier:kTestIdentifier
+ continueURI:kTestContinueURI
+ APIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRCreateAuthURIResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend createAuthURI:request
+ callback:^(FIRCreateAuthURIResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kInvalidEmailErrorMessage];
+
+ XCTAssert(callbackInvoked);
+ XCTAssertNotNil(RPCError);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeInvalidEmail);
+ XCTAssertNil(RPCResponse);
+}
+
+/** @fn testSuccessfulCreateAuthURI
+ @brief This test checks for invalid email identifier error.
+ */
+- (void)testSuccessfulCreateAuthURIResponse {
+ FIRCreateAuthURIRequest *request =
+ [[FIRCreateAuthURIRequest alloc]initWithIdentifier:kTestIdentifier
+ continueURI:kTestContinueURI
+ APIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRCreateAuthURIResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend createAuthURI:request
+ callback:^(FIRCreateAuthURIResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithJSON:@{
+ kAuthUriKey : kTestAuthUri
+ }];
+
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCError);
+ XCTAssertNotNil(RPCResponse);
+ XCTAssertEqualObjects(RPCResponse.authURI, kTestAuthUri);
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRDeleteAccountRequestTests.m b/Example/Auth/Tests/FIRDeleteAccountRequestTests.m
new file mode 100644
index 0000000..05f1d47
--- /dev/null
+++ b/Example/Auth/Tests/FIRDeleteAccountRequestTests.m
@@ -0,0 +1,98 @@
+/*
+ * 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 "FIRAuthErrors.h"
+#import "FIRAuthBackend.h"
+#import "FIRDeleteAccountRequest.h"
+#import "FIRDeleteAccountResponse.h"
+#import "FIRFakeBackendRPCIssuer.h"
+
+/** @var kTestAPIKey
+ @brief Fake API key used for testing.
+ */
+static NSString *const kTestAPIKey = @"APIKey";
+
+/** @var kLocalID
+ @brief Fake LocalID used for testing.
+ */
+static NSString *const kLocalID = @"LocalID";
+
+/** @var kLocalIDKey
+ @brief The name of the "localID" property in the request.
+ */
+static NSString *const kLocalIDKey = @"localId";
+
+/** @var kAccessToken
+ @brief The name of the "AccessToken" property in the request.
+ */
+static NSString *const kAccessToken = @"AccessToken";
+
+/** @var kExpectedAPIURL
+ @brief The expected URL for test calls.
+ */
+static NSString *const kExpectedAPIURL =
+ @"https://www.googleapis.com/identitytoolkit/v3/relyingparty/deleteAccount?key=APIKey";
+
+/** @class FIRDeleteUserRequestTests
+ @brief Tests for @c FIRDeleteAccountRequest.
+ */
+@interface FIRDeleteAccountRequestTests : XCTestCase
+@end
+@implementation FIRDeleteAccountRequestTests {
+ /** @var _RPCIssuer
+ @brief This backend RPC issuer is used to fake network responses for each test in the suite.
+ In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it.
+ */
+ FIRFakeBackendRPCIssuer *_RPCIssuer;
+}
+
+- (void)setUp {
+ [super setUp];
+ FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init];
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer];
+ _RPCIssuer = RPCIssuer;
+}
+
+- (void)tearDown {
+ _RPCIssuer = nil;
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
+ [super tearDown];
+}
+
+/** @fn testDeleteAccountRequest
+ @brief Tests the delete account request.
+ */
+- (void)testDeleteAccountRequest {
+
+ FIRDeleteAccountRequest *request = [[FIRDeleteAccountRequest alloc] initWithAPIKey:kTestAPIKey
+ localID:kLocalID
+ accessToken:kAccessToken];
+ __block BOOL callbackInvoked;
+ __block NSError *RPCError;
+ [FIRAuthBackend deleteAccount:request
+ callback:^(NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCError = error;
+ }];
+
+ XCTAssertEqualObjects(_RPCIssuer.requestURL.absoluteString, kExpectedAPIURL);
+ XCTAssertNotNil(_RPCIssuer.decodedRequest);
+ XCTAssertNotNil(_RPCIssuer.decodedRequest[kLocalIDKey]);
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRDeleteAccountResponseTests.m b/Example/Auth/Tests/FIRDeleteAccountResponseTests.m
new file mode 100644
index 0000000..f75735e
--- /dev/null
+++ b/Example/Auth/Tests/FIRDeleteAccountResponseTests.m
@@ -0,0 +1,172 @@
+/*
+ * 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 "FIRAuthErrors.h"
+#import "FIRAuthBackend.h"
+#import "FIRDeleteAccountRequest.h"
+#import "FIRDeleteAccountResponse.h"
+#import "FIRFakeBackendRPCIssuer.h"
+
+/** @var kTestAPIKey
+ @brief Fake API key used for testing.
+ */
+static NSString *const kTestAPIKey = @"APIKey";
+
+/** @var kLocalID
+ @brief Fake LocalID used for testing.
+ */
+static NSString *const kLocalID = @"LocalID";
+
+/** @var kAccessToken
+ @brief Fake AccessToken used for testing.
+ */
+static NSString *const kAccessToken = @"AccessToken";
+
+/** @var kUserDisabledErrorMessage
+ @brief The error returned by the server if the user account is diabled.
+ */
+static NSString *const kUserDisabledErrorMessage = @"USER_DISABLED";
+
+/** @var kinvalidUserTokenErrorMessage
+ @brief This is the error message the server responds with if user's saved auth credential is
+ invalid, and the user needs to sign in again.
+ */
+static NSString *const kinvalidUserTokenErrorMessage = @"INVALID_ID_TOKEN:";
+
+/** @var kCredentialTooOldErrorMessage
+ @brief This is the error message the server responds with if account change is attempted 5
+ minutes after signing in.
+ */
+static NSString *const kCredentialTooOldErrorMessage = @"CREDENTIAL_TOO_OLD_LOGIN_AGAIN:";
+
+/** @class FIRDeleteUserResponseTests
+ @brief Tests for @c FIRDeleteAccountResponse.
+ */
+@interface FIRDeleteAccountResponseTests : XCTestCase
+@end
+@implementation FIRDeleteAccountResponseTests {
+ /** @var _RPCIssuer
+ @brief This backend RPC issuer is used to fake network responses for each test in the suite.
+ In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it.
+ */
+ FIRFakeBackendRPCIssuer *_RPCIssuer;
+}
+
+- (void)setUp {
+ [super setUp];
+ FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init];
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer];
+ _RPCIssuer = RPCIssuer;
+}
+
+- (void)tearDown {
+ _RPCIssuer = nil;
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
+ [super tearDown];
+}
+
+/** @fn testUserDisabledError
+ @brief This test simulates the occurrence of a @c userDisabled error.
+ */
+- (void)testUserDisabledError {
+ FIRDeleteAccountRequest *request = [[FIRDeleteAccountRequest alloc] initWithAPIKey:kTestAPIKey
+ localID:kLocalID
+ accessToken:kAccessToken];
+
+ __block BOOL callbackInvoked;
+ __block NSError *RPCError;
+ [FIRAuthBackend deleteAccount:request
+ callback:^(NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kUserDisabledErrorMessage];
+
+ XCTAssert(callbackInvoked);
+ XCTAssertNotNil(RPCError);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeUserDisabled);
+}
+
+/** @fn testinvalidUserTokenError
+ @brief This test simulates the occurrence of a @c invalidUserToken error.
+ */
+- (void)testinvalidUserTokenError {
+ FIRDeleteAccountRequest *request = [[FIRDeleteAccountRequest alloc] initWithAPIKey:kTestAPIKey
+ localID:kLocalID
+ accessToken:kAccessToken];
+
+ __block BOOL callbackInvoked;
+ __block NSError *RPCError;
+ [FIRAuthBackend deleteAccount:request
+ callback:^(NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kinvalidUserTokenErrorMessage];
+
+ XCTAssert(callbackInvoked);
+ XCTAssertNotNil(RPCError);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeInvalidUserToken);
+}
+
+/** @fn testrequiredRecentLoginError
+ @brief This test simulates the occurrence of a @c credentialTooOld error.
+ */
+- (void)testrequiredRecentLoginError {
+ FIRDeleteAccountRequest *request = [[FIRDeleteAccountRequest alloc] initWithAPIKey:kTestAPIKey
+ localID:kLocalID
+ accessToken:kAccessToken];
+ __block BOOL callbackInvoked;
+ __block NSError *RPCError;
+ [FIRAuthBackend deleteAccount:request
+ callback:^(NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kCredentialTooOldErrorMessage];
+
+ XCTAssert(callbackInvoked);
+ XCTAssertNotNil(RPCError);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeRequiresRecentLogin);
+}
+
+/** @fn testSuccessfulDeleteAccount
+ @brief This test simulates a completed succesful deleteAccount operation.
+ */
+- (void)testSuccessfulDeleteAccountResponse {
+ FIRDeleteAccountRequest *request = [[FIRDeleteAccountRequest alloc] initWithAPIKey:kTestAPIKey
+ localID:kLocalID
+ accessToken:kAccessToken];
+ __block BOOL callbackInvoked;
+ __block NSError *RPCError;
+ [FIRAuthBackend deleteAccount:request
+ callback:^(NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithJSON:@{}];
+
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCError);
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRFakeBackendRPCIssuer.h b/Example/Auth/Tests/FIRFakeBackendRPCIssuer.h
new file mode 100644
index 0000000..d192cda
--- /dev/null
+++ b/Example/Auth/Tests/FIRFakeBackendRPCIssuer.h
@@ -0,0 +1,100 @@
+/*
+ * 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 "FIRAuthBackend.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** @class FIRFakeBackendRPCIssuer
+ @brief An implementation of @c FIRAuthBackendRPCIssuer which is used to test backend request,
+ response, and glue logic.
+ */
+@interface FIRFakeBackendRPCIssuer : NSObject <FIRAuthBackendRPCIssuer>
+
+/** @property requestURL
+ @brief The URL which was requested.
+ */
+@property(nonatomic, readonly) NSURL *requestURL;
+
+/** @property requestData
+ @brief The raw data in the POST body.
+ */
+@property(nonatomic, readonly) NSData *requestData;
+
+/** @property decodedRequest
+ @brief The raw data in the POST body decoded as JSON.
+ */
+@property(nonatomic, readonly) NSDictionary *decodedRequest;
+
+/** @property contentType
+ @brief The value of the content type HTTP header in the request.
+ */
+@property(nonatomic, readonly) NSString *contentType;
+
+/** @fn respondWithData:error:
+ @brief Responds to a pending RPC request with data and an error.
+ @remarks This is useful for simulating an error response with bogus data or unexpected data
+ (like unexpectedly receiving an HTML body.)
+ @param data The data to return as the body of an HTTP response.
+ @param error The simulated error to return from GTM.
+ */
+- (void)respondWithData:(nullable NSData *)data error:(nullable NSError *)error;
+
+/** @fn respondWithJSON:error:
+ @brief Responds to a pending RPC request with JSON and an error.
+ @remarks This is useful for simulating an error response with error JSON.
+ @param JSON The JSON to return.
+ @param error The simulated error to return from GTM.
+ */
+- (NSData *)respondWithJSON:(nullable NSDictionary *)JSON error:(nullable NSError *)error;
+
+/** @fn respondWithJSONError:
+ @brief Responds to a pending RPC request with a JSON server error.
+ @param JSON A dictionary which should be a server error encoded as JSON for fake response.
+ */
+- (NSData *)respondWithJSONError:(NSDictionary *)JSON;
+
+/** @fn respondWithError:
+ @brief Responds to a pending RPC request with an error. This is useful for simulating things
+ like a network timeout or unreachable host.
+ @param error The simulated error to return from GTM.
+ */
+- (NSData *)respondWithError:(NSError *)error;
+
+/** @fn respondWithServerErrorMessage:error:
+ @brief Responds to a pending RPC request with a server error message.
+ @param errorMessage The simulated error message to return from the server.
+ @param error The simulated error to return from GTM.
+ */
+- (NSData *)respondWithServerErrorMessage:(NSString *)errorMessage error:(NSError *)error;
+
+/** @fn respondWithServerErrorMessage:
+ @brief Responds to a pending RPC request with a server error message.
+ @param errorMessage The simulated error message to return from the server.
+ */
+- (NSData *)respondWithServerErrorMessage:(NSString *)errorMessage;
+
+/** @fn respondWithJSON:
+ @brief Responds to a pending RPC request with JSON.
+ @param JSON A dictionary which should be encoded as JSON for a fake response.
+ */
+- (NSData *)respondWithJSON:(NSDictionary *)JSON;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Example/Auth/Tests/FIRFakeBackendRPCIssuer.m b/Example/Auth/Tests/FIRFakeBackendRPCIssuer.m
new file mode 100644
index 0000000..93589e7
--- /dev/null
+++ b/Example/Auth/Tests/FIRFakeBackendRPCIssuer.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 "FIRFakeBackendRPCIssuer.h"
+
+/** @var kFakeErrorDomain
+ @brief Fake error domain used for testing.
+ */
+static NSString *const kFakeErrorDomain = @"fake domain";
+
+@implementation FIRFakeBackendRPCIssuer {
+ /** @var _handler
+ @brief A block we must invoke when @c respondWithError or @c respondWithJSON are called.
+ */
+ FIRAuthBackendRPCIssuerCompletionHandler _handler;
+}
+
+- (void)asyncPostToURL:(NSURL *)URL
+ body:(NSData *)body
+ contentType:(NSString *)contentType
+ completionHandler:(FIRAuthBackendRPCIssuerCompletionHandler)handler {
+ _requestURL = [URL copy];
+ _requestData = body;
+ NSDictionary *JSON = [NSJSONSerialization JSONObjectWithData:body options:0 error:nil];
+ _decodedRequest = JSON;
+ _contentType = contentType;
+ _handler = handler;
+}
+
+- (void)respondWithData:(NSData *)data error:(NSError *)error {
+ NSAssert(_handler, @"There is no pending RPC request.");
+ NSAssert(data || error, @"At least one of: data or error should be been non-nil.");
+ FIRAuthBackendRPCIssuerCompletionHandler handler = _handler;
+ _handler = nil;
+ handler(data, error);
+}
+
+- (NSData *)respondWithServerErrorMessage:(NSString *)errorMessage error:(NSError *)error {
+ return [self respondWithJSON:@{ @"error" : @{ @"message" : errorMessage } } error:error];
+}
+
+- (NSData *)respondWithServerErrorMessage:(NSString *)errorMessage {
+ NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:nil];
+ return [self respondWithServerErrorMessage:errorMessage error:error];
+}
+
+- (NSData *)respondWithJSON:(NSDictionary *)JSON error:(NSError *)error {
+ NSError *JSONEncodingError;
+ NSData *data;
+ if (JSON) {
+ data = [NSJSONSerialization dataWithJSONObject:JSON
+ options:NSJSONWritingPrettyPrinted
+ error:&JSONEncodingError];
+ }
+ NSAssert(!JSONEncodingError, @"An error occurred encoding the JSON response.");
+ [self respondWithData:data error:error];
+ return data;
+}
+
+- (NSData *)respondWithJSONError:(NSDictionary *)JSONError {
+ return [self respondWithJSON:JSONError
+ error:[NSError errorWithDomain:kFakeErrorDomain code:0 userInfo:nil]];
+}
+
+- (NSData *)respondWithError:(NSError *)error {
+ return [self respondWithJSON:nil error:error];
+}
+
+- (NSData *)respondWithJSON:(NSDictionary *)JSON {
+ return [self respondWithJSON:JSON error:nil];
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRGetAccountInfoRequestTests.m b/Example/Auth/Tests/FIRGetAccountInfoRequestTests.m
new file mode 100644
index 0000000..6f713b0
--- /dev/null
+++ b/Example/Auth/Tests/FIRGetAccountInfoRequestTests.m
@@ -0,0 +1,87 @@
+/*
+ * 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 "FIRAuthBackend.h"
+#import "FIRGetOOBConfirmationCodeResponse.h"
+#import "FIRGetAccountInfoRequest.h"
+#import "FIRGetAccountInfoResponse.h"
+#import "FIRFakeBackendRPCIssuer.h"
+
+/** @var kTestAPIKey
+ @brief Fake API key used for testing.
+ */
+static NSString *const kTestAPIKey = @"APIKey";
+
+/** @var kIDTokenKey
+ @brief The key for the "idToken" value in the request. This is actually the STS Access Token,
+ despite it's confusing (backwards compatiable) parameter name.
+ */
+static NSString *const kIDTokenKey = @"idToken";
+
+/** @var kTestAccessToken
+ @brief testing token.
+ */
+static NSString *const kTestAccessToken = @"testAccessToken";
+
+/** @var kExpectedAPIURL
+ @brief The expected URL for test calls.
+ */
+static NSString *const kExpectedAPIURL =
+ @"https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCustomToken?key=APIKey";
+
+@interface FIRGetAccountInfoRequestTests : XCTestCase
+@end
+@implementation FIRGetAccountInfoRequestTests {
+ /** @var _RPCIssuer
+ @brief This backend RPC issuer is used to fake network responses for each test in the suite.
+ In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it.
+ */
+ FIRFakeBackendRPCIssuer *_RPCIssuer;
+}
+
+- (void)setUp {
+ [super setUp];
+ FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init];
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer];
+ _RPCIssuer = RPCIssuer;
+}
+
+- (void)tearDown {
+ _RPCIssuer = nil;
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
+ [super tearDown];
+}
+
+/** @fn testGetAccountInfoRequest
+ @brief Tests the set account info request.
+ */
+- (void)testGetAccountInfoRequest {
+ FIRGetAccountInfoRequest *request =
+ [[FIRGetAccountInfoRequest alloc] initWithAPIKey:kTestAPIKey accessToken:kTestAccessToken];
+
+ [FIRAuthBackend getAccountInfo:request callback:^(FIRGetAccountInfoResponse *_Nullable response,
+ NSError *_Nullable error) {
+
+ }];
+ XCTAssertNotNil(_RPCIssuer.decodedRequest);
+ XCTAssert([_RPCIssuer.decodedRequest isKindOfClass:[NSDictionary class]]);
+ XCTAssertNotNil(_RPCIssuer.decodedRequest[kIDTokenKey]);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kIDTokenKey], kTestAccessToken);
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRGetAccountInfoResponseTests.m b/Example/Auth/Tests/FIRGetAccountInfoResponseTests.m
new file mode 100644
index 0000000..b6c261e
--- /dev/null
+++ b/Example/Auth/Tests/FIRGetAccountInfoResponseTests.m
@@ -0,0 +1,248 @@
+/*
+ * 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 "FIRAuthErrors.h"
+#import "FIRAuthInternalErrors.h"
+#import "FIRAuthBackend.h"
+#import "FIRGetOOBConfirmationCodeResponse.h"
+#import "FIRGetAccountInfoRequest.h"
+#import "FIRGetAccountInfoResponse.h"
+#import "FIRFakeBackendRPCIssuer.h"
+
+/** @var kTestAPIKey
+ @brief Fake API key used for testing.
+ */
+static NSString *const kTestAPIKey = @"APIKey";
+
+/** @var kUsersKey
+ @brief the name of the "users" property in the response.
+ */
+static NSString *const kUsersKey = @"users";
+
+/** @var kVerifiedProviderKey
+ @brief The name of the "VerifiedProvider" property in the response.
+ */
+static NSString *const kProviderUserInfoKey = @"providerUserInfo";
+
+/** @var kPhotoUrlKey
+ @brief The name of the "photoURL" property in the response.
+ */
+static NSString *const kPhotoUrlKey = @"photoUrl";
+
+/** @var kTestPhotoURL
+ @brief The fake photoUrl property value in the response.
+ */
+static NSString *const kTestPhotoURL = @"testPhotoURL";
+
+/** @var kTestAccessToken
+ @brief testing token.
+ */
+static NSString *const kTestAccessToken = @"testAccessToken";
+
+/** @var kProviderIDkey
+ @brief The name of the "provider ID" property in the response.
+ */
+static NSString *const kProviderIDkey = @"providerId";
+
+/** @var kTestProviderID
+ @brief The fake providerID property value in the response.
+ */
+static NSString *const kTestProviderID = @"testProviderID";
+
+/** @var kDisplayNameKey
+ @brief The name of the "Display Name" property in the response.
+ */
+static NSString *const kDisplayNameKey = @"displayName";
+
+/** @var kTestDisplayName
+ @brief The fake DisplayName property value in the response.
+ */
+static NSString *const kTestDisplayName = @"DisplayName";
+
+/** @var kFederatedIDKey
+ @brief The name of the "federated Id" property in the response.
+ */
+static NSString *const kFederatedIDKey = @"federatedId";
+
+/** @var kTestFederatedID
+ @brief The fake federated Id property value in the response.
+ */
+static NSString *const kTestFederatedID = @"testFederatedId";
+
+/** @var kEmailKey
+ @brief The name of the "Email" property in the response.
+ */
+static NSString *const kEmailKey = @"email";
+
+/** @var kTestEmail
+ @brief The fake email property value in the response.
+ */
+static NSString *const kTestEmail = @"testEmail";
+
+/** @var kPasswordHashKey
+ @brief The name of the "password hash" property in the response.
+ */
+static NSString *const kPasswordHashKey = @"passwordHash";
+
+/** @var kTestPasswordHash
+ @brief The fake password hash property value in the response.
+ */
+static NSString *const kTestPasswordHash = @"testPasswordHash";
+
+/** @var kLocalIDKey
+ @brief The key for the "localID" value in the response.
+ */
+static NSString *const kLocalIDKey = @"localId";
+
+/** @var kTestLocalID
+ @brief The fake @c localID for testing in the response.
+ */
+static NSString *const kTestLocalID = @"testLocalId";
+
+/** @var kEmailVerifiedKey
+ @brief The key for the "emailVerified" value in the response.
+ */
+static NSString *const kEmailVerifiedKey = @"emailVerified";
+
+/** @class FIRGetAccountInfoResponseTests
+ @brief Tests for @c FIRGetAccountInfoResponse.
+ */
+@interface FIRGetAccountInfoResponseTests : XCTestCase
+@end
+@implementation FIRGetAccountInfoResponseTests {
+ /** @var _RPCIssuer
+ @brief This backend RPC issuer is used to fake network responses for each test in the suite.
+ In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it.
+ */
+ FIRFakeBackendRPCIssuer *_RPCIssuer;
+}
+
+- (void)setUp {
+ [super setUp];
+ FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init];
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer];
+ _RPCIssuer = RPCIssuer;
+}
+
+- (void)tearDown {
+ _RPCIssuer = nil;
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
+ [super tearDown];
+}
+
+/** @fn testGetAccountInfoUnexpectedResponseError
+ @brief This test simulates an unexpected response returned from server in @c GetAccountInfo
+ flow.
+ */
+- (void)testGetAccountInfoUnexpectedResponseError {
+ FIRGetAccountInfoRequest *request =
+ [[FIRGetAccountInfoRequest alloc] initWithAPIKey:kTestAPIKey accessToken:kTestAccessToken];
+
+ __block BOOL callbackInvoked;
+ __block FIRGetAccountInfoResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend getAccountInfo:request
+ callback:^(FIRGetAccountInfoResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ NSArray *erroneousUserData = @[@"user1Data", @"user2Data"];
+ [_RPCIssuer respondWithJSON:@{
+ kUsersKey : erroneousUserData
+ }];
+
+ XCTAssert(callbackInvoked);
+ XCTAssertNotNil(RPCError);
+ XCTAssertEqualObjects(RPCError.domain, FIRAuthErrorDomain);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeInternalError);
+ XCTAssertNotNil(RPCError.userInfo[NSUnderlyingErrorKey]);
+ NSError *underlyingError = RPCError.userInfo[NSUnderlyingErrorKey];
+ XCTAssertNotNil(underlyingError);
+ XCTAssertNotNil(underlyingError.userInfo[FIRAuthErrorUserInfoDeserializedResponseKey]);
+ XCTAssertNil(RPCResponse);
+}
+
+/** @fn testSuccessfulGetAccountInfoResponse
+ @brief This test simulates a successful @c GetAccountInfo flow.
+ */
+- (void)testSuccessfulGetAccountInfoResponse {
+ FIRGetAccountInfoRequest *request =
+ [[FIRGetAccountInfoRequest alloc] initWithAPIKey:kTestAPIKey accessToken:kTestAccessToken];
+
+ __block BOOL callbackInvoked;
+ __block FIRGetAccountInfoResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend getAccountInfo:request
+ callback:^(FIRGetAccountInfoResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ NSArray *users = @[
+ @{
+ kProviderUserInfoKey:@[
+ @{
+ kProviderIDkey : kTestProviderID,
+ kDisplayNameKey: kTestDisplayName,
+ kPhotoUrlKey : kTestPhotoURL,
+ kFederatedIDKey : kTestFederatedID,
+ kEmailKey : kTestEmail,
+ }
+ ],
+ kLocalIDKey : kTestLocalID,
+ kDisplayNameKey : kTestDisplayName,
+ kEmailKey : kTestEmail,
+ kPhotoUrlKey : kTestPhotoURL,
+ kEmailVerifiedKey : @YES,
+ kPasswordHashKey : kTestPasswordHash
+ }
+ ];
+ [_RPCIssuer respondWithJSON:@{
+ @"users" : users,
+ }];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCError);
+ XCTAssertNotNil(RPCResponse);
+ XCTAssertNotNil(RPCResponse.users);
+ if ([RPCResponse.users count]) {
+ NSURL *responsePhotoUrl = RPCResponse.users[0].photoURL;
+ XCTAssertEqualObjects(responsePhotoUrl.absoluteString, kTestPhotoURL);
+ XCTAssertEqualObjects(RPCResponse.users[0].displayName, kTestDisplayName);
+ XCTAssertEqualObjects(RPCResponse.users[0].email, kTestEmail);
+ XCTAssertEqualObjects(RPCResponse.users[0].localID, kTestLocalID);
+ XCTAssertEqual(RPCResponse.users[0].emailVerified, YES);
+ XCTAssertEqualObjects(RPCResponse.users[0].passwordHash, kTestPasswordHash);
+ NSArray<FIRGetAccountInfoResponseProviderUserInfo *> *providerUserInfo =
+ RPCResponse.users[0].providerUserInfo;
+ if ([providerUserInfo count]) {
+ NSURL *providerInfoPhotoUrl = providerUserInfo[0].photoURL;
+ XCTAssertEqualObjects(providerInfoPhotoUrl.absoluteString, kTestPhotoURL);
+ XCTAssertEqualObjects(providerUserInfo[0].providerID, kTestProviderID);
+ XCTAssertEqualObjects(providerUserInfo[0].displayName, kTestDisplayName);
+ XCTAssertEqualObjects(providerUserInfo[0].federatedID, kTestFederatedID);
+ XCTAssertEqualObjects(providerUserInfo[0].email, kTestEmail);
+ }
+ }
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRGetOOBConfirmationCodeRequestTests.m b/Example/Auth/Tests/FIRGetOOBConfirmationCodeRequestTests.m
new file mode 100644
index 0000000..d5a22aa
--- /dev/null
+++ b/Example/Auth/Tests/FIRGetOOBConfirmationCodeRequestTests.m
@@ -0,0 +1,149 @@
+/*
+ * 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 "FIRAuthErrors.h"
+#import "FIRAuthBackend.h"
+#import "FIRGetOOBConfirmationCodeRequest.h"
+#import "FIRGetOOBConfirmationCodeResponse.h"
+#import "FIRFakeBackendRPCIssuer.h"
+
+/** @var kTestAPIKey
+ @brief Fake API key used for testing.
+ */
+static NSString *const kTestAPIKey = @"APIKey";
+
+/** @var kExpectedAPIURL
+ @brief The expected URL for the test calls.
+ */
+static NSString *const kExpectedAPIURL =
+ @"https://www.googleapis.com/identitytoolkit/v3/relyingparty/getOobConfirmationCode?key=APIKey";
+
+/** @var kRequestTypeKey
+ @brief The name of the required "requestType" property in the request.
+ */
+static NSString *const kRequestTypeKey = @"requestType";
+
+/** @var kPasswordResetRequestTypeValue
+ @brief The value for the "PASSWORD_RESET" request type.
+ */
+static NSString *const kPasswordResetRequestTypeValue = @"PASSWORD_RESET";
+
+/** @var kVerifyEmailRequestTypeValue
+ @brief The value for the "VERIFY_EMAIL" request type.
+ */
+static NSString *const kVerifyEmailRequestTypeValue = @"VERIFY_EMAIL";
+
+/** @var kEmailKey
+ @brief The name of the "email" property in the request.
+ */
+static NSString *const kEmailKey = @"email";
+
+/** @var kTestEmail
+ @brief Testing user email adadress.
+ */
+static NSString *const kTestEmail = @"test@gmail.com";
+
+/** @var kAccessTokenKey
+ @brief The name of the "accessToken" property in the request.
+ */
+static NSString *const kAccessTokenKey = @"idToken";
+
+/** @var kTestAccessToken
+ @brief Testing access token.
+ */
+static NSString *const kTestAccessToken = @"ACCESS_TOKEN";
+
+/** @class FIRGetOOBConfirmationCodeRequestTests
+ @brief Tests for @c FIRGetOOBConfirmationCodeRequest.
+ */
+@interface FIRGetOOBConfirmationCodeRequestTests : XCTestCase
+@end
+@implementation FIRGetOOBConfirmationCodeRequestTests {
+ /** @var _RPCIssuer
+ @brief This backend RPC issuer is used to fake network responses for each test in the suite.
+ In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it.
+ */
+ FIRFakeBackendRPCIssuer *_RPCIssuer;
+}
+
+- (void)setUp {
+ [super setUp];
+ FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init];
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer];
+ _RPCIssuer = RPCIssuer;
+}
+
+- (void)tearDown {
+ _RPCIssuer = nil;
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
+ [super tearDown];
+}
+
+/** @fn testPasswordResetRequest
+ @brief Tests the encoding of a password reset request.
+ */
+- (void)testPasswordResetRequest {
+ FIRGetOOBConfirmationCodeRequest *request =
+ [FIRGetOOBConfirmationCodeRequest passwordResetRequestWithEmail:kTestEmail
+ APIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRGetOOBConfirmationCodeResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend getOOBConfirmationCode:request
+ callback:^(FIRGetOOBConfirmationCodeResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ XCTAssertEqualObjects(_RPCIssuer.requestURL.absoluteString, kExpectedAPIURL);
+ XCTAssertNotNil(_RPCIssuer.decodedRequest);
+ XCTAssert([_RPCIssuer.decodedRequest isKindOfClass:[NSDictionary class]]);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kEmailKey], kTestEmail);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kRequestTypeKey], kPasswordResetRequestTypeValue);
+}
+
+/** @fn testEmailVerificationRequest
+ @brief Tests the encoding of an email verification request.
+ */
+- (void)testEmailVerificationRequest {
+ FIRGetOOBConfirmationCodeRequest *request =
+ [FIRGetOOBConfirmationCodeRequest verifyEmailRequestWithAccessToken:kTestAccessToken
+ APIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRGetOOBConfirmationCodeResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend getOOBConfirmationCode:request
+ callback:^(FIRGetOOBConfirmationCodeResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ XCTAssertEqualObjects(_RPCIssuer.requestURL.absoluteString, kExpectedAPIURL);
+ XCTAssertNotNil(_RPCIssuer.decodedRequest);
+ XCTAssert([_RPCIssuer.decodedRequest isKindOfClass:[NSDictionary class]]);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kAccessTokenKey], kTestAccessToken);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kRequestTypeKey], kVerifyEmailRequestTypeValue);
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRGetOOBConfirmationCodeResponseTests.m b/Example/Auth/Tests/FIRGetOOBConfirmationCodeResponseTests.m
new file mode 100644
index 0000000..98c9d8e
--- /dev/null
+++ b/Example/Auth/Tests/FIRGetOOBConfirmationCodeResponseTests.m
@@ -0,0 +1,320 @@
+/*
+ * 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 "FIRAuthErrors.h"
+#import "FIRAuthBackend.h"
+#import "FIRGetOOBConfirmationCodeRequest.h"
+#import "FIRGetOOBConfirmationCodeResponse.h"
+#import "FIRFakeBackendRPCIssuer.h"
+
+/** @var kTestEmail
+ @brief Testing user email adadress.
+ */
+static NSString *const kTestEmail = @"test@gmail.com";
+
+/** @var kTestAccessToken
+ @brief Testing access token.
+ */
+static NSString *const kTestAccessToken = @"ACCESS_TOKEN";
+
+/** @var kTestAPIKey
+ @brief Fake API key used for testing.
+ */
+static NSString *const kTestAPIKey = @"APIKey";
+
+/** @var kOOBCodeKey
+ @brief The name of the field in the response JSON for the OOB code.
+ */
+static NSString *const kOOBCodeKey = @"oobCode";
+
+/** @var kTestOOBCode
+ @brief Fake OOB Code used for testing.
+ */
+static NSString *const kTestOOBCode = @"OOBCode";
+
+/** @var kEmailNotFoundMessage
+ @brief The value of the "message" field returned for an "email not found" error.
+ */
+static NSString *const kEmailNotFoundMessage = @"EMAIL_NOT_FOUND: fake custom message";
+
+/** @var kInvalidEmailErrorMessage
+ @brief The error returned by the server if the email is invalid.
+ */
+static NSString *const kInvalidEmailErrorMessage = @"INVALID_EMAIL:";
+
+/** @var kInvalidMessagePayloadErrorMessage
+ @brief This is the prefix for the error message the server responds with if an invalid message
+ payload was sent.
+ */
+static NSString *const kInvalidMessagePayloadErrorMessage = @"INVALID_MESSAGE_PAYLOAD";
+
+/** @var kInvalidSenderErrorMessage
+ @brief This is the prefix for the error message the server responds with if invalid sender is
+ used to send the email for updating user's email address.
+ */
+static NSString *const kInvalidSenderErrorMessage = @"INVALID_SENDER";
+
+
+/** @var kInvalidRecipientEmailErrorMessage
+ @brief This is the prefix for the error message the server responds with if the recipient email
+ is invalid.
+ */
+static NSString *const kInvalidRecipientEmailErrorMessage = @"INVALID_RECIPIENT_EMAIL";
+
+/** @class FIRGetOOBConfirmationCodeResponseTests
+ @brief Tests for @c FIRGetOOBConfirmationCodeResponse.
+ */
+@interface FIRGetOOBConfirmationCodeResponseTests : XCTestCase
+@end
+@implementation FIRGetOOBConfirmationCodeResponseTests {
+ /** @var _RPCIssuer
+ @brief This backend RPC issuer is used to fake network responses for each test in the suite.
+ In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it.
+ */
+ FIRFakeBackendRPCIssuer *_RPCIssuer;
+}
+
+- (void)setUp {
+ [super setUp];
+ FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init];
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer];
+ _RPCIssuer = RPCIssuer;
+}
+
+- (void)tearDown {
+ _RPCIssuer = nil;
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
+ [super tearDown];
+}
+
+/** @fn testSuccessfulPasswordResetResponse
+ @brief This test simulates a complete password reset response (with OOB Code) and makes sure
+ it succeeds, and we get the OOB Code decoded correctly.
+ */
+- (void)testSuccessfulPasswordResetResponse {
+ FIRGetOOBConfirmationCodeRequest *request =
+ [FIRGetOOBConfirmationCodeRequest passwordResetRequestWithEmail:kTestEmail
+ APIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRGetOOBConfirmationCodeResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend getOOBConfirmationCode:request
+ callback:^(FIRGetOOBConfirmationCodeResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithJSON:@{
+ kOOBCodeKey : kTestOOBCode
+ }];
+
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCError);
+ XCTAssertNotNil(RPCResponse);
+ XCTAssertEqualObjects(RPCResponse.OOBCode, kTestOOBCode);
+}
+
+/** @fn testSuccessfulPasswordResetResponseWithoutOOBCode
+ @brief This test simulates a password reset request where we don't receive the optional OOBCode
+ response value. It should still succeed.
+ */
+- (void)testSuccessfulPasswordResetResponseWithoutOOBCode {
+ FIRGetOOBConfirmationCodeRequest *request =
+ [FIRGetOOBConfirmationCodeRequest passwordResetRequestWithEmail:kTestEmail
+ APIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRGetOOBConfirmationCodeResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend getOOBConfirmationCode:request
+ callback:^(FIRGetOOBConfirmationCodeResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithJSON:@{}];
+
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCError);
+ XCTAssertNotNil(RPCResponse);
+ XCTAssertNil(RPCResponse.OOBCode);
+}
+
+/** @fn testEmailNotFoundError
+ @brief This test checks for email not found responses, and makes sure they are decoded to the
+ correct error response.
+ */
+- (void)testEmailNotFoundError {
+ FIRGetOOBConfirmationCodeRequest *request =
+ [FIRGetOOBConfirmationCodeRequest passwordResetRequestWithEmail:kTestEmail
+ APIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRGetOOBConfirmationCodeResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend getOOBConfirmationCode:request
+ callback:^(FIRGetOOBConfirmationCodeResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kEmailNotFoundMessage];
+
+ XCTAssert(callbackInvoked);
+ XCTAssertNotNil(RPCError);
+ XCTAssertEqualObjects(RPCError.domain, FIRAuthErrorDomain);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeUserNotFound);
+ XCTAssertNil(RPCResponse);
+}
+
+/** @fn testInvalidEmailError
+ @brief This test checks for the INVALID_EMAIL error message from the backend.
+ */
+- (void)testInvalidEmailError {
+ FIRGetOOBConfirmationCodeRequest *request =
+ [FIRGetOOBConfirmationCodeRequest passwordResetRequestWithEmail:kTestEmail
+ APIKey:kTestAPIKey];
+ __block BOOL callbackInvoked;
+ __block FIRGetOOBConfirmationCodeResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend getOOBConfirmationCode:request
+ callback:^(FIRGetOOBConfirmationCodeResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kInvalidEmailErrorMessage];
+
+ XCTAssert(callbackInvoked);
+ XCTAssertNotNil(RPCError);
+ XCTAssertEqualObjects(RPCError.domain, FIRAuthErrorDomain);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeInvalidEmail);
+ XCTAssertNil(RPCResponse);
+}
+
+/** @fn testInvalidMessagePayloadError
+ @brief Tests for @c FIRAuthErrorCodeInvalidMessagePayload.
+ */
+- (void)testInvalidMessagePayloadError {
+ FIRGetOOBConfirmationCodeRequest *request =
+ [FIRGetOOBConfirmationCodeRequest passwordResetRequestWithEmail:kTestEmail
+ APIKey:kTestAPIKey];
+ __block BOOL callbackInvoked;
+ __block FIRGetOOBConfirmationCodeResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend getOOBConfirmationCode:request
+ callback:^(FIRGetOOBConfirmationCodeResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kInvalidMessagePayloadErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeInvalidMessagePayload);
+}
+
+/** @fn testInvalidSenderError
+ @brief Tests for @c FIRAuthErrorCodeInvalidSender.
+ */
+- (void)testInvalidSenderError {
+ FIRGetOOBConfirmationCodeRequest *request =
+ [FIRGetOOBConfirmationCodeRequest passwordResetRequestWithEmail:kTestEmail
+ APIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRGetOOBConfirmationCodeResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend getOOBConfirmationCode:request
+ callback:^(FIRGetOOBConfirmationCodeResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+ [_RPCIssuer respondWithServerErrorMessage:kInvalidSenderErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeInvalidSender);
+}
+
+/** @fn testInvalidRecipientEmailError
+ @brief Tests for @c FIRAuthErrorCodeInvalidRecipientEmail.
+ */
+- (void)testInvalidRecipientEmailError {
+ FIRGetOOBConfirmationCodeRequest *request =
+ [FIRGetOOBConfirmationCodeRequest passwordResetRequestWithEmail:kTestEmail
+ APIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRGetOOBConfirmationCodeResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend getOOBConfirmationCode:request
+ callback:^(FIRGetOOBConfirmationCodeResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+ [_RPCIssuer respondWithServerErrorMessage:kInvalidRecipientEmailErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeInvalidRecipientEmail);
+}
+
+/** @fn testSuccessfulEmailVerificationResponse
+ @brief This test is really not much different than the original test for password reset. But
+ it's here for completeness sake.
+ */
+- (void)testSuccessfulEmailVerificationResponse {
+ FIRGetOOBConfirmationCodeRequest *request =
+ [FIRGetOOBConfirmationCodeRequest passwordResetRequestWithEmail:kTestEmail
+ APIKey:kTestAPIKey];
+ __block BOOL callbackInvoked;
+ __block FIRGetOOBConfirmationCodeResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend getOOBConfirmationCode:request
+ callback:^(FIRGetOOBConfirmationCodeResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithJSON:@{
+ kOOBCodeKey : kTestOOBCode
+ }];
+
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCError);
+ XCTAssertNotNil(RPCResponse);
+ XCTAssertEqualObjects(RPCResponse.OOBCode, kTestOOBCode);
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRGitHubAuthProviderTests.m b/Example/Auth/Tests/FIRGitHubAuthProviderTests.m
new file mode 100644
index 0000000..dfcd87f
--- /dev/null
+++ b/Example/Auth/Tests/FIRGitHubAuthProviderTests.m
@@ -0,0 +1,52 @@
+/*
+ * 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 "GitHub/FIRGitHubAuthProvider.h"
+#import "FIRAuthCredential_Internal.h"
+#import "FIRVerifyAssertionRequest.h"
+
+/** @var kGitHubToken
+ @brief A testing GitHub token.
+ */
+static NSString *const kGitHubToken = @"Token";
+
+/** @var kAPIKey
+ @brief A testing API Key.
+ */
+static NSString *const kAPIKey = @"APIKey";
+
+/** @class FIRGitHubAuthProviderTests
+ @brief Tests for @c FIRGitHubAuthProvider
+ */
+@interface FIRGitHubAuthProviderTests : XCTestCase
+@end
+@implementation FIRGitHubAuthProviderTests
+
+/** @fn testCredentialWithToken
+ @brief Tests the @c credentialWithToken method to make sure the credential it produces populates
+ the appropriate fields in a verify assertion request.
+ */
+- (void)testCredentialWithToken {
+ FIRAuthCredential *credential = [FIRGitHubAuthProvider credentialWithToken:kGitHubToken];
+ FIRVerifyAssertionRequest *request =
+ [[FIRVerifyAssertionRequest alloc] initWithAPIKey:kAPIKey providerID:FIRGitHubAuthProviderID];
+ [credential prepareVerifyAssertionRequest:request];
+ XCTAssertEqualObjects(request.providerAccessToken, kGitHubToken);
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRPhoneAuthProviderTests.m b/Example/Auth/Tests/FIRPhoneAuthProviderTests.m
new file mode 100644
index 0000000..f907601
--- /dev/null
+++ b/Example/Auth/Tests/FIRPhoneAuthProviderTests.m
@@ -0,0 +1,550 @@
+/*
+ * 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 "Phone/FIRPhoneAuthProvider.h"
+#import "Phone/FIRPhoneAuthCredential_Internal.h"
+#import "Phone/NSString+FIRAuth.h"
+#import "FIRAuthAPNSToken.h"
+#import "FIRAuthAPNSTokenManager.h"
+#import "FIRAuthAppCredential.h"
+#import "FIRAuthAppCredentialManager.h"
+#import "FIRAuthNotificationManager.h"
+#import "FIRAuth_Internal.h"
+#import "FIRAuthCredential_Internal.h"
+#import "FIRAuthErrorUtils.h"
+#import "FIRAuthGlobalWorkQueue.h"
+#import "FIRAuthBackend.h"
+#import "FIRSendVerificationCodeRequest.h"
+#import "FIRSendVerificationCodeResponse.h"
+#import "FIRVerifyClientRequest.h"
+#import "FIRVerifyClientResponse.h"
+#import "FIRApp+FIRAuthUnitTests.h"
+#import "OCMStubRecorder+FIRAuthUnitTests.h"
+#import <OCMock/OCMock.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** @var kTestPhoneNumber
+ @brief A testing phone number.
+ */
+static NSString *const kTestPhoneNumber = @"55555555";
+
+/** @var kTestInvalidPhoneNumber
+ @brief An invalid testing phone number.
+ */
+static NSString *const kTestInvalidPhoneNumber = @"555+!*55555";
+
+/** @var kTestVerificationID
+ @brief A testing verfication ID.
+ */
+static NSString *const kTestVerificationID = @"verificationID";
+
+/** @var kTestReceipt
+ @brief A fake receipt for testing.
+ */
+static NSString *const kTestReceipt = @"receipt";
+
+/** @var kTestSecret
+ @brief A fake secret for testing.
+ */
+static NSString *const kTestSecret = @"secret";
+
+/** @var kTestOldReceipt
+ @brief A fake old receipt for testing.
+ */
+static NSString *const kTestOldReceipt = @"old_receipt";
+
+/** @var kTestOldSecret
+ @brief A fake old secret for testing.
+ */
+static NSString *const kTestOldSecret = @"old_secret";
+
+
+/** @var kTestVerificationCode
+ @brief A fake verfication code.
+ */
+static NSString *const kTestVerificationCode = @"verificationCode";
+
+/** @var kTestTimeout
+ @brief A fake timeout value for waiting for push notification.
+ */
+static const NSTimeInterval kTestTimeout = 5;
+
+/** @var kAPIKey
+ @brief The fake API key.
+ */
+static NSString *const kAPIKey = @"FAKE_API_KEY";
+
+/** @var kExpectationTimeout
+ @brief The maximum time waiting for expectations to fulfill.
+ */
+static const NSTimeInterval kExpectationTimeout = 1;
+
+/** @class FIRPhoneAuthProviderTests
+ @brief Tests for @c FIRPhoneAuthProvider
+ */
+@interface FIRPhoneAuthProviderTests : XCTestCase
+@end
+
+@implementation FIRPhoneAuthProviderTests {
+ /** @var _mockBackend
+ @brief The mock @c FIRAuthBackendImplementation .
+ */
+ id _mockBackend;
+
+ /** @var _provider
+ @brief The @c FIRPhoneAuthProvider instance under test.
+ */
+ FIRPhoneAuthProvider *_provider;
+
+ /** @var _mockAuth
+ @brief The mock @c FIRAuth instance associated with @c _provider .
+ */
+ id _mockAuth;
+
+ /** @var _mockAPNSTokenManager
+ @brief The mock @c FIRAuthAPNSTokenManager instance associated with @c _mockAuth .
+ */
+ id _mockAPNSTokenManager;
+
+ /** @var _mockAppCredentialManager
+ @brief The mock @c FIRAuthAppCredentialManager instance associated with @c _mockAuth .
+ */
+ id _mockAppCredentialManager;
+
+ /** @var _mockNotificationManager
+ @brief The mock @c FIRAuthNotificationManager instance associated with @c _mockAuth .
+ */
+ id _mockNotificationManager;
+}
+
+- (void)setUp {
+ [super setUp];
+ _mockBackend = OCMProtocolMock(@protocol(FIRAuthBackendImplementation));
+ [FIRAuthBackend setBackendImplementation:_mockBackend];
+ _mockAuth = OCMClassMock([FIRAuth class]);
+ _mockAPNSTokenManager = OCMClassMock([FIRAuthAPNSTokenManager class]);
+ OCMStub([_mockAuth tokenManager]).andReturn(_mockAPNSTokenManager);
+ _mockAppCredentialManager = OCMClassMock([FIRAuthAppCredentialManager class]);
+ OCMStub([_mockAuth appCredentialManager]).andReturn(_mockAppCredentialManager);
+ _mockNotificationManager = OCMClassMock([FIRAuthNotificationManager class]);
+ OCMStub([_mockAuth notificationManager]).andReturn(_mockNotificationManager);
+ _provider = [FIRPhoneAuthProvider providerWithAuth:_mockAuth];
+}
+
+- (void)tearDown {
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
+ [super tearDown];
+}
+
+/** @fn testCredentialWithVerificationID
+ @brief Tests the @c credentialWithToken method to make sure that it returns a valid
+ FIRAuthCredential instance.
+ */
+- (void)testCredentialWithVerificationID {
+ FIRPhoneAuthCredential *credential =
+ [_provider credentialWithVerificationID:kTestVerificationID
+ verificationCode:kTestVerificationCode];
+ XCTAssertEqualObjects(credential.verificationID, kTestVerificationID);
+ XCTAssertEqualObjects(credential.verificationCode, kTestVerificationCode);
+ XCTAssertNil(credential.temporaryProof);
+ XCTAssertNil(credential.phoneNumber);
+}
+
+/** @fn testVerifyEmptyPhoneNumber
+ @brief Tests a failed invocation @c verifyPhoneNumber:completion: because an empty phone
+ number was provided.
+ */
+- (void)testVerifyEmptyPhoneNumber {
+ // Empty phone number is checked on the client side so no backend RPC is mocked.
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [_provider verifyPhoneNumber:@""
+ completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
+ XCTAssertNotNil(error);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeMissingPhoneNumber);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+}
+
+/** @fn testVerifyInvalidPhoneNumber
+ @brief Tests a failed invocation @c verifyPhoneNumber:completion: because an invalid phone
+ number was provided.
+ */
+- (void)testVerifyInvalidPhoneNumber {
+ OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
+ .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
+ OCMStub([_mockAppCredentialManager credential])
+ .andReturn([[FIRAuthAppCredential alloc] initWithReceipt:kTestReceipt secret:kTestSecret]);
+ OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
+ FIRSendVerificationCodeResponseCallback callback) {
+ XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
+ XCTAssertEqualObjects(request.appCredential.receipt, kTestReceipt);
+ XCTAssertEqualObjects(request.appCredential.secret, kTestSecret);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ callback(nil, [FIRAuthErrorUtils invalidPhoneNumberErrorWithMessage:nil]);
+ });
+ });
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [_provider verifyPhoneNumber:kTestPhoneNumber
+ completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(verificationID);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidPhoneNumber);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+ OCMVerifyAll(_mockNotificationManager);
+ OCMVerifyAll(_mockAppCredentialManager);
+}
+
+/** @fn testVerifyPhoneNumber
+ @brief Tests a successful invocation of @c verifyPhoneNumber:completion:.
+ */
+- (void)testVerifyPhoneNumber {
+ OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
+ .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
+ OCMStub([_mockAppCredentialManager credential])
+ .andReturn([[FIRAuthAppCredential alloc] initWithReceipt:kTestReceipt secret:kTestSecret]);
+ OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
+ FIRSendVerificationCodeResponseCallback callback) {
+ XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
+ XCTAssertEqualObjects(request.appCredential.receipt, kTestReceipt);
+ XCTAssertEqualObjects(request.appCredential.secret, kTestSecret);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockSendVerificationCodeResponse = OCMClassMock([FIRSendVerificationCodeResponse class]);
+ OCMStub([mockSendVerificationCodeResponse verificationID]).andReturn(kTestVerificationID);
+ callback(mockSendVerificationCodeResponse, nil);
+ });
+ });
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [_provider verifyPhoneNumber:kTestPhoneNumber
+ completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(error);
+ XCTAssertEqualObjects(verificationID, kTestVerificationID);
+ XCTAssertEqualObjects(verificationID.fir_authPhoneNumber, kTestPhoneNumber);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+ OCMVerifyAll(_mockNotificationManager);
+ OCMVerifyAll(_mockAppCredentialManager);
+}
+
+/** @fn testNotForwardingNotification
+ @brief Tests returning an error for the app failing to forward notification.
+ */
+- (void)testNotForwardingNotification {
+ OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
+ .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(NO); });
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [_provider verifyPhoneNumber:kTestPhoneNumber
+ completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(verificationID);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeNotificationNotForwarded);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockNotificationManager);
+}
+
+/** @fn testMissingAPNSToken
+ @brief Tests returning an error for the app failing to provide an APNS device token.
+ */
+- (void)testMissingAPNSToken {
+ OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
+ .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
+ OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
+ OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
+ .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) { callback(nil); });
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [_provider verifyPhoneNumber:kTestPhoneNumber
+ completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(verificationID);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeMissingAppToken);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockNotificationManager);
+ OCMVerifyAll(_mockAppCredentialManager);
+ OCMVerifyAll(_mockAPNSTokenManager);
+}
+
+/** @fn testVerifyClient
+ @brief Tests verifying client before sending verification code.
+ */
+- (void)testVerifyClient {
+ OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
+ .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
+ OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
+ NSData *data = [@"!@#$%^" dataUsingEncoding:NSUTF8StringEncoding];
+ FIRAuthAPNSToken *token = [[FIRAuthAPNSToken alloc] initWithData:data
+ type:FIRAuthAPNSTokenTypeProd];
+ OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
+ .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) { callback(token); });
+ // Expect verify client request to the backend.
+ OCMExpect([_mockBackend verifyClient:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRVerifyClientRequest *request,
+ FIRVerifyClientResponseCallback callback) {
+ XCTAssertEqualObjects(request.appToken, @"21402324255E");
+ XCTAssertFalse(request.isSandbox);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockVerifyClientResponse = OCMClassMock([FIRVerifyClientResponse class]);
+ OCMStub([mockVerifyClientResponse receipt]).andReturn(kTestReceipt);
+ OCMStub([mockVerifyClientResponse suggestedTimeOutDate])
+ .andReturn([NSDate dateWithTimeIntervalSinceNow:kTestTimeout]);
+ callback(mockVerifyClientResponse, nil);
+ });
+ });
+ // Mock receiving of push notification.
+ OCMExpect([[_mockAppCredentialManager ignoringNonObjectArgs]
+ didStartVerificationWithReceipt:OCMOCK_ANY timeout:0 callback:OCMOCK_ANY])
+ .andCallIdDoubleIdBlock(^(NSString *receipt,
+ NSTimeInterval timeout,
+ FIRAuthAppCredentialCallback callback) {
+ XCTAssertEqualObjects(receipt, kTestReceipt);
+ // Unfortunately 'ignoringNonObjectArgs' means the real value for 'timeout' doesn't get passed
+ // into the block either, so we can't verify it here.
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ callback([[FIRAuthAppCredential alloc] initWithReceipt:kTestReceipt secret:kTestSecret]);
+ });
+ });
+ // Expect send verification code request to the backend.
+ OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
+ FIRSendVerificationCodeResponseCallback callback) {
+ XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
+ XCTAssertEqualObjects(request.appCredential.receipt, kTestReceipt);
+ XCTAssertEqualObjects(request.appCredential.secret, kTestSecret);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockSendVerificationCodeResponse = OCMClassMock([FIRSendVerificationCodeResponse class]);
+ OCMStub([mockSendVerificationCodeResponse verificationID]).andReturn(kTestVerificationID);
+ callback(mockSendVerificationCodeResponse, nil);
+ });
+ });
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [_provider verifyPhoneNumber:kTestPhoneNumber
+ completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(error);
+ XCTAssertEqualObjects(verificationID, kTestVerificationID);
+ XCTAssertEqualObjects(verificationID.fir_authPhoneNumber, kTestPhoneNumber);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+ OCMVerifyAll(_mockNotificationManager);
+ OCMVerifyAll(_mockAppCredentialManager);
+ OCMVerifyAll(_mockAPNSTokenManager);
+}
+
+/** @fn testSendVerificationCodeFailedRetry
+ @brief Tests failed retry after failing to send verification code.
+ */
+- (void)testSendVerificationCodeFailedRetry {
+ OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
+ .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
+
+ // Expect twice due to null check consumes one expectation.
+ OCMExpect([_mockAppCredentialManager credential])
+ .andReturn([[FIRAuthAppCredential alloc] initWithReceipt:kTestOldReceipt
+ secret:kTestOldSecret]);
+ OCMExpect([_mockAppCredentialManager credential])
+ .andReturn([[FIRAuthAppCredential alloc] initWithReceipt:kTestOldReceipt
+ secret:kTestOldSecret]);
+ NSData *data = [@"!@#$%^" dataUsingEncoding:NSUTF8StringEncoding];
+ FIRAuthAPNSToken *token = [[FIRAuthAPNSToken alloc] initWithData:data
+ type:FIRAuthAPNSTokenTypeProd];
+
+ // Expect first sendVerificationCode request to the backend, with request containing old app
+ // credential.
+ OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
+ FIRSendVerificationCodeResponseCallback callback) {
+ XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
+ XCTAssertEqualObjects(request.appCredential.receipt, kTestOldReceipt);
+ XCTAssertEqualObjects(request.appCredential.secret, kTestOldSecret);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ callback(nil, [FIRAuthErrorUtils invalidAppCredentialWithMessage:nil]);
+ });
+ });
+
+ // Expect send verification code request to the backend, with request containing new app
+ // credential data.
+ OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
+ FIRSendVerificationCodeResponseCallback callback) {
+ XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
+ XCTAssertEqualObjects(request.appCredential.receipt, kTestReceipt);
+ XCTAssertEqualObjects(request.appCredential.secret, kTestSecret);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ callback(nil, [FIRAuthErrorUtils invalidAppCredentialWithMessage:nil]);
+ });
+ });
+
+ OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
+ .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) { callback(token); });
+ // Expect verify client request to the backend.
+ OCMExpect([_mockBackend verifyClient:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRVerifyClientRequest *request,
+ FIRVerifyClientResponseCallback callback) {
+ XCTAssertEqualObjects(request.appToken, @"21402324255E");
+ XCTAssertFalse(request.isSandbox);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockVerifyClientResponse = OCMClassMock([FIRVerifyClientResponse class]);
+ OCMStub([mockVerifyClientResponse receipt]).andReturn(kTestReceipt);
+ OCMStub([mockVerifyClientResponse suggestedTimeOutDate])
+ .andReturn([NSDate dateWithTimeIntervalSinceNow:kTestTimeout]);
+ callback(mockVerifyClientResponse, nil);
+ });
+ });
+
+ // Mock receiving of push notification.
+ OCMStub([[_mockAppCredentialManager ignoringNonObjectArgs]
+ didStartVerificationWithReceipt:OCMOCK_ANY timeout:0 callback:OCMOCK_ANY])
+ .andCallIdDoubleIdBlock(^(NSString *receipt,
+ NSTimeInterval timeout,
+ FIRAuthAppCredentialCallback callback) {
+ XCTAssertEqualObjects(receipt, kTestReceipt);
+ // Unfortunately 'ignoringNonObjectArgs' means the real value for 'timeout' doesn't get passed
+ // into the block either, so we can't verify it here.
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ callback([[FIRAuthAppCredential alloc] initWithReceipt:kTestReceipt secret:kTestSecret]);
+ });
+ });
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [_provider verifyPhoneNumber:kTestPhoneNumber
+ completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertNil(verificationID);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeInternalError);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+ OCMVerifyAll(_mockNotificationManager);
+ OCMVerifyAll(_mockAppCredentialManager);
+ OCMVerifyAll(_mockAPNSTokenManager);
+}
+
+/** @fn testSendVerificationCodeSuccessFulRetry
+ @brief Tests successful retry after failing to send verification code.
+ */
+- (void)testSendVerificationCodeSuccessFulRetry {
+ OCMExpect([_mockNotificationManager checkNotificationForwardingWithCallback:OCMOCK_ANY])
+ .andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
+
+ // Expect twice due to null check consumes one expectation.
+ OCMExpect([_mockAppCredentialManager credential])
+ .andReturn([[FIRAuthAppCredential alloc] initWithReceipt:kTestOldReceipt
+ secret:kTestOldSecret]);
+ OCMExpect([_mockAppCredentialManager credential])
+ .andReturn([[FIRAuthAppCredential alloc] initWithReceipt:kTestOldReceipt
+ secret:kTestOldSecret]);
+ NSData *data = [@"!@#$%^" dataUsingEncoding:NSUTF8StringEncoding];
+ FIRAuthAPNSToken *token = [[FIRAuthAPNSToken alloc] initWithData:data
+ type:FIRAuthAPNSTokenTypeProd];
+
+ // Expect first sendVerificationCode request to the backend, with request containing old app
+ // credential.
+ OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
+ FIRSendVerificationCodeResponseCallback callback) {
+ XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
+ XCTAssertEqualObjects(request.appCredential.receipt, kTestOldReceipt);
+ XCTAssertEqualObjects(request.appCredential.secret, kTestOldSecret);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ callback(nil, [FIRAuthErrorUtils invalidAppCredentialWithMessage:nil]);
+ });
+ });
+
+ // Expect send verification code request to the backend, with request containing new app
+ // credential data.
+ OCMExpect([_mockBackend sendVerificationCode:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRSendVerificationCodeRequest *request,
+ FIRSendVerificationCodeResponseCallback callback) {
+ XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
+ XCTAssertEqualObjects(request.appCredential.receipt, kTestReceipt);
+ XCTAssertEqualObjects(request.appCredential.secret, kTestSecret);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockSendVerificationCodeResponse = OCMClassMock([FIRSendVerificationCodeResponse class]);
+ OCMStub([mockSendVerificationCodeResponse verificationID]).andReturn(kTestVerificationID);
+ callback(mockSendVerificationCodeResponse, nil);
+ });
+ });
+
+ OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
+ .andCallBlock1(^(FIRAuthAPNSTokenCallback callback) { callback(token); });
+ // Expect verify client request to the backend.
+ OCMExpect([_mockBackend verifyClient:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRVerifyClientRequest *request,
+ FIRVerifyClientResponseCallback callback) {
+ XCTAssertEqualObjects(request.appToken, @"21402324255E");
+ XCTAssertFalse(request.isSandbox);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockVerifyClientResponse = OCMClassMock([FIRVerifyClientResponse class]);
+ OCMStub([mockVerifyClientResponse receipt]).andReturn(kTestReceipt);
+ OCMStub([mockVerifyClientResponse suggestedTimeOutDate])
+ .andReturn([NSDate dateWithTimeIntervalSinceNow:kTestTimeout]);
+ callback(mockVerifyClientResponse, nil);
+ });
+ });
+
+ // Mock receiving of push notification.
+ OCMStub([[_mockAppCredentialManager ignoringNonObjectArgs]
+ didStartVerificationWithReceipt:OCMOCK_ANY timeout:0 callback:OCMOCK_ANY])
+ .andCallIdDoubleIdBlock(^(NSString *receipt,
+ NSTimeInterval timeout,
+ FIRAuthAppCredentialCallback callback) {
+ XCTAssertEqualObjects(receipt, kTestReceipt);
+ // Unfortunately 'ignoringNonObjectArgs' means the real value for 'timeout' doesn't get passed
+ // into the block either, so we can't verify it here.
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ callback([[FIRAuthAppCredential alloc] initWithReceipt:kTestReceipt secret:kTestSecret]);
+ });
+ });
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [_provider verifyPhoneNumber:kTestPhoneNumber
+ completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
+ XCTAssertNil(error);
+ XCTAssertEqualObjects(verificationID, kTestVerificationID);
+ XCTAssertEqualObjects(verificationID.fir_authPhoneNumber, kTestPhoneNumber);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+ OCMVerifyAll(_mockNotificationManager);
+ OCMVerifyAll(_mockAppCredentialManager);
+ OCMVerifyAll(_mockAPNSTokenManager);
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Example/Auth/Tests/FIRResetPasswordRequestTests.m b/Example/Auth/Tests/FIRResetPasswordRequestTests.m
new file mode 100644
index 0000000..d0ccc5d
--- /dev/null
+++ b/Example/Auth/Tests/FIRResetPasswordRequestTests.m
@@ -0,0 +1,101 @@
+/*
+ * 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 "FIRAuthErrors.h"
+#import "FIRAuthBackend.h"
+#import "FIRResetPasswordRequest.h"
+#import "FIRResetPasswordResponse.h"
+#import "FIRFakeBackendRPCIssuer.h"
+
+/** @var kTestAPIKey
+ @brief Fake API key used for testing.
+ */
+static NSString *const kTestAPIKey = @"APIKey";
+
+/** @var kTestOOBCode
+ @brief Fake OOBCode used for testing.
+ */
+static NSString *const kTestOOBCode = @"OOBCode";
+
+/** @var kTestNewPassword
+ @brief Fake new password used for testing.
+ */
+static NSString *const kTestNewPassword = @"newPassword:-)";
+
+/** @var kOOBCodeKey
+ @brief The "resetPassword" key.
+ */
+static NSString *const kOOBCodeKey = @"oobCode";
+
+/** @var knewPasswordKey
+ @brief The "newPassword" key.
+ */
+static NSString *const knewPasswordKey = @"newPassword";
+
+/** @var kExpectedAPIURL
+ @brief The expected URL for test calls.
+ */
+static NSString *const kExpectedAPIURL =
+ @"https://www.googleapis.com/identitytoolkit/v3/relyingparty/resetPassword?key=APIKey";
+
+/** @class FIRResetPasswordRequestTests
+ @brief Tests for @c FIRResetPasswordRequest.
+ */
+@interface FIRResetPasswordRequestTest : XCTestCase
+@end
+
+@implementation FIRResetPasswordRequestTest {
+ /** @var _RPCIssuer
+ @brief This backend RPC issuer is used to fake network responses for each test in the suite.
+ In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it.
+ */
+ FIRFakeBackendRPCIssuer *_RPCIssuer;
+}
+
+- (void)setUp {
+ [super setUp];
+ FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init];
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer];
+ _RPCIssuer = RPCIssuer;
+}
+
+- (void)tearDown {
+ _RPCIssuer = nil;
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
+ [super tearDown];
+}
+
+/** @fn testResetPasswordRequest
+ @brief Tests the reset password reqeust.
+ */
+- (void)testResetPasswordRequest {
+ FIRResetPasswordRequest *request =
+ [[FIRResetPasswordRequest alloc] initWithAPIKey:kTestAPIKey
+ oobCode:kTestOOBCode
+ newPassword:kTestNewPassword];
+ [FIRAuthBackend resetPassword:request callback:^(FIRResetPasswordResponse *_Nullable response,
+ NSError *_Nullable error) {
+
+ }];
+ XCTAssertEqualObjects(_RPCIssuer.requestURL.absoluteString, kExpectedAPIURL);
+ XCTAssertNotNil(_RPCIssuer.decodedRequest);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[knewPasswordKey], kTestNewPassword);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kOOBCodeKey], kTestOOBCode);
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRResetPasswordResponseTests.m b/Example/Auth/Tests/FIRResetPasswordResponseTests.m
new file mode 100644
index 0000000..51f1155
--- /dev/null
+++ b/Example/Auth/Tests/FIRResetPasswordResponseTests.m
@@ -0,0 +1,257 @@
+/*
+ * 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 "FIRAuthErrors.h"
+#import "FIRAuthBackend.h"
+#import "FIRResetPasswordRequest.h"
+#import "FIRResetPasswordResponse.h"
+#import "FIRFakeBackendRPCIssuer.h"
+
+/** @var kTestAPIKey
+ @brief Fake API key used for testing.
+ */
+static NSString *const kTestAPIKey = @"APIKey";
+
+/** @var kUserDisabledErrorMessage
+ @brief This is the error message the server will respond with if the user's account has been
+ disabled.
+ */
+static NSString *const kUserDisabledErrorMessage = @"USER_DISABLED";
+
+/** @var kOperationNotAllowedErrorMessage
+ @brief This is the error message the server will respond with if Admin disables IDP specified by
+ provider.
+ */
+static NSString *const kOperationNotAllowedErrorMessage = @"OPERATION_NOT_ALLOWED";
+
+/** @var kExpiredActionCodeErrorMessage
+ @brief This is the error message the server will respond with if the action code is expired.
+ */
+static NSString *const kExpiredActionCodeErrorMessage = @"EXPIRED_OOB_CODE";
+
+/** @var kInvalidActionCodeErrorMessage
+ @brief This is the error message the server will respond with if the action code is invalid.
+ */
+static NSString *const kInvalidActionCodeErrorMessage = @"INVALID_OOB_CODE";
+
+/** @var kWeakPasswordErrorMessagePrefix
+ @brief This is the prefix for the error message the server responds with if user's new password
+ to be set is too weak.
+ */
+static NSString *const kWeakPasswordErrorMessagePrefix = @"WEAK_PASSWORD : ";
+
+/** @var kTestOOBCode
+ @brief Fake OOBCode used for testing.
+ */
+static NSString *const kTestOOBCode = @"OOBCode";
+
+/** @var kTestNewPassword
+ @brief Fake new password used for testing.
+ */
+static NSString *const kTestNewPassword = @"newPassword";
+
+/** @var kEmailKey
+ @brief The key for the email returned in the response.
+ */
+static NSString *const kEmailKey = @"email";
+
+/** @var kRequestTypeKey
+ @brief The key for the request type returned in the response.
+ */
+static NSString *const kRequestTypeKey = @"requestType";
+
+/** @var kTestEmail
+ @brief The email returned in the response.
+ */
+static NSString *const kTestEmail = @"test@email.com";
+
+/** @var kResetPasswordExpectedRequestType.
+ @brief The expected request type returned for reset password request.
+ */
+static NSString *const kExpectedResetPasswordRequestType = @"PASSWORD_RESET";
+
+/** @class FIRResetPasswordRequestTests
+ @brief Tests for @c FIRResetPasswordRequest.
+ */
+@interface FIRResetPasswordResponseTests : XCTestCase
+@end
+
+@implementation FIRResetPasswordResponseTests {
+ /** @var _RPCIssuer
+ @brief This backend RPC issuer is used to fake network responses for each test in the suite.
+ In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it.
+ */
+ FIRFakeBackendRPCIssuer *_RPCIssuer;
+}
+
+- (void)setUp {
+ [super setUp];
+ FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init];
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer];
+ _RPCIssuer = RPCIssuer;
+}
+
+- (void)tearDown {
+ _RPCIssuer = nil;
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
+ [super tearDown];
+}
+
+/** @fn testUserDisabledError
+ @brief Tests for @c FIRAuthErrorCodeUserDisabled.
+ */
+- (void)testUserDisabledError {
+ FIRResetPasswordRequest *request =
+ [[FIRResetPasswordRequest alloc] initWithAPIKey:kTestAPIKey
+ oobCode:kTestOOBCode
+ newPassword:kTestNewPassword];
+ __block BOOL callbackInvoked;
+ __block FIRResetPasswordResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend resetPassword:request callback:^(FIRResetPasswordResponse *_Nullable response,
+ NSError *_Nullable error) {
+ RPCResponse = response;
+ RPCError = error;
+ callbackInvoked = YES;
+ }];
+ [_RPCIssuer respondWithServerErrorMessage:kUserDisabledErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeUserDisabled);
+}
+
+/** @fn testOperationNotAllowedError
+ @brief Tests for @c FIRAuthErrorCodeOperationNotAllowed.
+ */
+- (void)testOperationNotAllowedError {
+ FIRResetPasswordRequest *request =
+ [[FIRResetPasswordRequest alloc] initWithAPIKey:kTestAPIKey
+ oobCode:kTestOOBCode
+ newPassword:kTestNewPassword];
+ __block BOOL callbackInvoked;
+ __block FIRResetPasswordResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend resetPassword:request callback:^(FIRResetPasswordResponse *_Nullable response,
+ NSError *_Nullable error) {
+ RPCResponse = response;
+ RPCError = error;
+ callbackInvoked = YES;
+ }];
+ [_RPCIssuer respondWithServerErrorMessage:kOperationNotAllowedErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeOperationNotAllowed);
+}
+
+/** @fn testOOBExpiredError
+ @brief Tests for @c FIRAuthErrorCodeExpiredActionCode.
+ */
+- (void)testOOBExpiredError {
+ FIRResetPasswordRequest *request =
+ [[FIRResetPasswordRequest alloc] initWithAPIKey:kTestAPIKey
+ oobCode:kTestOOBCode
+ newPassword:kTestNewPassword];
+ __block BOOL callbackInvoked;
+ __block FIRResetPasswordResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend resetPassword:request callback:^(FIRResetPasswordResponse *_Nullable response,
+ NSError *_Nullable error) {
+ RPCResponse = response;
+ RPCError = error;
+ callbackInvoked = YES;
+ }];
+ [_RPCIssuer respondWithServerErrorMessage:kExpiredActionCodeErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeExpiredActionCode);
+}
+
+/** @fn testOOBInvalidError
+ @brief Tests for @c FIRAuthErrorCodeInvalidActionCode.
+ */
+- (void)testOOBInvalidError {
+ FIRResetPasswordRequest *request =
+ [[FIRResetPasswordRequest alloc] initWithAPIKey:kTestAPIKey
+ oobCode:kTestOOBCode
+ newPassword:kTestNewPassword];
+ __block BOOL callbackInvoked;
+ __block FIRResetPasswordResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend resetPassword:request callback:^(FIRResetPasswordResponse *_Nullable response,
+ NSError *_Nullable error) {
+ RPCResponse = response;
+ RPCError = error;
+ callbackInvoked = YES;
+ }];
+ [_RPCIssuer respondWithServerErrorMessage:kInvalidActionCodeErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeInvalidActionCode);
+}
+
+/** @fn testWeakPasswordError
+ @brief Tests for @c FIRAuthErrorCodeWeakPassword.
+ */
+- (void)testWeakPasswordError {
+ FIRResetPasswordRequest *request =
+ [[FIRResetPasswordRequest alloc] initWithAPIKey:kTestAPIKey
+ oobCode:kTestOOBCode
+ newPassword:kTestNewPassword];
+ __block BOOL callbackInvoked;
+ __block FIRResetPasswordResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend resetPassword:request callback:^(FIRResetPasswordResponse *_Nullable response,
+ NSError *_Nullable error) {
+ RPCResponse = response;
+ RPCError = error;
+ callbackInvoked = YES;
+ }];
+ [_RPCIssuer respondWithServerErrorMessage:kWeakPasswordErrorMessagePrefix];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeWeakPassword);
+}
+
+/** @fn testSuccessfulResetPassword
+ @brief Tests a successful reset password flow.
+ */
+- (void)testSuccessfulResetPassword {
+ FIRResetPasswordRequest *request =
+ [[FIRResetPasswordRequest alloc] initWithAPIKey:kTestAPIKey
+ oobCode:kTestOOBCode
+ newPassword:kTestNewPassword];
+ __block BOOL callbackInvoked;
+ __block FIRResetPasswordResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend resetPassword:request callback:^(FIRResetPasswordResponse *_Nullable response,
+ NSError *_Nullable error) {
+ RPCResponse = response;
+ RPCError = error;
+ callbackInvoked = YES;
+ }];
+ [_RPCIssuer respondWithJSON:@{
+ kEmailKey : kTestEmail,
+ kRequestTypeKey : kExpectedResetPasswordRequestType
+ }];
+ XCTAssert(callbackInvoked);
+ XCTAssertEqualObjects(RPCResponse.email, kTestEmail);
+ XCTAssertEqualObjects(RPCResponse.requestType, kExpectedResetPasswordRequestType);
+ XCTAssertNil(RPCError);
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRSendVerificationCodeRequestTests.m b/Example/Auth/Tests/FIRSendVerificationCodeRequestTests.m
new file mode 100644
index 0000000..5582d32
--- /dev/null
+++ b/Example/Auth/Tests/FIRSendVerificationCodeRequestTests.m
@@ -0,0 +1,119 @@
+/*
+ * 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 "FIRAuthAppCredential.h"
+#import "FIRAuthBackend.h"
+#import "FIRSendVerificationCodeRequest.h"
+#import "FIRSendVerificationCodeResponse.h"
+#import "FIRFakeBackendRPCIssuer.h"
+
+/** @var kTestAPIKey
+ @brief Fake API key used for testing.
+ */
+static NSString *const kTestAPIKey = @"APIKey";
+
+/** @var kTestPhoneNumber
+ @brief Fake phone number used for testing.
+ */
+static NSString *const kTestPhoneNumber = @"12345678";
+
+/** @var kTestSecret
+ @brief Fake secret used for testing.
+ */
+static NSString *const kTestSecret = @"secret";
+
+/** @var kTestReceipt
+ @brief Fake receipt used for testing.
+ */
+static NSString *const kTestReceipt = @"receipt";
+
+/** @var kPhoneNumberKey
+ @brief The key for the "phone number" value in the request.
+ */
+static NSString *const kPhoneNumberKey = @"phoneNumber";
+
+/** @var kReceiptKey
+ @brief The key for the receipt parameter in the request.
+ */
+static NSString *const kReceiptKey = @"iosReceipt";
+
+/** @var kSecretKey
+ @brief The key for the Secret parameter in the request.
+ */
+static NSString *const kSecretKey = @"iosSecret";
+
+/** @var kExpectedAPIURL
+ @brief The expected URL for the test calls.
+ */
+static NSString *const kExpectedAPIURL =
+ @"https://www.googleapis.com/identitytoolkit/v3/relyingparty/sendVerificationCode?key=APIKey";
+
+/** @class FIRSendVerificationCodeRequestTests
+ @brief Tests for @c FIRSendVerificationCodeRequest.
+ */
+@interface FIRSendVerificationCodeRequestTests : XCTestCase
+@end
+
+@implementation FIRSendVerificationCodeRequestTests {
+ /** @var _RPCIssuer
+ @brief This backend RPC issuer is used to fake network responses for each test in the suite.
+ In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it.
+ */
+ FIRFakeBackendRPCIssuer *_RPCIssuer;
+}
+
+- (void)setUp {
+ [super setUp];
+ FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init];
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer];
+ _RPCIssuer = RPCIssuer;
+}
+
+- (void)tearDown {
+ _RPCIssuer = nil;
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
+ [super tearDown];
+}
+
+/** @fn testSendVerificationCodeRequest
+ @brief Tests the sendVerificationCode request.
+ */
+- (void)testSendVerificationCodeRequest {
+ FIRAuthAppCredential *credential =
+ [[FIRAuthAppCredential alloc]initWithReceipt:kTestReceipt secret:kTestSecret];
+ FIRSendVerificationCodeRequest *request =
+ [[FIRSendVerificationCodeRequest alloc] initWithPhoneNumber:kTestPhoneNumber
+ appCredential:credential
+ APIKey:kTestAPIKey];
+ XCTAssertEqualObjects(request.phoneNumber, kTestPhoneNumber);
+ XCTAssertEqualObjects(request.appCredential.receipt, kTestReceipt);
+ XCTAssertEqualObjects(request.appCredential.secret, kTestSecret);
+
+ [FIRAuthBackend sendVerificationCode:request
+ callback:^(FIRSendVerificationCodeResponse *_Nullable response,
+ NSError *_Nullable error) {
+ }];
+ XCTAssertEqualObjects(_RPCIssuer.requestURL.absoluteString, kExpectedAPIURL);
+ XCTAssertNotNil(_RPCIssuer.decodedRequest);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kPhoneNumberKey], kTestPhoneNumber);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kPhoneNumberKey], kTestPhoneNumber);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kReceiptKey], kTestReceipt);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kSecretKey], kTestSecret);
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRSendVerificationCodeResponseTests.m b/Example/Auth/Tests/FIRSendVerificationCodeResponseTests.m
new file mode 100644
index 0000000..5a1244b
--- /dev/null
+++ b/Example/Auth/Tests/FIRSendVerificationCodeResponseTests.m
@@ -0,0 +1,221 @@
+/*
+ * 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 "FIRAuthAppCredential.h"
+#import "FIRAuthErrors.h"
+#import "FIRAuthErrorUtils.h"
+#import "FIRAuthBackend.h"
+#import "FIRSendVerificationCodeRequest.h"
+#import "FIRSendVerificationCodeResponse.h"
+#import "FIRFakeBackendRPCIssuer.h"
+
+/** @var kTestAPIKey
+ @brief Fake API key used for testing.
+ */
+static NSString *const kTestAPIKey = @"APIKey";
+
+/** @var kTestPhoneNumber
+ @brief Fake phone number used for testing.
+ */
+static NSString *const kTestPhoneNumber = @"12345678";
+
+/** @var kTestInvalidPhoneNumber
+ @brief An invalid testing phone number.
+ */
+static NSString *const kTestInvalidPhoneNumber = @"555+!*55555";
+
+/** @var kVerificationIDKey
+ @brief Fake key for the test verification ID.
+ */
+static NSString *const kVerificationIDKey = @"sessionInfo";
+
+/** @var kFakeVerificationID
+ @brief Fake verification ID for testing.
+ */
+static NSString *const kFakeVerificationID = @"testVerificationID";
+
+/** @var kTestSecret
+ @brief Fake secret used for testing.
+ */
+static NSString *const kTestSecret = @"secret";
+
+/** @var kTestReceipt
+ @brief Fake receipt used for testing.
+ */
+static NSString *const kTestReceipt = @"receipt";
+
+/** @var kInvalidPhoneNumberErrorMessage
+ @brief This is the error message the server will respond with if an incorrectly formatted phone
+ number is provided.
+ */
+static NSString *const kInvalidPhoneNumberErrorMessage = @"INVALID_PHONE_NUMBER";
+
+/** @var kQuotaExceededErrorMessage
+ @brief This is the error message the server will respond with if the quota for SMS text messages
+ has been exceeded for the project.
+ */
+static NSString *const kQuotaExceededErrorMessage = @"QUOTA_EXCEEDED";
+
+/** @var kAppNotVerifiedErrorMessage
+ @brief This is the error message the server will respond with if Firebase could not verify the
+ app during a phone authentication flow.
+ */
+static NSString *const kAppNotVerifiedErrorMessage = @"APP_NOT_VERIFIED";
+
+/** @class FIRSendVerificationCodeResponseTests
+ @brief Tests for @c FIRSendVerificationCodeResponseTests.
+ */
+@interface FIRSendVerificationCodeResponseTests : XCTestCase
+@end
+
+@implementation FIRSendVerificationCodeResponseTests {
+ /** @var _RPCIssuer
+ @brief This backend RPC issuer is used to fake network responses for each test in the suite.
+ In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it.
+ */
+ FIRFakeBackendRPCIssuer *_RPCIssuer;
+}
+
+- (void)setUp {
+ FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init];
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer];
+ _RPCIssuer = RPCIssuer;
+}
+
+- (void)tearDown {
+ _RPCIssuer = nil;
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
+ [super tearDown];
+}
+
+/** @fn testSendVerificationCodeResponseInvalidPhoneNumber
+ @brief Tests a failed attempt to send a verification code with an invalid phone number.
+ */
+- (void)testSendVerificationCodeResponseInvalidPhoneNumber {
+ FIRAuthAppCredential *credential =
+ [[FIRAuthAppCredential alloc]initWithReceipt:kTestReceipt secret:kTestSecret];
+ FIRSendVerificationCodeRequest *request =
+ [[FIRSendVerificationCodeRequest alloc] initWithPhoneNumber:kTestInvalidPhoneNumber
+ appCredential:credential
+ APIKey:kTestAPIKey];
+ __block BOOL callbackInvoked;
+ __block FIRSendVerificationCodeResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend sendVerificationCode:request
+ callback:^(FIRSendVerificationCodeResponse *_Nullable response,
+ NSError *_Nullable error) {
+ RPCResponse = response;
+ RPCError = error;
+ callbackInvoked = YES;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kInvalidPhoneNumberErrorMessage];
+
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeInvalidPhoneNumber);
+}
+
+/** @fn testSendVerificationCodeResponseQuotaExceededError
+ @brief Tests a failed attempt to send a verification code due to SMS quota having been exceeded.
+ */
+- (void)testSendVerificationCodeResponseQuotaExceededError {
+ FIRAuthAppCredential *credential =
+ [[FIRAuthAppCredential alloc]initWithReceipt:kTestReceipt secret:kTestSecret];
+ FIRSendVerificationCodeRequest *request =
+ [[FIRSendVerificationCodeRequest alloc] initWithPhoneNumber:kTestPhoneNumber
+ appCredential:credential
+ APIKey:kTestAPIKey];
+ __block BOOL callbackInvoked;
+ __block FIRSendVerificationCodeResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend sendVerificationCode:request
+ callback:^(FIRSendVerificationCodeResponse *_Nullable response,
+ NSError *_Nullable error) {
+ RPCResponse = response;
+ RPCError = error;
+ callbackInvoked = YES;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kQuotaExceededErrorMessage];
+
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeQuotaExceeded);
+}
+
+/** @fn testSendVerificationCodeResponseAppNotVerifiedError
+ @brief Tests a failed attempt to send a verification code due to Firebase not being able to
+ verify the app.
+ */
+- (void)testSendVerificationCodeResponseAppNotVerifiedError {
+ FIRAuthAppCredential *credential =
+ [[FIRAuthAppCredential alloc]initWithReceipt:kTestReceipt secret:kTestSecret];
+ FIRSendVerificationCodeRequest *request =
+ [[FIRSendVerificationCodeRequest alloc] initWithPhoneNumber:kTestPhoneNumber
+ appCredential:credential
+ APIKey:kTestAPIKey];
+ __block BOOL callbackInvoked;
+ __block FIRSendVerificationCodeResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend sendVerificationCode:request
+ callback:^(FIRSendVerificationCodeResponse *_Nullable response,
+ NSError *_Nullable error) {
+ RPCResponse = response;
+ RPCError = error;
+ callbackInvoked = YES;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kAppNotVerifiedErrorMessage];
+
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeAppNotVerified);
+}
+
+/** @fn testSuccessfulSendVerificationCodeResponse
+ @brief Tests a succesful to send a verification code.
+ */
+- (void)testSuccessfulSendVerificationCodeResponse {
+ FIRAuthAppCredential *credential =
+ [[FIRAuthAppCredential alloc]initWithReceipt:kTestReceipt secret:kTestSecret];
+ FIRSendVerificationCodeRequest *request =
+ [[FIRSendVerificationCodeRequest alloc] initWithPhoneNumber:kTestPhoneNumber
+ appCredential:credential
+ APIKey:kTestAPIKey];
+ __block BOOL callbackInvoked;
+ __block FIRSendVerificationCodeResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend sendVerificationCode:request
+ callback:^(FIRSendVerificationCodeResponse *_Nullable response,
+ NSError *_Nullable error) {
+ RPCResponse = response;
+ RPCError = error;
+ callbackInvoked = YES;
+ }];
+
+ [_RPCIssuer respondWithJSON:@{
+ kVerificationIDKey : kFakeVerificationID
+ }];
+
+ XCTAssert(callbackInvoked);
+ XCTAssertNotNil(RPCResponse);
+ XCTAssertEqualObjects(RPCResponse.verificationID, kFakeVerificationID);
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRSetAccountInfoRequestTests.m b/Example/Auth/Tests/FIRSetAccountInfoRequestTests.m
new file mode 100644
index 0000000..54d8ff0
--- /dev/null
+++ b/Example/Auth/Tests/FIRSetAccountInfoRequestTests.m
@@ -0,0 +1,285 @@
+/*
+ * 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 "FIRAuthErrors.h"
+#import "FIRAuthBackend.h"
+#import "FIRSetAccountInfoRequest.h"
+#import "FIRSetAccountInfoResponse.h"
+#import "FIRFakeBackendRPCIssuer.h"
+
+/** @var kTestAPIKey
+ @brief Fake API key used for testing.
+ */
+static NSString *const kTestAPIKey = @"APIKey";
+
+/** @var kIDTokenKey
+ @brief The key for the "idToken" value in the request. This is actually the STS Access Token,
+ despite it's confusing (backwards compatiable) parameter name.
+ */
+static NSString *const kIDTokenKey = @"idToken";
+
+/** @var kTestAccessToken
+ @bried Fake acess token for testing.
+ */
+ static NSString *const kTestAccessToken = @"accessToken";
+
+/** @var kDisplayNameKey
+ @brief The key for the "displayName" value in the request.
+ */
+static NSString *const kDisplayNameKey = @"displayName";
+
+/** @var kTestDisplayName
+ @brief The fake @c displayName for testing.
+ */
+static NSString *const kTestDisplayName = @"testDisplayName";
+
+/** @var kLocalIDKey
+ @brief The key for the "localID" value in the request.
+ */
+static NSString *const kLocalIDKey = @"localId";
+
+/** @var kTestLocalID
+ @brief The fake @c localID for testing in the request.
+ */
+static NSString *const kTestLocalID = @"testLocalId";
+
+/** @var kEmailKey
+ @brief The key for the "email" value in the request.
+ */
+static NSString *const kEmailKey = @"email";
+
+/** @var kTestEmail
+ @brief The fake @c email used for testing in the request.
+ */
+static NSString *const ktestEmail = @"testEmail";
+
+/** @var kPasswordKey
+ @brief The key for the "password" value in the request.
+ */
+static NSString *const kPasswordKey = @"password";
+
+/** @var kTestPassword
+ @brief The fake @c password used for testing in the request.
+ */
+static NSString *const kTestPassword = @"testPassword";
+
+/** @var kPhotoURLKey
+ @brief The key for the "photoURL" value in the request.
+ */
+static NSString *const kPhotoURLKey = @"photoUrl";
+
+/** @var kTestPhotoURL
+ @brief The fake photoUrl for testing in the request.
+ */
+static NSString *const kTestPhotoURL = @"testPhotoUrl";
+
+/** @var kProvidersKey
+ @brief The key for the "providers" value in the request.
+ */
+static NSString *const kProvidersKey = @"provider";
+
+/** @var kTestProviders
+ @brief The fake @c providers value used for testing in the request.
+ */
+static NSString *const kTestProviders = @"testProvider";
+
+/** @var kOOBCodeKey
+ @brief The key for the "OOBCode" value in the request.
+ */
+static NSString *const kOOBCodeKey = @"oobCode";
+
+/** @var kTestOOBCode
+ @brief The fake @c OOBCode used for testing the request.
+ */
+static NSString *const kTestOOBCode = @"testOobCode";
+
+/** @var kEmailVerifiedKey
+ @brief The key for the "emailVerified" value in the request.
+ */
+static NSString *const kEmailVerifiedKey = @"emailVerified";
+
+/** @var kTestEmailVerified
+ @brief The fake @c emailVerified value used for testing the request.
+ */
+static const BOOL kTestEmailVerified = YES;
+
+/** @var kUpgradeToFederatedLoginKey
+ @brief The key for the "upgradeToFederatedLogin" value in the request.
+ */
+static NSString *const kUpgradeToFederatedLoginKey = @"upgradeToFederatedLogin";
+
+/** @var kTestUpgradeToFederatedLogin
+ @brief The fake @c upgradeToFederatedLogin value for testing the request.
+ */
+static const BOOL kTestUpgradeToFederatedLogin = YES;
+
+/** @var kCaptchaChallengeKey
+ @brief The key for the "captchaChallenge" value in the request.
+ */
+static NSString *const kCaptchaChallengeKey = @"captchaChallenge";
+
+/** @var kTestCaptchaChallenge
+ @brief The fake @c captchaChallenge for testing in the request.
+ */
+static NSString *const kTestCaptchaChallenge = @"TestCaptchaChallenge";
+
+/** @var kCaptchaResponseKey
+ @brief The key for the "captchaResponse" value the request.
+ */
+static NSString *const kCaptchaResponseKey = @"captchaResponse";
+
+/** @var kTestCaptchaResponse
+ @brief The fake @c captchaResponse for testing the request.
+ */
+static NSString *const kTestCaptchaResponse = @"TestCaptchaResponse";
+
+/** @var kDeleteAttributesKey
+ @brief The key for the "deleteAttribute" value in the request.
+ */
+static NSString *const kDeleteAttributesKey = @"deleteAttribute";
+
+/** @var kTestDeleteAttributes
+ @brief The fake @c deleteAttribute value for testing the request.
+ */
+static NSString *const kTestDeleteAttributes = @"TestDeleteAttributes";
+
+/** @var kDeleteProvidersKey
+ @brief The key for the "deleteProvider" value in the request.
+ */
+static NSString *const kDeleteProvidersKey = @"deleteProvider";
+
+/** @var kTestDeleteProviders
+ @brief The fake @c deleteProviders for testing the request.
+ */
+static NSString *const kTestDeleteProviders = @"TestDeleteProviders";
+
+/** @var kReturnSecureTokenKey
+ @brief The key for the "returnSecureToken" value in the request.
+ */
+static NSString *const kReturnSecureTokenKey = @"returnSecureToken";
+
+/** @var kExpectedAPIURL
+ @brief The expected URL for test calls.
+ */
+static NSString *const kExpectedAPIURL =
+ @"https://www.googleapis.com/identitytoolkit/v3/relyingparty/setAccountInfo?key=APIKey";
+
+/** @class FIRSetAccountInfoRequestTests
+ @brief Tests for @c FIRSetAccountInfoRequest.
+ */
+@interface FIRSetAccountInfoRequestTests : XCTestCase
+@end
+@implementation FIRSetAccountInfoRequestTests {
+ /** @var _RPCIssuer
+ @brief This backend RPC issuer is used to fake network responses for each test in the suite.
+ In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it.
+ */
+ FIRFakeBackendRPCIssuer *_RPCIssuer;
+}
+
+- (void)setUp {
+ [super setUp];
+ FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init];
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer];
+ _RPCIssuer = RPCIssuer;
+}
+
+- (void)tearDown {
+ _RPCIssuer = nil;
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
+ [super tearDown];
+}
+
+/** @fn testSetAccountInfoRequest
+ @brief Tests the set account info request.
+ */
+- (void)testSetAccountInfoRequest {
+ FIRSetAccountInfoRequest *request = [[FIRSetAccountInfoRequest alloc] initWithAPIKey:kTestAPIKey];
+ request.returnSecureToken = NO;
+ [FIRAuthBackend setAccountInfo:request
+ callback:^(FIRSetAccountInfoResponse *_Nullable response,
+ NSError *_Nullable error) {
+ }];
+
+ XCTAssertNotNil(_RPCIssuer.decodedRequest);
+ XCTAssert([_RPCIssuer.decodedRequest isKindOfClass:[NSDictionary class]]);
+ XCTAssertNil(_RPCIssuer.decodedRequest[kIDTokenKey]);
+ XCTAssertNil(_RPCIssuer.decodedRequest[kDisplayNameKey]);
+ XCTAssertNil(_RPCIssuer.decodedRequest[kLocalIDKey]);
+ XCTAssertNil(_RPCIssuer.decodedRequest[kEmailKey]);
+ XCTAssertNil(_RPCIssuer.decodedRequest[kPasswordKey]);
+ XCTAssertNil(_RPCIssuer.decodedRequest[kPhotoURLKey]);
+ XCTAssertNil(_RPCIssuer.decodedRequest[kProvidersKey]);
+ XCTAssertNil(_RPCIssuer.decodedRequest[kOOBCodeKey]);
+ XCTAssertNil(_RPCIssuer.decodedRequest[kEmailVerifiedKey]);
+ XCTAssertNil(_RPCIssuer.decodedRequest[kUpgradeToFederatedLoginKey]);
+ XCTAssertNil(_RPCIssuer.decodedRequest[kCaptchaChallengeKey]);
+ XCTAssertNil(_RPCIssuer.decodedRequest[kCaptchaResponseKey]);
+ XCTAssertNil(_RPCIssuer.decodedRequest[kDeleteAttributesKey]);
+ XCTAssertNil(_RPCIssuer.decodedRequest[kDeleteProvidersKey]);
+ XCTAssertNil(_RPCIssuer.decodedRequest[kReturnSecureTokenKey]);
+ XCTAssertEqualObjects(_RPCIssuer.requestURL.absoluteString, kExpectedAPIURL);
+}
+
+/** @fn testSetAccountInfoRequestOptionalFields
+ @brief Tests the set account info request with optional fields.
+ */
+- (void)testSetAccountInfoRequestOptionalFields {
+ FIRSetAccountInfoRequest *request = [[FIRSetAccountInfoRequest alloc] initWithAPIKey:kTestAPIKey];
+ request.accessToken = kTestAccessToken;
+ request.displayName = kTestDisplayName;
+ request.localID = kTestLocalID;
+ request.email = ktestEmail;
+ request.password = kTestPassword;
+ request.providers = @[ kTestProviders ];
+ request.OOBCode = kTestOOBCode;
+ request.emailVerified = kTestEmailVerified;
+ request.photoURL = [NSURL URLWithString:kTestPhotoURL];
+ request.upgradeToFederatedLogin = kTestUpgradeToFederatedLogin;
+ request.captchaChallenge = kTestCaptchaChallenge;
+ request.captchaResponse = kTestCaptchaResponse;
+ request.deleteAttributes = @[ kTestDeleteAttributes ];
+ request.deleteProviders = @[ kTestDeleteProviders ];
+
+ [FIRAuthBackend setAccountInfo:request
+ callback:^(FIRSetAccountInfoResponse *_Nullable response,
+ NSError *_Nullable error) {
+ }];
+
+ XCTAssertNotNil(_RPCIssuer.decodedRequest);
+ XCTAssert([_RPCIssuer.decodedRequest isKindOfClass:[NSDictionary class]]);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kIDTokenKey], kTestAccessToken);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kDisplayNameKey], kTestDisplayName);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kLocalIDKey], kTestLocalID);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kEmailKey], ktestEmail);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kPasswordKey], kTestPassword);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kPhotoURLKey], kTestPhotoURL);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kProvidersKey], @[ kTestProviders ]);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kOOBCodeKey], kTestOOBCode);
+ XCTAssert(_RPCIssuer.decodedRequest[kEmailVerifiedKey]);
+ XCTAssert(_RPCIssuer.decodedRequest[kUpgradeToFederatedLoginKey]);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kCaptchaChallengeKey], kTestCaptchaChallenge);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kCaptchaResponseKey], kTestCaptchaResponse);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kDeleteAttributesKey],
+ @[ kTestDeleteAttributes ]);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kDeleteProvidersKey], @[ kTestDeleteProviders ]);
+ XCTAssertTrue([_RPCIssuer.decodedRequest[kReturnSecureTokenKey] boolValue]);
+ XCTAssertEqualObjects(_RPCIssuer.requestURL.absoluteString, kExpectedAPIURL);
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRSetAccountInfoResponseTests.m b/Example/Auth/Tests/FIRSetAccountInfoResponseTests.m
new file mode 100644
index 0000000..d650f13
--- /dev/null
+++ b/Example/Auth/Tests/FIRSetAccountInfoResponseTests.m
@@ -0,0 +1,530 @@
+/*
+ * 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 "FIRAuthErrors.h"
+#import "FIRAuthBackend.h"
+#import "FIRSetAccountInfoRequest.h"
+#import "FIRSetAccountInfoResponse.h"
+#import "FIRFakeBackendRPCIssuer.h"
+
+/** @var kTestAPIKey
+ @brief Fake API key used for testing.
+ */
+static NSString *const kTestAPIKey = @"APIKey";
+
+/** @var kEmailExistsErrorMessage
+ @brief This is the error message the server will respond with if the user entered an invalid
+ email address.
+ */
+static NSString *const kEmailExistsErrorMessage = @"EMAIL_EXISTS";
+
+/** @var kVerifiedProviderKey
+ @brief The name of the "VerifiedProvider" property in the response.
+ */
+static NSString *const kProviderUserInfoKey = @"providerUserInfo";
+
+/** @var kPhotoUrlKey
+ @brief The name of the "photoURL" property in the response.
+ */
+static NSString *const kPhotoUrlKey = @"photoUrl";
+
+/** @var kTestPhotoURL
+ @brief The fake photoUrl property value in the response.
+ */
+static NSString *const kTestPhotoURL = @"testPhotoURL";
+
+/** @var kIDTokenKey
+ @brief The name of the "IDToken" property in the response.
+ */
+static NSString *const kIDTokenKey = @"idToken";
+
+/** @var kTestIDToken
+ @brief Testing ID token for verifying assertion.
+ */
+static NSString *const kTestIDToken = @"ID_TOKEN";
+
+/** @var kExpiresInKey
+ @brief The name of the "expiresIn" property in the response.
+ */
+static NSString *const kExpiresInKey = @"expiresIn";
+
+/** @var kTestExpiresIn
+ @brief Fake token expiration time.
+ */
+static NSString *const kTestExpiresIn = @"12345";
+
+/** @var kRefreshTokenKey
+ @brief The name of the "refreshToken" property in the response.
+ */
+static NSString *const kRefreshTokenKey = @"refreshToken";
+
+/** @var kTestRefreshToken
+ @brief Fake refresh token.
+ */
+static NSString *const kTestRefreshToken = @"REFRESH_TOKEN";
+
+/** @var kEmailSignUpNotAllowedErrorMessage
+ @brief This is the error message the server will respond with if admin disables password
+ account.
+ */
+static NSString *const kEmailSignUpNotAllowedErrorMessage = @"OPERATION_NOT_ALLOWED";
+
+/** @var kPasswordLoginDisabledErrorMessage
+ @brief This is the error message the server responds with if password login is disabled.
+ */
+static NSString *const kPasswordLoginDisabledErrorMessage = @"PASSWORD_LOGIN_DISABLED";
+
+/** @var kCredentialTooOldErrorMessage
+ @brief This is the error message the server responds with if account change is attempted 5
+ minutes after signing in.
+ */
+static NSString *const kCredentialTooOldErrorMessage = @"CREDENTIAL_TOO_OLD_LOGIN_AGAIN";
+
+/** @var kinvalidUserTokenErrorMessage
+ @brief This is the error message the server will respond with if the user's saved auth
+ credential is invalid, the user has to sign-in again.
+ */
+static NSString *const kinvalidUserTokenErrorMessage = @"INVALID_ID_TOKEN";
+
+/** @var kUserDisabledErrorMessage
+ @brief This is the error message the server will respond with if the user's account has been
+ disabled.
+ */
+static NSString *const kUserDisabledErrorMessage = @"USER_DISABLED";
+
+/** @var kInvalidEmailErrorMessage
+ @brief The error returned by the server if the email is invalid.
+ */
+static NSString *const kInvalidEmailErrorMessage = @"INVALID_EMAIL";
+
+/** @var kWeakPasswordErrorMessage
+ @brief This is the error message the server will respond with if the user's new password
+ is too weak that it is too short.
+ */
+static NSString *const kWeakPasswordErrorMessage =
+ @"WEAK_PASSWORD : Password should be at least 6 characters";
+
+/** @var kWeakPasswordClientErrorMessage
+ @brief This is the error message the client will see if the user's new password is too weak
+ that it is too short.
+ @remarks This message should be derived from @c kWeakPasswordErrorMessage .
+ */
+static NSString *const kWeakPasswordClientErrorMessage =
+ @"Password should be at least 6 characters";
+
+/** @var kExpiredActionCodeErrorMessage
+ @brief This is the error message the server will respond with if the action code is expired.
+ */
+static NSString *const kExpiredActionCodeErrorMessage = @"EXPIRED_OOB_CODE:";
+
+/** @var kInvalidActionCodeErrorMessage
+ @brief This is the error message the server will respond with if the action code is invalid.
+ */
+static NSString *const kInvalidActionCodeErrorMessage = @"INVALID_OOB_CODE";
+
+/** @var kInvalidMessagePayloadErrorMessage
+ @brief This is the prefix for the error message the server responds with if an invalid message
+ payload was sent.
+ */
+static NSString *const kInvalidMessagePayloadErrorMessage = @"INVALID_MESSAGE_PAYLOAD";
+
+/** @var kInvalidSenderErrorMessage
+ @brief This is the prefix for the error message the server responds with if invalid sender is
+ used to send the email for updating user's email address.
+ */
+static NSString *const kInvalidSenderErrorMessage = @"INVALID_SENDER";
+
+/** @var kInvalidRecipientEmailErrorMessage
+ @brief This is the prefix for the error message the server responds with if the recipient email
+ is invalid.
+ */
+static NSString *const kInvalidRecipientEmailErrorMessage = @"INVALID_RECIPIENT_EMAIL";
+
+/** @var kEpsilon
+ @brief Allowed difference when comparing floating point numbers.
+ */
+static const double kEpsilon = 1e-3;
+
+/** @class FIRSetAccountInfoResponseTests
+ @brief Tests for @c FIRSetAccountInfoResponse.
+ */
+@interface FIRSetAccountInfoResponseTests : XCTestCase
+@end
+@implementation FIRSetAccountInfoResponseTests {
+ /** @var _RPCIssuer
+ @brief This backend RPC issuer is used to fake network responses for each test in the suite.
+ In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it.
+ */
+ FIRFakeBackendRPCIssuer *_RPCIssuer;
+}
+
+- (void)setUp {
+ [super setUp];
+ FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init];
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer];
+ _RPCIssuer = RPCIssuer;
+}
+
+- (void)tearDown {
+ _RPCIssuer = nil;
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
+ [super tearDown];
+}
+
+/** @fn testEmailExistsError
+ @brief This test simulates @c testSignUpNewUserEmailExistsError with @c
+ FIRAuthErrorCodeEmailExists error.
+ */
+- (void)testEmailExistsError {
+ FIRSetAccountInfoRequest *request = [[FIRSetAccountInfoRequest alloc] initWithAPIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRSetAccountInfoResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend setAccountInfo:request
+ callback:^(FIRSetAccountInfoResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kEmailExistsErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeEmailAlreadyInUse);
+}
+
+/** @fn testEmailSignUpNotAllowedError
+ @brief This test simulates @c testEmailSignUpNotAllowedError with @c
+ FIRAuthErrorCodeOperationNotAllowed error.
+ */
+- (void)testEmailSignUpNotAllowedError {
+ FIRSetAccountInfoRequest *request = [[FIRSetAccountInfoRequest alloc] initWithAPIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRSetAccountInfoResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend setAccountInfo:request
+ callback:^(FIRSetAccountInfoResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kEmailSignUpNotAllowedErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeOperationNotAllowed);
+}
+
+/** @fn testPasswordLoginDisabledError
+ @brief This test simulates @c passwordLoginDisabledError with @c
+ FIRAuthErrorCodeOperationNotAllowed error.
+ */
+- (void)testPasswordLoginDisabledError {
+ FIRSetAccountInfoRequest *request = [[FIRSetAccountInfoRequest alloc] initWithAPIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRSetAccountInfoResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend setAccountInfo:request
+ callback:^(FIRSetAccountInfoResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kPasswordLoginDisabledErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeOperationNotAllowed);
+}
+
+/** @fn testUserDisabledError
+ @brief This test simulates @c testUserDisabledError with @c FIRAuthErrorCodeUserDisabled error.
+ */
+- (void)testUserDisabledError {
+ FIRSetAccountInfoRequest *request = [[FIRSetAccountInfoRequest alloc] initWithAPIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRSetAccountInfoResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend setAccountInfo:request
+ callback:^(FIRSetAccountInfoResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kUserDisabledErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeUserDisabled);
+}
+
+/** @fn testInvalidUserTokenError
+ @brief This test simulates @c testinvalidUserTokenError with @c
+ FIRAuthErrorCodeCredentialTooOld error.
+ */
+- (void)testInvalidUserTokenError {
+ FIRSetAccountInfoRequest *request = [[FIRSetAccountInfoRequest alloc] initWithAPIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRSetAccountInfoResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend setAccountInfo:request
+ callback:^(FIRSetAccountInfoResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kinvalidUserTokenErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeInvalidUserToken);
+}
+
+/** @fn testrequiresRecentLogin
+ @brief This test simulates @c testCredentialTooOldError with @c
+ FIRAuthErrorCodeRequiresRecentLogin error.
+ */
+- (void)testrequiresRecentLogin {
+ FIRSetAccountInfoRequest *request = [[FIRSetAccountInfoRequest alloc] initWithAPIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRSetAccountInfoResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend setAccountInfo:request
+ callback:^(FIRSetAccountInfoResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kCredentialTooOldErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeRequiresRecentLogin);
+}
+
+/** @fn testWeakPasswordError
+ @brief This test simulates @c FIRAuthErrorCodeWeakPassword error.
+ */
+- (void)testWeakPasswordError {
+ FIRSetAccountInfoRequest *request = [[FIRSetAccountInfoRequest alloc] initWithAPIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRSetAccountInfoResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend setAccountInfo:request
+ callback:^(FIRSetAccountInfoResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kWeakPasswordErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeWeakPassword);
+ XCTAssertEqualObjects(RPCError.userInfo[NSLocalizedFailureReasonErrorKey],
+ kWeakPasswordClientErrorMessage);
+}
+
+/** @fn testInvalidEmailError
+ @brief This test simulates @c FIRAuthErrorCodeInvalidEmail error code.
+ */
+- (void)testInvalidEmailError {
+ FIRSetAccountInfoRequest *request = [[FIRSetAccountInfoRequest alloc] initWithAPIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRSetAccountInfoResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend setAccountInfo:request
+ callback:^(FIRSetAccountInfoResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kInvalidEmailErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeInvalidEmail);
+}
+
+/** @fn testInvalidActionCodeError
+ @brief This test simulates @c FIRAuthErrorCodeInvalidActionCode error code.
+ */
+- (void)testInvalidActionCodeError {
+ FIRSetAccountInfoRequest *request = [[FIRSetAccountInfoRequest alloc] initWithAPIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRSetAccountInfoResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend setAccountInfo:request
+ callback:^(FIRSetAccountInfoResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kInvalidActionCodeErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeInvalidActionCode);
+}
+
+/** @fn testExpiredActionCodeError
+ @brief This test simulates @c FIRAuthErrorCodeExpiredActionCode error code.
+ */
+- (void)testExpiredActionCodeError {
+ FIRSetAccountInfoRequest *request = [[FIRSetAccountInfoRequest alloc] initWithAPIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRSetAccountInfoResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend setAccountInfo:request
+ callback:^(FIRSetAccountInfoResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kExpiredActionCodeErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeExpiredActionCode);
+}
+
+/** @fn testInvalidMessagePayloadError
+ @brief Tests for @c FIRAuthErrorCodeInvalidMessagePayload.
+ */
+- (void)testInvalidMessagePayloadError {
+ FIRSetAccountInfoRequest *request = [[FIRSetAccountInfoRequest alloc] initWithAPIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRSetAccountInfoResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend setAccountInfo:request
+ callback:^(FIRSetAccountInfoResponse *_Nullable response,
+ NSError *_Nullable error) {
+ RPCResponse = response;
+ RPCError = error;
+ callbackInvoked = YES;
+ }];
+ [_RPCIssuer respondWithServerErrorMessage:kInvalidMessagePayloadErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeInvalidMessagePayload);
+}
+
+/** @fn testInvalidSenderError
+ @brief Tests for @c FIRAuthErrorCodeInvalidSender.
+ */
+- (void)testInvalidSenderError {
+ FIRSetAccountInfoRequest *request = [[FIRSetAccountInfoRequest alloc] initWithAPIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRSetAccountInfoResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend setAccountInfo:request
+ callback:^(FIRSetAccountInfoResponse *_Nullable response,
+ NSError *_Nullable error) {
+ RPCResponse = response;
+ RPCError = error;
+ callbackInvoked = YES;
+ }];
+ [_RPCIssuer respondWithServerErrorMessage:kInvalidSenderErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeInvalidSender);
+}
+
+/** @fn testInvalidRecipientEmailError
+ @brief Tests for @c FIRAuthErrorCodeInvalidRecipientEmail.
+ */
+- (void)testInvalidRecipientEmailError {
+ FIRSetAccountInfoRequest *request = [[FIRSetAccountInfoRequest alloc] initWithAPIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRSetAccountInfoResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend setAccountInfo:request
+ callback:^(FIRSetAccountInfoResponse *_Nullable response,
+ NSError *_Nullable error) {
+ RPCResponse = response;
+ RPCError = error;
+ callbackInvoked = YES;
+ }];
+ [_RPCIssuer respondWithServerErrorMessage:kInvalidRecipientEmailErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeInvalidRecipientEmail);
+}
+
+/** @fn testSuccessfulSetAccountInfoResponse
+ @brief This test simulates a successful @c SetAccountInfo flow.
+ */
+- (void)testSuccessfulSetAccountInfoResponse {
+ FIRSetAccountInfoRequest *request = [[FIRSetAccountInfoRequest alloc] initWithAPIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRSetAccountInfoResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend setAccountInfo:request
+ callback:^(FIRSetAccountInfoResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithJSON:@{
+ kProviderUserInfoKey:@[
+ @{ kPhotoUrlKey : kTestPhotoURL }
+ ],
+ kIDTokenKey : kTestIDToken,
+ kExpiresInKey : kTestExpiresIn,
+ kRefreshTokenKey : kTestRefreshToken
+ }];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCError);
+ XCTAssertNotNil(RPCResponse);
+ if ([RPCResponse.providerUserInfo count]) {
+ NSURL *responsePhotoUrl = RPCResponse.providerUserInfo[0].photoURL;
+ XCTAssertEqualObjects(responsePhotoUrl.absoluteString, kTestPhotoURL);
+ }
+ XCTAssertEqualObjects(RPCResponse.IDToken, kTestIDToken);
+ NSTimeInterval expiresIn = [RPCResponse.approximateExpirationDate timeIntervalSinceNow];
+ XCTAssertLessThanOrEqual(fabs(expiresIn - [kTestExpiresIn doubleValue]), kEpsilon);
+ XCTAssertEqualObjects(RPCResponse.refreshToken, kTestRefreshToken);
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRSignUpNewUserRequestTests.m b/Example/Auth/Tests/FIRSignUpNewUserRequestTests.m
new file mode 100644
index 0000000..622ec7c
--- /dev/null
+++ b/Example/Auth/Tests/FIRSignUpNewUserRequestTests.m
@@ -0,0 +1,140 @@
+/*
+ * 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 "FIRAuthErrors.h"
+#import "FIRAuthBackend.h"
+#import "FIRGetOOBConfirmationCodeResponse.h"
+#import "FIRSignUpNewUserRequest.h"
+#import "FIRSignUpNewUserResponse.h"
+#import "FIRFakeBackendRPCIssuer.h"
+
+/** @var kExpectedAPIURL
+ @brief The expected URL for the test calls.
+ */
+static NSString *const kExpectedAPIURL =
+ @"https://www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser?key=APIKey";
+
+/** @var kTestAPIKey
+ @brief Fake API key used for testing.
+ */
+static NSString *const kTestAPIKey = @"APIKey";
+
+/** @var kEmailKey
+ @brief The name of the "email" property in the request.
+ */
+static NSString *const kEmailKey = @"email";
+
+/** @var kTestEmail
+ @brief Testing user email adadress.
+ */
+static NSString *const kTestEmail = @"test@gmail.com";
+
+/** @var kDisplayNameKey
+ @brief the name of the "displayName" property in the request.
+ */
+static NSString *const kDisplayNameKey = @"displayName";
+
+/** @var kTestDisplayName
+ @brief Testing display name.
+ */
+static NSString *const kTestDisplayName = @"DisplayName";
+
+/** @var kPasswordKey
+ @brief the name of the "password" property in the request.
+ */
+static NSString *const kPasswordKey = @"password";
+
+/** @var kTestPassword
+ @brief Testing password.
+ */
+static NSString *const kTestPassword = @"Password";
+
+/** @var kReturnSecureTokenKey
+ @brief The key for the "returnSecureToken" value in the request.
+ */
+static NSString *const kReturnSecureTokenKey = @"returnSecureToken";
+
+@interface FIRSignUpNewUserRequestTests : XCTestCase
+
+@end
+
+@implementation FIRSignUpNewUserRequestTests {
+ /** @var _RPCIssuer
+ @brief This backend RPC issuer is used to fake network responses for each test in the suite.
+ In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it.
+ */
+ FIRFakeBackendRPCIssuer *_RPCIssuer;
+}
+
+- (void)setUp {
+ [super setUp];
+ FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init];
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer];
+ _RPCIssuer = RPCIssuer;
+}
+
+- (void)tearDown {
+ _RPCIssuer = nil;
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
+ [super tearDown];
+}
+
+/** @fn testSignUpNewUserRequestAnonymous
+ @brief Tests the encoding of a sign up new user request when user is signed in anonymously.
+ */
+- (void)testSignUpNewUserRequestAnonymous {
+ FIRSignUpNewUserRequest *request = [[FIRSignUpNewUserRequest alloc] initWithAPIKey:kTestAPIKey];
+ request.returnSecureToken = NO;
+ [FIRAuthBackend signUpNewUser:request
+ callback:^(FIRSignUpNewUserResponse *_Nullable response,
+ NSError *_Nullable error) {
+ }];
+
+ XCTAssertEqualObjects(_RPCIssuer.requestURL.absoluteString, kExpectedAPIURL);
+ XCTAssertNotNil(_RPCIssuer.decodedRequest);
+ XCTAssert([_RPCIssuer.decodedRequest isKindOfClass:[NSDictionary class]]);
+ XCTAssertNil(_RPCIssuer.decodedRequest[kEmailKey]);
+ XCTAssertNil(_RPCIssuer.decodedRequest[kDisplayNameKey]);
+ XCTAssertNil(_RPCIssuer.decodedRequest[kPasswordKey]);
+ XCTAssertNil(_RPCIssuer.decodedRequest[kReturnSecureTokenKey]);
+}
+
+/** @fn testSignUpNewUserRequestNotAnonymous
+ @brief Tests the encoding of a sign up new user request when user is not signed in anonymously.
+ */
+- (void)testSignUpNewUserRequestNotAnonymous {
+ FIRSignUpNewUserRequest *request =
+ [[FIRSignUpNewUserRequest alloc] initWithAPIKey:kTestAPIKey
+ email:kTestEmail
+ password:kTestPassword
+ displayName:kTestDisplayName];
+ [FIRAuthBackend signUpNewUser:request
+ callback:^(FIRSignUpNewUserResponse *_Nullable response,
+ NSError *_Nullable error) {
+ }];
+
+ XCTAssertEqualObjects(_RPCIssuer.requestURL.absoluteString, kExpectedAPIURL);
+ XCTAssertNotNil(_RPCIssuer.decodedRequest);
+ XCTAssert([_RPCIssuer.decodedRequest isKindOfClass:[NSDictionary class]]);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kEmailKey], kTestEmail);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kPasswordKey], kTestPassword);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kDisplayNameKey], kTestDisplayName);
+ XCTAssertTrue([_RPCIssuer.decodedRequest[kReturnSecureTokenKey] boolValue]);
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRSignUpNewUserResponseTests.m b/Example/Auth/Tests/FIRSignUpNewUserResponseTests.m
new file mode 100644
index 0000000..89479f7
--- /dev/null
+++ b/Example/Auth/Tests/FIRSignUpNewUserResponseTests.m
@@ -0,0 +1,291 @@
+/*
+ * 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 "FIRAuthErrors.h"
+#import "FIRAuthBackend.h"
+#import "FIRSignUpNewUserRequest.h"
+#import "FIRSignUpNewUserResponse.h"
+#import "FIRFakeBackendRPCIssuer.h"
+
+/** @var kTestAPIKey
+ @brief Fake API key used for testing.
+ */
+static NSString *const kTestAPIKey = @"APIKey";
+
+/** @var kIDTokenKey
+ @brief The name of the "IDToken" property in the response.
+ */
+static NSString *const kIDTokenKey = @"idToken";
+
+/** @var kTestIDToken
+ @brief Testing ID token for verifying assertion.
+ */
+static NSString *const kTestIDToken = @"ID_TOKEN";
+
+/** @var kExpiresInKey
+ @brief The name of the "expiresIn" property in the response.
+ */
+static NSString *const kExpiresInKey = @"expiresIn";
+
+/** @var kTestExpiresIn
+ @brief Fake token expiration time.
+ */
+static NSString *const kTestExpiresIn = @"12345";
+
+/** @var kRefreshTokenKey
+ @brief The name of the "refreshToken" property in the response.
+ */
+static NSString *const kRefreshTokenKey = @"refreshToken";
+
+/** @var kTestRefreshToken
+ @brief Fake refresh token.
+ */
+static NSString *const kTestRefreshToken = @"REFRESH_TOKEN";
+
+/** @var kTestEmail
+ @brief Testing user email adadress.
+ */
+static NSString *const kTestEmail = @"test@gmail.com";
+
+/** @var kTestDisplayName
+ @brief Testing display name.
+ */
+static NSString *const kTestDisplayName = @"DisplayName";
+
+/** @var kTestPassword
+ @brief Testing password.
+ */
+static NSString *const kTestPassword = @"Password";
+
+/** @var kEmailAlreadyInUseErrorMessage
+ @brief This is the error message the server will respond with if the user entered an invalid
+ email address.
+ */
+static NSString *const kEmailAlreadyInUseErrorMessage = @"EMAIL_EXISTS";
+
+/** @var kOperationNotAllowedErrorMessage
+ @brief This is the error message the server will respond with if user/password account was
+ disabled by the administrator.
+ */
+static NSString *const kEmailSignUpNotAllowedErrorMessage = @"OPERATION_NOT_ALLOWED";
+
+/** @var kPasswordLoginDisabledErrorMessage
+ @brief This is the error message the server responds with if password login is disabled.
+ */
+static NSString *const kPasswordLoginDisabledErrorMessage = @"PASSWORD_LOGIN_DISABLED:";
+
+/** @var kInvalidEmailErrorMessage
+ @brief The error returned by the server if the email is invalid.
+ */
+static NSString *const kInvalidEmailErrorMessage = @"INVALID_EMAIL";
+
+/** @var kWeakPasswordErrorMessage
+ @brief This is the error message the server will respond with if the new user's password
+ is too weak that it is too short.
+ */
+static NSString *const kWeakPasswordErrorMessage =
+ @"WEAK_PASSWORD : Password should be at least 6 characters";
+
+/** @var kWeakPasswordClientErrorMessage
+ @brief This is the error message the client will see if the new user's password is too weak
+ that it is too short.
+ @remarks This message should be derived from @c kWeakPasswordErrorMessage .
+ */
+static NSString *const kWeakPasswordClientErrorMessage =
+ @"Password should be at least 6 characters";
+
+/** @var kEpsilon
+ @brief Allowed difference when comparing floating point numbers.
+ */
+static const double kEpsilon = 1e-3;
+
+@interface FIRSignUpNewUserResponseTests : XCTestCase
+@end
+@implementation FIRSignUpNewUserResponseTests
+ /** @var _RPCIssuer
+ @brief This backend RPC issuer is used to fake network responses for each test in the suite.
+ In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it.
+ */
+ FIRFakeBackendRPCIssuer *_RPCIssuer;
+
+- (void)setUp {
+ [super setUp];
+ FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init];
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer];
+ _RPCIssuer = RPCIssuer;
+}
+
+- (void)tearDown {
+ _RPCIssuer = nil;
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
+ [super tearDown];
+}
+
+/** @fn testSuccessfulSignUp
+ @brief This test simulates a complete sign up flow with no errors.
+ */
+- (void)testSuccessfulSignUp {
+ FIRSignUpNewUserRequest *request =
+ [[FIRSignUpNewUserRequest alloc] initWithAPIKey:kTestAPIKey
+ email:kTestEmail
+ password:kTestPassword
+ displayName:kTestDisplayName];
+
+ __block BOOL callbackInvoked;
+ __block FIRSignUpNewUserResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend signUpNewUser:request
+ callback:^(FIRSignUpNewUserResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithJSON:@{
+ kIDTokenKey : kTestIDToken,
+ kExpiresInKey : kTestExpiresIn,
+ kRefreshTokenKey : kTestRefreshToken
+ }];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCError);
+ XCTAssertNotNil(RPCResponse);
+ XCTAssertEqualObjects(RPCResponse.IDToken, kTestIDToken);
+ NSTimeInterval expiresIn = [RPCResponse.approximateExpirationDate timeIntervalSinceNow];
+ XCTAssertLessThanOrEqual(fabs(expiresIn - [kTestExpiresIn doubleValue]), kEpsilon);
+ XCTAssertEqualObjects(RPCResponse.refreshToken, kTestRefreshToken);
+ XCTAssertNil(RPCError, "There should be no error");
+}
+
+/** @fn testSignUpNewUserEmailAlreadyInUseError
+ @brief This test simulates @c testSignUpNewUserEmailAlreadyInUseError with @c
+ FIRAuthErrorCodeEmailAlreadyInUse error.
+ */
+- (void)testSignUpNewUserEmailAlreadyInUseError {
+ FIRSignUpNewUserRequest *request = [[FIRSignUpNewUserRequest alloc] initWithAPIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRSignUpNewUserResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend signUpNewUser:request
+ callback:^(FIRSignUpNewUserResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+ [_RPCIssuer respondWithServerErrorMessage:kEmailAlreadyInUseErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeEmailAlreadyInUse);
+}
+
+/** @fn testSignUpNewUserOperationNotAllowedError
+ @brief This test simulates @c testSignUpNewUserEmailExistsError with @c
+ FIRAuthErrorCodeOperationNotAllowed error.
+ */
+- (void)testSignUpNewUserOperationNotAllowedError {
+ FIRSignUpNewUserRequest *request = [[FIRSignUpNewUserRequest alloc] initWithAPIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRSignUpNewUserResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend signUpNewUser:request
+ callback:^(FIRSignUpNewUserResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+ [_RPCIssuer respondWithServerErrorMessage:kEmailSignUpNotAllowedErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeOperationNotAllowed);
+}
+
+/** @fn testSignUpNewUserPasswordLoginDisabledError
+ @brief This test simulates @c signUpNewUserPasswordLoginDisabledError with @c
+ FIRAuthErrorCodeOperationNotAllowed error.
+ */
+- (void)testSignUpNewUserPasswordLoginDisabledError {
+ FIRSignUpNewUserRequest *request = [[FIRSignUpNewUserRequest alloc] initWithAPIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRSignUpNewUserResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend signUpNewUser:request
+ callback:^(FIRSignUpNewUserResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+ [_RPCIssuer respondWithServerErrorMessage:kPasswordLoginDisabledErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeOperationNotAllowed);
+}
+
+/** @fn testinvalidEmailError
+ @brief This test simulates making a request containing an invalid email address and receiving @c
+ FIRAuthErrorInvalidEmail error as a result.
+ */
+- (void)testinvalidEmailError {
+ FIRSignUpNewUserRequest *request = [[FIRSignUpNewUserRequest alloc] initWithAPIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRSignUpNewUserResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend signUpNewUser:request
+ callback:^(FIRSignUpNewUserResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+ [_RPCIssuer respondWithServerErrorMessage:kInvalidEmailErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeInvalidEmail);
+}
+
+/** @fn testSignUpNewUserWeakPasswordError
+ @brief This test simulates @c FIRAuthErrorCodeWeakPassword error.
+ */
+- (void)testSignUpNewUserWeakPasswordError {
+ FIRSignUpNewUserRequest *request = [[FIRSignUpNewUserRequest alloc] initWithAPIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRSignUpNewUserResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend signUpNewUser:request
+ callback:^(FIRSignUpNewUserResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+ [_RPCIssuer respondWithServerErrorMessage:kWeakPasswordErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeWeakPassword);
+ XCTAssertEqualObjects(RPCError.userInfo[NSLocalizedFailureReasonErrorKey],
+ kWeakPasswordClientErrorMessage);
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRTwitterAuthProviderTests.m b/Example/Auth/Tests/FIRTwitterAuthProviderTests.m
new file mode 100644
index 0000000..da02c43
--- /dev/null
+++ b/Example/Auth/Tests/FIRTwitterAuthProviderTests.m
@@ -0,0 +1,60 @@
+/*
+ * 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 "Twitter/FIRTwitterAuthProvider.h"
+#import "FIRAuthCredential_Internal.h"
+#import "FIRVerifyAssertionRequest.h"
+
+/** @var kTwitterToken
+ @brief A testing Twitter token.
+ */
+static NSString *const kTwitterToken = @"Token";
+
+/** @var kTwitterSecret
+ @brief A testing Twitter secret.
+ */
+static NSString *const kTwitterSecret = @"Secret";
+
+/** @var kAPIKey
+ @brief A testing API Key.
+ */
+static NSString *const kAPIKey = @"APIKey";
+
+/** @class FIRTwitterAuthProviderTests
+ @brief Tests for @c FIRTwitterAuthProvider
+ */
+@interface FIRTwitterAuthProviderTests : XCTestCase
+@end
+@implementation FIRTwitterAuthProviderTests
+
+/** @fn testCredentialWithToken
+ @brief Tests the @c credentialWithToken method to make sure the credential it produces populates
+ the appropriate fields in a verify assertion request.
+ */
+- (void)testCredentialWithToken {
+ FIRAuthCredential *credential =
+ [FIRTwitterAuthProvider credentialWithToken:kTwitterToken secret:kTwitterSecret];
+ FIRVerifyAssertionRequest *request =
+ [[FIRVerifyAssertionRequest alloc] initWithAPIKey:kAPIKey
+ providerID:FIRTwitterAuthProviderID];
+ [credential prepareVerifyAssertionRequest:request];
+ XCTAssertEqualObjects(request.providerAccessToken, kTwitterToken);
+ XCTAssertEqualObjects(request.providerOAuthTokenSecret, kTwitterSecret);
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRUserTests.m b/Example/Auth/Tests/FIRUserTests.m
new file mode 100644
index 0000000..5a4c00a
--- /dev/null
+++ b/Example/Auth/Tests/FIRUserTests.m
@@ -0,0 +1,1801 @@
+/*
+ * 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 "EmailPassword/FIREmailAuthProvider.h"
+#import "Facebook/FIRFacebookAuthProvider.h"
+#import "Google/FIRGoogleAuthProvider.h"
+#import "Phone/FIRPhoneAuthCredential_Internal.h"
+#import "Phone/FIRPhoneAuthProvider.h"
+#import "FIRAdditionalUserInfo.h"
+#import "FIRAuth.h"
+#import "FIRAuthErrorUtils.h"
+#import "FIRAuthGlobalWorkQueue.h"
+#import "FIRUser.h"
+#import "FIRUserInfo.h"
+#import "FIRAuthBackend.h"
+#import "FIRGetAccountInfoRequest.h"
+#import "FIRGetAccountInfoResponse.h"
+#import "FIRSetAccountInfoRequest.h"
+#import "FIRSetAccountInfoResponse.h"
+#import "FIRVerifyAssertionResponse.h"
+#import "FIRVerifyAssertionRequest.h"
+#import "FIRVerifyPasswordRequest.h"
+#import "FIRVerifyPasswordResponse.h"
+#import "FIRVerifyPhoneNumberRequest.h"
+#import "FIRVerifyPhoneNumberResponse.h"
+#import "FIRApp+FIRAuthUnitTests.h"
+#import "OCMStubRecorder+FIRAuthUnitTests.h"
+#import <OCMock/OCMock.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** @var kAPIKey
+ @brief The fake API key.
+ */
+static NSString *const kAPIKey = @"FAKE_API_KEY";
+
+/** @var kAccessToken
+ @brief The fake access token.
+ */
+static NSString *const kAccessToken = @"ACCESS_TOKEN";
+
+/** @var kNewAccessToken
+ @brief A new value for the fake access token.
+ */
+static NSString *const kNewAccessToken = @"NEW_ACCESS_TOKEN";
+
+/** @var kAccessTokenValidInterval
+ @brief The time to live for the fake access token.
+ */
+static const NSTimeInterval kAccessTokenTimeToLive = 60 * 60;
+
+/** @var kRefreshToken
+ @brief The fake refresh token.
+ */
+static NSString *const kRefreshToken = @"REFRESH_TOKEN";
+
+/** @var kLocalID
+ @brief The fake local user ID.
+ */
+static NSString *const kLocalID = @"LOCAL_ID";
+
+/** @var kAnotherLocalID
+ @brief The fake local ID of another user.
+ */
+static NSString *const kAnotherLocalID = @"ANOTHER_LOCAL_ID";
+
+/** @var kGoogleIDToken
+ @brief The fake ID token from Google Sign-In.
+ */
+static NSString *const kGoogleIDToken = @"GOOGLE_ID_TOKEN";
+
+/** @var kFacebookIDToken
+ @brief The fake ID token from Facebook Sign-In. Facebook provider ID token is always nil.
+ */
+static NSString *const kFacebookIDToken = nil;
+
+/** @var kGoogleAccessToken
+ @brief The fake access token from Google Sign-In.
+ */
+static NSString *const kGoogleAccessToken = @"GOOGLE_ACCESS_TOKEN";
+
+/** @var kFacebookAccessToken
+ @brief The fake access token from Facebook Sign-In.
+ */
+static NSString *const kFacebookAccessToken = @"FACEBOOK_ACCESS_TOKEN";
+
+/** @var kEmail
+ @brief The fake user email.
+ */
+static NSString *const kEmail = @"user@company.com";
+
+/** @var kPhoneNumber
+ @brief The fake user phone number.
+ */
+static NSString *const kPhoneNumber = @"12345658";
+
+/** @var kTemporaryProof
+ @brief The fake temporary proof.
+ */
+static NSString *const kTemporaryProof = @"12345658";
+
+/** @var kNewEmail
+ @brief A new value for the fake user email.
+ */
+static NSString *const kNewEmail = @"newuser@company.com";
+
+/** @var kUserName
+ @brief The fake user name.
+ */
+static NSString *const kUserName = @"User Doe";
+
+/** @var kNewDisplayName
+ @brief A new value for the fake user display name.
+ */
+static NSString *const kNewDisplayName = @"New User Doe";
+
+/** @var kPhotoURL
+ @brief The fake user profile image URL string.
+ */
+static NSString *const kPhotoURL = @"https://host.domain/image";
+
+/** @var kNewPhotoURL
+ @brief A new value for the fake user profile image URL string..
+ */
+static NSString *const kNewPhotoURL = @"https://host.domain/new/image";
+
+/** @var kPassword
+ @brief The fake user password.
+ */
+static NSString *const kPassword = @"123456";
+
+/** @var kNewPassword
+ @brief The fake new user password.
+ */
+static NSString *const kNewPassword = @"1234567";
+
+/** @var kPasswordHash
+ @brief The fake user password hash.
+ */
+static NSString *const kPasswordHash = @"UkVEQUNURUQ=";
+
+/** @var kGoogleUD
+ @brief The fake user ID under Google Sign-In.
+ */
+static NSString *const kGoogleID = @"GOOGLE_ID";
+
+/** @var kGoogleEmail
+ @brief The fake user email under Google Sign-In.
+ */
+static NSString *const kGoogleEmail = @"user@gmail.com";
+
+/** @var kGoogleDisplayName
+ @brief The fake user display name under Google Sign-In.
+ */
+static NSString *const kGoogleDisplayName = @"Google Doe";
+
+/** @var kEmailDisplayName
+ @brief The fake user display name for email password user.
+ */
+static NSString *const kEmailDisplayName = @"Email Doe";
+
+/** @var kFacebookDisplayName
+ @brief The fake user display name under Facebook Sign-In.
+ */
+static NSString *const kFacebookDisplayName = @"Facebook Doe";
+
+/** @var kGooglePhotoURL
+ @brief The fake user profile image URL string under Google Sign-In.
+ */
+static NSString *const kGooglePhotoURL = @"https://googleusercontents.com/user/profile";
+
+/** @var kFacebookID
+ @brief The fake user ID under Facebook Login.
+ */
+static NSString *const kFacebookID = @"FACEBOOK_ID";
+
+/** @var kFacebookEmail
+ @brief The fake user email under Facebook Login.
+ */
+static NSString *const kFacebookEmail = @"user@facebook.com";
+
+/** @var kVerificationCode
+ @brief Fake verification code used for testing.
+ */
+static NSString *const kVerificationCode = @"12345678";
+
+/** @var kVerificationID
+ @brief Fake verification ID for testing.
+ */
+static NSString *const kVerificationID = @"55432";
+
+/** @var kExpectationTimeout
+ @brief The maximum time waiting for expectations to fulfill.
+ */
+static const NSTimeInterval kExpectationTimeout = 1;
+
+/** @class FIRUserTests
+ @brief Tests for @c FIRUser .
+ */
+@interface FIRUserTests : XCTestCase
+@end
+@implementation FIRUserTests {
+
+ /** @var _mockBackend
+ @brief The mock @c FIRAuthBackendImplementation .
+ */
+ id _mockBackend;
+}
+
+/** @fn googleProfile
+ @brief The fake user profile under additional user data in @c FIRVerifyAssertionResponse.
+ */
++ (NSDictionary *)googleProfile {
+ static NSDictionary *kGoogleProfile = nil;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ kGoogleProfile = @{
+ @"email": kGoogleEmail,
+ @"given_name": @"User",
+ @"family_name": @"Doe"
+ };
+ });
+ return kGoogleProfile;
+}
+
+- (void)setUp {
+ [super setUp];
+ _mockBackend = OCMProtocolMock(@protocol(FIRAuthBackendImplementation));
+ [FIRAuthBackend setBackendImplementation:_mockBackend];
+ [FIRApp resetAppForAuthUnitTests];
+}
+
+- (void)tearDown {
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
+ [super tearDown];
+}
+
+#pragma mark - Tests
+
+/** @fn testUserProperties
+ @brief Tests properties of the @c FIRUser instance.
+ */
+- (void)testUserProperties {
+ // Mock auth provider user info for email/password for GetAccountInfo.
+ id mockPasswordUserInfo = OCMClassMock([FIRGetAccountInfoResponseProviderUserInfo class]);
+ OCMStub([mockPasswordUserInfo providerID]).andReturn(FIREmailAuthProviderID);
+ OCMStub([mockPasswordUserInfo federatedID]).andReturn(kEmail);
+ OCMStub([mockPasswordUserInfo email]).andReturn(kEmail);
+
+ // Mock auth provider user info from Google for GetAccountInfo.
+ id mockGoogleUserInfo = OCMClassMock([FIRGetAccountInfoResponseProviderUserInfo class]);
+ OCMStub([mockGoogleUserInfo providerID]).andReturn(FIRGoogleAuthProviderID);
+ OCMStub([mockGoogleUserInfo displayName]).andReturn(kGoogleDisplayName);
+ OCMStub([mockGoogleUserInfo photoURL]).andReturn([NSURL URLWithString:kGooglePhotoURL]);
+ OCMStub([mockGoogleUserInfo federatedID]).andReturn(kGoogleID);
+ OCMStub([mockGoogleUserInfo email]).andReturn(kGoogleEmail);
+
+ // Mock auth provider user info from Facebook for GetAccountInfo.
+ id mockFacebookUserInfo = OCMClassMock([FIRGetAccountInfoResponseProviderUserInfo class]);
+ OCMStub([mockFacebookUserInfo providerID]).andReturn(FIRFacebookAuthProviderID);
+ OCMStub([mockFacebookUserInfo federatedID]).andReturn(kFacebookID);
+ OCMStub([mockFacebookUserInfo email]).andReturn(kFacebookEmail);
+
+ // Mock auth provider user info from Phone auth provider for GetAccountInfo.
+ id mockPhoneUserInfo = OCMClassMock([FIRGetAccountInfoResponseProviderUserInfo class]);
+ OCMStub([mockPhoneUserInfo providerID]).andReturn(FIRPhoneAuthProviderID);
+ OCMStub([mockPhoneUserInfo phoneNumber]).andReturn(kPhoneNumber);
+
+ // Mock the root user info object for GetAccountInfo.
+ id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+ OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
+ OCMStub([mockGetAccountInfoResponseUser emailVerified]).andReturn(YES);
+ OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
+ OCMStub([mockGetAccountInfoResponseUser photoURL]).andReturn([NSURL URLWithString:kPhotoURL]);
+ OCMStub([mockGetAccountInfoResponseUser providerUserInfo])
+ .andReturn((@[ mockPasswordUserInfo,
+ mockGoogleUserInfo,
+ mockFacebookUserInfo,
+ mockPhoneUserInfo ]));
+ OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
+ completion:^(FIRUser *user) {
+ // Verify FIRUserInfo properties on FIRUser itself.
+ XCTAssertEqualObjects(user.providerID, @"Firebase");
+ XCTAssertEqualObjects(user.uid, kLocalID);
+ XCTAssertEqualObjects(user.displayName, kGoogleDisplayName);
+ XCTAssertEqualObjects(user.photoURL, [NSURL URLWithString:kPhotoURL]);
+ XCTAssertEqualObjects(user.email, kEmail);
+
+ // Verify FIRUser properties besides providerData contents.
+ XCTAssertFalse(user.anonymous);
+ XCTAssertTrue(user.emailVerified);
+ XCTAssertEqualObjects(user.refreshToken, kRefreshToken);
+ XCTAssertEqual(user.providerData.count, 4u);
+
+ NSDictionary<NSString *, id<FIRUserInfo>> *providerMap =
+ [self dictionaryWithUserInfoArray:user.providerData];
+
+ // Verify FIRUserInfo properties from email/password.
+ id<FIRUserInfo> passwordUserInfo = providerMap[FIREmailAuthProviderID];
+ XCTAssertNotNil(passwordUserInfo);
+ XCTAssertEqualObjects(passwordUserInfo.uid, kEmail);
+ XCTAssertNil(passwordUserInfo.displayName);
+ XCTAssertNil(passwordUserInfo.photoURL);
+ XCTAssertEqualObjects(passwordUserInfo.email, kEmail);
+
+ // Verify FIRUserInfo properties from the Google auth provider.
+ id<FIRUserInfo> googleUserInfo = providerMap[FIRGoogleAuthProviderID];
+ XCTAssertNotNil(googleUserInfo);
+ XCTAssertEqualObjects(googleUserInfo.uid, kGoogleID);
+ XCTAssertEqualObjects(googleUserInfo.displayName, kGoogleDisplayName);
+ XCTAssertEqualObjects(googleUserInfo.photoURL, [NSURL URLWithString:kGooglePhotoURL]);
+ XCTAssertEqualObjects(googleUserInfo.email, kGoogleEmail);
+
+ // Verify FIRUserInfo properties from the Facebook auth provider.
+ id<FIRUserInfo> facebookUserInfo = providerMap[FIRFacebookAuthProviderID];
+ XCTAssertNotNil(facebookUserInfo);
+ XCTAssertEqualObjects(facebookUserInfo.uid, kFacebookID);
+ XCTAssertNil(facebookUserInfo.displayName);
+ XCTAssertNil(facebookUserInfo.photoURL);
+ XCTAssertEqualObjects(facebookUserInfo.email, kFacebookEmail);
+
+ // Verify FIRUserInfo properties from the phone auth provider.
+ id<FIRUserInfo> phoneUserInfo = providerMap[FIRPhoneAuthProviderID];
+ XCTAssertNotNil(phoneUserInfo);
+ XCTAssertEqualObjects(phoneUserInfo.phoneNumber, kPhoneNumber);
+
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testUpdateEmailSuccess
+ @brief Tests the flow of a successful @c updateEmail:completion: call.
+ */
+- (void)testUpdateEmailSuccess {
+ id (^mockUserInfoWithDisplayName)(NSString *) = ^(NSString *displayName) {
+ id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+ OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
+ OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(displayName);
+ OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
+ return mockGetAccountInfoResponseUser;
+ };
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ id userInfoResponse = mockUserInfoWithDisplayName(kGoogleDisplayName);
+ [self signInWithEmailPasswordWithMockUserInfoResponse:userInfoResponse
+ completion:^(FIRUser *user) {
+ // Pretend that the display name on the server has been changed since last request.
+ [self
+ expectGetAccountInfoWithMockUserInfoResponse:mockUserInfoWithDisplayName(kNewDisplayName)];
+ OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRSetAccountInfoRequest *_Nullable request,
+ FIRSetAccountInfoResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.accessToken, kAccessToken);
+ XCTAssertEqualObjects(request.email, kNewEmail);
+ XCTAssertNil(request.localID);
+ XCTAssertNil(request.displayName);
+ XCTAssertNil(request.photoURL);
+ XCTAssertNil(request.password);
+ XCTAssertNil(request.providers);
+ XCTAssertNil(request.deleteAttributes);
+ XCTAssertNil(request.deleteProviders);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockSetAccountInfoResponse = OCMClassMock([FIRSetAccountInfoResponse class]);
+ OCMStub([mockSetAccountInfoResponse email]).andReturn(kNewEmail);
+ OCMStub([mockSetAccountInfoResponse displayName]).andReturn(kNewDisplayName);
+ callback(mockSetAccountInfoResponse, nil);
+ });
+ });
+ [user updateEmail:kNewEmail completion:^(NSError *_Nullable error) {
+ XCTAssertNil(error);
+ XCTAssertEqualObjects(user.email, kNewEmail);
+ XCTAssertEqualObjects(user.displayName, kNewDisplayName);
+ [expectation fulfill];
+ }];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testUpdateEmailFailure
+ @brief Tests the flow of a failed @c updateEmail:completion: call.
+ */
+- (void)testUpdateEmailFailure {
+ id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+ OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
+ OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
+ OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
+ completion:^(FIRUser *user) {
+ [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser];
+ OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]])
+ .andDispatchError2([FIRAuthErrorUtils invalidEmailErrorWithMessage:nil]);
+ [user updateEmail:kNewEmail completion:^(NSError *_Nullable error) {
+ XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidEmail);
+ // Email should not have changed on the client side.
+ XCTAssertEqualObjects(user.email, kEmail);
+ [expectation fulfill];
+ }];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testUpdatePhoneSuccess
+ @brief Tests the flow of a successful @c updatePhoneNumberCredential:completion: call.
+ */
+- (void)testUpdatePhoneSuccess {
+ id (^mockUserInfoWithPhoneNumber)(NSString *) = ^(NSString *phoneNumber) {
+ id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+ OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
+ OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
+ if (phoneNumber.length) {
+ OCMStub([mockGetAccountInfoResponseUser phoneNumber]).andReturn(phoneNumber);
+ }
+ return mockGetAccountInfoResponseUser;
+ };
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ id userInfoResponse = mockUserInfoWithPhoneNumber(nil);
+ [self signInWithEmailPasswordWithMockUserInfoResponse:userInfoResponse
+ completion:^(FIRUser *user) {
+ [self expectVerifyPhoneNumberRequestWithPhoneNumber:kPhoneNumber error:nil];
+ id userInfoResponseUpdate = mockUserInfoWithPhoneNumber(kPhoneNumber);
+ [self expectGetAccountInfoWithMockUserInfoResponse:userInfoResponseUpdate];
+
+ FIRPhoneAuthCredential *credential =
+ [[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID
+ verificationCode:kVerificationCode];
+ [user updatePhoneNumberCredential:credential
+ completion:^(NSError * _Nullable error) {
+ XCTAssertNil(error);
+ XCTAssertEqualObjects([FIRAuth auth].currentUser.phoneNumber, kPhoneNumber);
+ [expectation fulfill];
+ }];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testUpdatePhoneNumberFailure
+ @brief Tests the flow of a failed @c updatePhoneNumberCredential:completion: call.
+ */
+- (void)testUpdatePhoneNumberFailure {
+ id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+ OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
+ OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
+ OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
+ completion:^(FIRUser *user) {
+ OCMExpect([_mockBackend verifyPhoneNumber:[OCMArg any] callback:[OCMArg any]])
+ .andDispatchError2([FIRAuthErrorUtils invalidPhoneNumberErrorWithMessage:nil]);
+ FIRPhoneAuthCredential *credential =
+ [[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID
+ verificationCode:kVerificationCode];
+ [user updatePhoneNumberCredential:credential completion:^(NSError *_Nullable error) {
+ XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidPhoneNumber);
+ [expectation fulfill];
+ }];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testUpdatePasswordSuccess
+ @brief Tests the flow of a successful @c updatePassword:completion: call.
+ */
+- (void)testUpdatePasswordSuccess {
+ id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+ OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
+ OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
+ OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
+ completion:^(FIRUser *user) {
+ [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser];
+ OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRSetAccountInfoRequest *_Nullable request,
+ FIRSetAccountInfoResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.accessToken, kAccessToken);
+ XCTAssertEqualObjects(request.password, kNewPassword);
+ XCTAssertNil(request.localID);
+ XCTAssertNil(request.displayName);
+ XCTAssertNil(request.photoURL);
+ XCTAssertNil(request.email);
+ XCTAssertNil(request.providers);
+ XCTAssertNil(request.deleteAttributes);
+ XCTAssertNil(request.deleteProviders);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockSetAccountInfoResponse = OCMClassMock([FIRSetAccountInfoResponse class]);
+ OCMStub([mockSetAccountInfoResponse displayName]).andReturn(kNewDisplayName);
+ callback(mockSetAccountInfoResponse, nil);
+ });
+ });
+ [user updatePassword:kNewPassword completion:^(NSError *_Nullable error) {
+ XCTAssertNil(error);
+ XCTAssertFalse(user.isAnonymous);
+ [expectation fulfill];
+ }];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testUpdatePasswordFailure
+ @brief Tests the flow of a failed @c updatePassword:completion: call.
+ */
+- (void)testUpdatePasswordFailure {
+ id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+ OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
+ OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
+ OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
+ completion:^(FIRUser *user) {
+ [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser];
+ OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]])
+ .andDispatchError2([FIRAuthErrorUtils userDisabledErrorWithMessage:nil]);
+ [user updatePassword:kNewPassword completion:^(NSError *_Nullable error) {
+ XCTAssertEqual(error.code, FIRAuthErrorCodeUserDisabled);
+ [expectation fulfill];
+ }];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testUpdateEmptyPasswordFailure
+ @brief Tests the flow of a failed @c updatePassword:completion: call due to an empty password.
+ */
+- (void)testUpdateEmptyPasswordFailure {
+ id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+ OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
+ OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
+ OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
+ completion:^(FIRUser *user) {
+ [user updatePassword:@"" completion:^(NSError *_Nullable error) {
+ XCTAssertEqual(error.code, FIRAuthErrorCodeWeakPassword);
+ [expectation fulfill];
+ }];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+}
+
+/** @fn testChangeProfileSuccess
+ @brief Tests a successful user profile change flow.
+ */
+- (void)testChangeProfileSuccess {
+ id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+ OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
+ OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
+ OCMStub([mockGetAccountInfoResponseUser photoURL]).andReturn(kPhotoURL);
+ OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
+ completion:^(FIRUser *user) {
+ [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser];
+ OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRSetAccountInfoRequest *_Nullable request,
+ FIRSetAccountInfoResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.accessToken, kAccessToken);
+ XCTAssertEqualObjects(request.displayName, kNewDisplayName);
+ XCTAssertEqualObjects(request.photoURL, [NSURL URLWithString:kNewPhotoURL]);
+ XCTAssertNil(request.localID);
+ XCTAssertNil(request.email);
+ XCTAssertNil(request.password);
+ XCTAssertNil(request.providers);
+ XCTAssertNil(request.deleteAttributes);
+ XCTAssertNil(request.deleteProviders);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockSetAccountInfoResponse = OCMClassMock([FIRSetAccountInfoResponse class]);
+ OCMStub([mockSetAccountInfoResponse displayName]).andReturn(kNewDisplayName);
+ callback(mockSetAccountInfoResponse, nil);
+ });
+ });
+ FIRUserProfileChangeRequest *profileChange = [user profileChangeRequest];
+ profileChange.photoURL = [NSURL URLWithString:kNewPhotoURL];
+ profileChange.displayName = kNewDisplayName;
+ [profileChange commitChangesWithCompletion:^(NSError *_Nullable error) {
+ XCTAssertNil(error);
+ XCTAssertEqualObjects(user.displayName, kNewDisplayName);
+ XCTAssertEqualObjects(user.photoURL, [NSURL URLWithString:kNewPhotoURL]);
+ [expectation fulfill];
+ }];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testChangeProfileFailure
+ @brief Tests a failed user profile change flow.
+ */
+- (void)testChangeProfileFailure {
+ id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+ OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
+ OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
+ OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
+ completion:^(FIRUser *user) {
+ [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser];
+ OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]])
+ .andDispatchError2([FIRAuthErrorUtils tooManyRequestsErrorWithMessage:nil]);
+ FIRUserProfileChangeRequest *profileChange = [user profileChangeRequest];
+ profileChange.displayName = kNewDisplayName;
+ [profileChange commitChangesWithCompletion:^(NSError *_Nullable error) {
+ XCTAssertEqual(error.code, FIRAuthErrorCodeTooManyRequests);
+ XCTAssertEqualObjects(user.displayName, kGoogleDisplayName);
+ [expectation fulfill];
+ }];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testReloadSuccess
+ @brief Tests the flow of a successful @c reloadWithCompletion: call.
+ */
+- (void)testReloadSuccess {
+ id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+ OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
+ OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
+ OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
+ completion:^(FIRUser *user) {
+ id mockGetAccountInfoResponseUserNew = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUserNew localID]).andReturn(kLocalID);
+ OCMStub([mockGetAccountInfoResponseUserNew email]).andReturn(kNewEmail);
+ OCMStub([mockGetAccountInfoResponseUserNew displayName]).andReturn(kNewDisplayName);
+ OCMStub([mockGetAccountInfoResponseUserNew passwordHash]).andReturn(kPasswordHash);
+ [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUserNew];
+ [user reloadWithCompletion:^(NSError *_Nullable error) {
+ XCTAssertNil(error);
+ XCTAssertEqualObjects(user.email, kNewEmail);
+ XCTAssertEqualObjects(user.displayName, kNewDisplayName);
+ [expectation fulfill];
+ }];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testReloadFailure
+ @brief Tests the flow of a failed @c reloadWithCompletion: call.
+ */
+- (void)testReloadFailure {
+ id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+ OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
+ OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
+ completion:^(FIRUser *user) {
+ OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]])
+ .andDispatchError2([FIRAuthErrorUtils userTokenExpiredErrorWithMessage:nil]);
+ [user reloadWithCompletion:^(NSError *_Nullable error) {
+ XCTAssertEqual(error.code, FIRAuthErrorCodeUserTokenExpired);
+ [expectation fulfill];
+ }];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testReauthenticateSuccess
+ @brief Tests the flow of a successful @c reauthenticateWithCredential:completion: call.
+ */
+- (void)testReauthenticateSuccess {
+ id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+ OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
+ OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
+ OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
+ completion:^(FIRUser *user) {
+ OCMExpect([_mockBackend verifyPassword:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRVerifyPasswordRequest *_Nullable request,
+ FIRVerifyPasswordResponseCallback callback) {
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockVeriyPasswordResponse = OCMClassMock([FIRVerifyPasswordResponse class]);
+ // New authentication comes back with new access token.
+ OCMStub([mockVeriyPasswordResponse IDToken]).andReturn(kNewAccessToken);
+ OCMStub([mockVeriyPasswordResponse approximateExpirationDate])
+ .andReturn([NSDate dateWithTimeIntervalSinceNow:kAccessTokenTimeToLive]);
+ OCMStub([mockVeriyPasswordResponse refreshToken]).andReturn(kRefreshToken);
+ callback(mockVeriyPasswordResponse, nil);
+ });
+ });
+ OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRGetAccountInfoRequest *_Nullable request,
+ FIRGetAccountInfoResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ // Verify that the new access token is being used for subsequent requests.
+ XCTAssertEqualObjects(request.accessToken, kNewAccessToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockGetAccountInfoResponse = OCMClassMock([FIRGetAccountInfoResponse class]);
+ OCMStub([mockGetAccountInfoResponse users]).andReturn(@[ mockGetAccountInfoResponseUser ]);
+ callback(mockGetAccountInfoResponse, nil);
+ });
+ });
+ FIRAuthCredential *emailCredential =
+ [FIREmailAuthProvider credentialWithEmail:kEmail password:kPassword];
+ [user reauthenticateWithCredential:emailCredential completion:^(NSError *_Nullable error) {
+ XCTAssertNil(error);
+ // Verify that the current user is unchanged.
+ XCTAssertEqual([FIRAuth auth].currentUser, user);
+ [expectation fulfill];
+ }];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testReauthenticateAndRetrieveDataSuccess
+ @brief Tests the flow of a successful @c reauthenticateAndRetrieveDataWithCredential:completion:
+ call.
+ */
+- (void)testReauthenticateAndRetrieveDataSuccess {
+ [self expectVerifyAssertionRequest:FIRGoogleAuthProviderID
+ federatedID:kGoogleID
+ displayName:kGoogleDisplayName
+ profile:[[self class] googleProfile]
+ providerIDToken:kGoogleIDToken
+ providerAccessToken:kGoogleAccessToken];
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ FIRAuthCredential *googleCredential =
+ [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken];
+ [[FIRAuth auth] signInAndRetrieveDataWithCredential:googleCredential
+ completion:^(FIRAuthDataResult *_Nullable authResult,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ [self assertUserGoogle:authResult.user];
+ XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]);
+ XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName);
+ XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRGoogleAuthProviderID);
+ XCTAssertNil(error);
+
+ [self expectVerifyAssertionRequest:FIRGoogleAuthProviderID
+ federatedID:kGoogleID
+ displayName:kGoogleDisplayName
+ profile:[[self class] googleProfile]
+ providerIDToken:kGoogleIDToken
+ providerAccessToken:kGoogleAccessToken];
+
+ FIRAuthCredential *reauthenticateGoogleCredential =
+ [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken];
+ [authResult.user
+ reauthenticateAndRetrieveDataWithCredential:reauthenticateGoogleCredential
+ completion:^(FIRAuthDataResult *_Nullable
+ reauthenticateAuthResult,
+ NSError *_Nullable error) {
+ XCTAssertNil(error);
+ // Verify that the current user is unchanged.
+ XCTAssertEqual([FIRAuth auth].currentUser, authResult.user);
+ // Verify that the current user and reauthenticated user are not same pointers.
+ XCTAssertNotEqualObjects(authResult.user, reauthenticateAuthResult.user);
+ // Verify that anyway the current user and reauthenticated user have same IDs.
+ XCTAssertEqualObjects(authResult.user.uid, reauthenticateAuthResult.user.uid);
+ XCTAssertEqualObjects(authResult.user.displayName, reauthenticateAuthResult.user.displayName);
+ XCTAssertEqualObjects(reauthenticateAuthResult.additionalUserInfo.profile,
+ [[self class] googleProfile]);
+ XCTAssertEqualObjects(reauthenticateAuthResult.additionalUserInfo.username, kUserName);
+ XCTAssertEqualObjects(reauthenticateAuthResult.additionalUserInfo.providerID,
+ FIRGoogleAuthProviderID);
+ [expectation fulfill];
+ }];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ [self assertUserGoogle:[FIRAuth auth].currentUser];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testReauthenticateFailure
+ @brief Tests the flow of a failed @c reauthenticateWithCredential:completion: call.
+ */
+- (void)testReauthenticateFailure {
+ id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+ OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
+ OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
+ OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
+ completion:^(FIRUser *user) {
+ OCMExpect([_mockBackend verifyPassword:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRVerifyPasswordRequest *_Nullable request,
+ FIRVerifyPasswordResponseCallback callback) {
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockVeriyPasswordResponse = OCMClassMock([FIRVerifyPasswordResponse class]);
+ OCMStub([mockVeriyPasswordResponse IDToken]).andReturn(kNewAccessToken);
+ OCMStub([mockVeriyPasswordResponse approximateExpirationDate])
+ .andReturn([NSDate dateWithTimeIntervalSinceNow:kAccessTokenTimeToLive]);
+ OCMStub([mockVeriyPasswordResponse refreshToken]).andReturn(kRefreshToken);
+ callback(mockVeriyPasswordResponse, nil);
+ });
+ });
+ OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRGetAccountInfoRequest *_Nullable request,
+ FIRGetAccountInfoResponseCallback callback) {
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockGetAccountInfoResponseUserNew = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ // The newly-signed-in user has a different ID.
+ OCMStub([mockGetAccountInfoResponseUserNew localID]).andReturn(kAnotherLocalID);
+ OCMStub([mockGetAccountInfoResponseUserNew email]).andReturn(kNewEmail);
+ OCMStub([mockGetAccountInfoResponseUserNew displayName]).andReturn(kNewDisplayName);
+ OCMStub([mockGetAccountInfoResponseUserNew passwordHash]).andReturn(kPasswordHash);
+ id mockGetAccountInfoResponse = OCMClassMock([FIRGetAccountInfoResponse class]);
+ OCMStub([mockGetAccountInfoResponse users])
+ .andReturn(@[ mockGetAccountInfoResponseUserNew ]);
+ callback(mockGetAccountInfoResponse, nil);
+ });
+ });
+ FIRAuthCredential *emailCredential =
+ [FIREmailAuthProvider credentialWithEmail:kEmail password:kPassword];
+ [user reauthenticateWithCredential:emailCredential completion:^(NSError *_Nullable error) {
+ // Verify user mismatch error.
+ XCTAssertEqual(error.code, FIRAuthErrorCodeUserMismatch);
+ // Verify that the current user is unchanged.
+ XCTAssertEqual([FIRAuth auth].currentUser, user);
+ [expectation fulfill];
+ }];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testReauthenticateUserMismatchFailure
+ @brief Tests the flow of a failed @c reauthenticateWithCredential:completion: call due to trying
+ to reauthenticate a user that does not exist.
+ */
+- (void)testReauthenticateUserMismatchFailure {
+ id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+ OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
+ OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName);
+ OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser
+ completion:^(FIRUser *user) {
+ OCMExpect([_mockBackend verifyAssertion:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRVerifyAssertionRequest *_Nullable request,
+ FIRVerifyAssertionResponseCallback callback) {
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ callback(nil, [FIRAuthErrorUtils userNotFoundErrorWithMessage:nil]);
+ });
+ });
+ FIRAuthCredential *googleCredential =
+ [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken];
+ [user reauthenticateWithCredential:googleCredential completion:^(NSError *_Nullable error) {
+ // Verify user mismatch error.
+ XCTAssertEqual(error.code, FIRAuthErrorCodeUserMismatch);
+ // Verify that the current user is unchanged.
+ XCTAssertEqual([FIRAuth auth].currentUser, user);
+ [expectation fulfill];
+ }];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testlinkAndRetrieveDataSuccess
+ @brief Tests the flow of a successful @c linkAndRetrieveDataWithCredential:completion:
+ call.
+ */
+- (void)testlinkAndRetrieveDataSuccess {
+ [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID
+ federatedID:kFacebookID
+ displayName:kFacebookDisplayName
+ profile:[[self class] googleProfile]
+ providerIDToken:kFacebookIDToken
+ providerAccessToken:kFacebookAccessToken];
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ FIRAuthCredential *facebookCredential =
+ [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken];
+ [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential
+ completion:^(FIRAuthDataResult *_Nullable authResult,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ [self assertUserFacebook:authResult.user];
+ XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]);
+ XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName);
+ XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID);
+ XCTAssertNil(error);
+
+ [self expectVerifyAssertionRequest:FIRGoogleAuthProviderID
+ federatedID:kGoogleID
+ displayName:kGoogleDisplayName
+ profile:[[self class] googleProfile]
+ providerIDToken:kGoogleIDToken
+ providerAccessToken:kGoogleAccessToken];
+
+ FIRAuthCredential *linkGoogleCredential =
+ [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken];
+ [authResult.user linkAndRetrieveDataWithCredential:linkGoogleCredential
+ completion:^(FIRAuthDataResult *_Nullable
+ linkAuthResult,
+ NSError *_Nullable error) {
+ XCTAssertNil(error);
+ // Verify that the current user is unchanged.
+ XCTAssertEqual([FIRAuth auth].currentUser, authResult.user);
+ // Verify that the current user and reauthenticated user are same pointers.
+ XCTAssertEqualObjects(authResult.user, linkAuthResult.user);
+ // Verify that anyway the current user and linked user have same IDs.
+ XCTAssertEqualObjects(authResult.user.uid, linkAuthResult.user.uid);
+ XCTAssertEqualObjects(authResult.user.displayName, linkAuthResult.user.displayName);
+ XCTAssertEqualObjects(linkAuthResult.additionalUserInfo.profile,
+ [[self class] googleProfile]);
+ XCTAssertEqualObjects(linkAuthResult.additionalUserInfo.username, kUserName);
+ XCTAssertEqualObjects(linkAuthResult.additionalUserInfo.providerID,
+ FIRGoogleAuthProviderID);
+ [expectation fulfill];
+ }];
+
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ [self assertUserGoogle:[FIRAuth auth].currentUser];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testlinkAndRetrieveDataError
+ @brief Tests the flow of an unsuccessful @c linkAndRetrieveDataWithCredential:completion:
+ call with an error from the backend.
+ */
+- (void)testlinkAndRetrieveDataError {
+ [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID
+ federatedID:kFacebookID
+ displayName:kFacebookDisplayName
+ profile:[[self class] googleProfile]
+ providerIDToken:kFacebookIDToken
+ providerAccessToken:kFacebookAccessToken];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ FIRAuthCredential *facebookCredential =
+ [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken];
+ [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential
+ completion:^(FIRAuthDataResult *_Nullable authResult,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ [self assertUserFacebook:authResult.user];
+ XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]);
+ XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName);
+ XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID);
+ XCTAssertNil(error);
+
+ OCMExpect([_mockBackend verifyAssertion:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRVerifyAssertionRequest *_Nullable request,
+ FIRVerifyAssertionResponseCallback callback) {
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ callback(nil, [FIRAuthErrorUtils userDisabledErrorWithMessage:nil]);
+ });
+ });
+
+ FIRAuthCredential *linkGoogleCredential =
+ [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken];
+ [authResult.user linkAndRetrieveDataWithCredential:linkGoogleCredential
+ completion:^(FIRAuthDataResult *_Nullable
+ linkAuthResult,
+ NSError *_Nullable error) {
+ XCTAssertNil(linkAuthResult);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeUserDisabled);
+ [expectation fulfill];
+ }];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testlinkAndRetrieveDataProviderAlreadyLinked
+ @brief Tests the flow of an unsuccessful @c linkAndRetrieveDataWithCredential:completion:
+ call with FIRAuthErrorCodeProviderAlreadyLinked, which is a client side error.
+ */
+- (void)testlinkAndRetrieveDataProviderAlreadyLinked {
+ [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID
+ federatedID:kFacebookID
+ displayName:kFacebookDisplayName
+ profile:[[self class] googleProfile]
+ providerIDToken:kFacebookIDToken
+ providerAccessToken:kFacebookAccessToken];
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ FIRAuthCredential *facebookCredential =
+ [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken];
+ [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential
+ completion:^(FIRAuthDataResult *_Nullable authResult,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ [self assertUserFacebook:authResult.user];
+ XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]);
+ XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName);
+ XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID);
+ XCTAssertNil(error);
+
+ FIRAuthCredential *linkFacebookCredential =
+ [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken];
+ [authResult.user linkAndRetrieveDataWithCredential:linkFacebookCredential
+ completion:^(FIRAuthDataResult *_Nullable
+ linkAuthResult,
+ NSError *_Nullable error) {
+ XCTAssertNil(linkAuthResult);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeProviderAlreadyLinked);
+ [expectation fulfill];
+ }];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testlinkEmailAndRetrieveDataSuccess
+ @brief Tests the flow of a successful @c linkAndRetrieveDataWithCredential:completion:
+ invocation for email credential.
+ */
+- (void)testlinkEmailAndRetrieveDataSuccess {
+ [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID
+ federatedID:kFacebookID
+ displayName:kFacebookDisplayName
+ profile:[[self class] googleProfile]
+ providerIDToken:kFacebookIDToken
+ providerAccessToken:kFacebookAccessToken];
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ FIRAuthCredential *facebookCredential =
+ [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken];
+ [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential
+ completion:^(FIRAuthDataResult *_Nullable authResult,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ [self assertUserFacebook:authResult.user];
+ XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]);
+ XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName);
+ XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID);
+ XCTAssertNil(error);
+
+ id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+ OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
+ OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kEmailDisplayName);
+ OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
+ // Get account info is expected to be invoked twice.
+ [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser];
+ [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser];
+
+ OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRSetAccountInfoRequest *_Nullable request,
+ FIRSetAccountInfoResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.accessToken, kAccessToken);
+ XCTAssertEqualObjects(request.password, kPassword);
+ XCTAssertNil(request.localID);
+ XCTAssertNil(request.displayName);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockSetAccountInfoResponse = OCMClassMock([FIRSetAccountInfoResponse class]);
+ callback(mockSetAccountInfoResponse, nil);
+ });
+ });
+
+ FIRAuthCredential *linkEmailCredential =
+ [FIREmailAuthProvider credentialWithEmail:kEmail password:kPassword];
+ [authResult.user linkAndRetrieveDataWithCredential:linkEmailCredential
+ completion:^(FIRAuthDataResult *_Nullable
+ linkAuthResult,
+ NSError *_Nullable error) {
+ XCTAssertNil(error);
+ XCTAssertEqualObjects(linkAuthResult.user.email, kEmail);
+ XCTAssertEqualObjects(linkAuthResult.user.displayName, kEmailDisplayName);
+ [expectation fulfill];
+ }];
+
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testlinkEmailProviderAlreadyLinkedError
+ @brief Tests the flow of an unsuccessful @c linkAndRetrieveDataWithCredential:completion:
+ invocation for email credential and FIRAuthErrorCodeProviderAlreadyLinked which is a client
+ side error.
+ */
+- (void)testlinkEmailProviderAlreadyLinkedError {
+ [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID
+ federatedID:kFacebookID
+ displayName:kFacebookDisplayName
+ profile:[[self class] googleProfile]
+ providerIDToken:kFacebookIDToken
+ providerAccessToken:kFacebookAccessToken];
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ FIRAuthCredential *facebookCredential =
+ [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken];
+ [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential
+ completion:^(FIRAuthDataResult *_Nullable authResult,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ [self assertUserFacebook:authResult.user];
+ XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]);
+ XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName);
+ XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID);
+ XCTAssertNil(error);
+
+ id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+ OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
+ OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kEmailDisplayName);
+ OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
+ // Get account info is expected to be invoked twice.
+ [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser];
+ [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser];
+
+ OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRSetAccountInfoRequest *_Nullable request,
+ FIRSetAccountInfoResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.accessToken, kAccessToken);
+ XCTAssertEqualObjects(request.password, kPassword);
+ XCTAssertNil(request.localID);
+ XCTAssertNil(request.displayName);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockSetAccountInfoResponse = OCMClassMock([FIRSetAccountInfoResponse class]);
+ callback(mockSetAccountInfoResponse, nil);
+ });
+ });
+
+ FIRAuthCredential *linkEmailCredential =
+ [FIREmailAuthProvider credentialWithEmail:kEmail password:kPassword];
+ [authResult.user linkAndRetrieveDataWithCredential:linkEmailCredential
+ completion:^(FIRAuthDataResult *_Nullable
+ linkAuthResult,
+ NSError *_Nullable error) {
+ XCTAssertNil(error);
+ XCTAssertEqualObjects(linkAuthResult.user.email, kEmail);
+ XCTAssertEqualObjects(linkAuthResult.user.displayName, kEmailDisplayName);
+
+ // Try linking same credential a second time to trigger client side error.
+ [authResult.user linkAndRetrieveDataWithCredential:linkEmailCredential
+ completion:^(FIRAuthDataResult *_Nullable
+ linkAuthResult,
+ NSError *_Nullable error) {
+ XCTAssertNil(linkAuthResult);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeProviderAlreadyLinked);
+ [expectation fulfill];
+ }];
+ }];
+
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testlinkEmailAndRetrieveDataError
+ @brief Tests the flow of an unsuccessful @c linkAndRetrieveDataWithCredential:completion:
+ invocation for email credential and an error from the backend.
+ */
+- (void)testlinkEmailAndRetrieveDataError {
+ [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID
+ federatedID:kFacebookID
+ displayName:kFacebookDisplayName
+ profile:[[self class] googleProfile]
+ providerIDToken:kFacebookIDToken
+ providerAccessToken:kFacebookAccessToken];
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ FIRAuthCredential *facebookCredential =
+ [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken];
+ [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential
+ completion:^(FIRAuthDataResult *_Nullable authResult,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ [self assertUserFacebook:authResult.user];
+ XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]);
+ XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName);
+ XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID);
+ XCTAssertNil(error);
+
+ OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRGetAccountInfoRequest *_Nullable request,
+ FIRGetAccountInfoResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.accessToken, kAccessToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ callback(nil, [FIRAuthErrorUtils tooManyRequestsErrorWithMessage:nil]);
+ });
+ });
+
+ FIRAuthCredential *linkEmailCredential =
+ [FIREmailAuthProvider credentialWithEmail:kEmail password:kPassword];
+ [authResult.user linkAndRetrieveDataWithCredential:linkEmailCredential
+ completion:^(FIRAuthDataResult *_Nullable
+ linkAuthResult,
+ NSError *_Nullable error) {
+ XCTAssertNil(linkAuthResult);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeTooManyRequests);
+ [expectation fulfill];
+ }];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testlinkCredentialSuccess
+ @brief Tests the flow of a successful @c linkWithCredential:completion: call, without additional
+ IDP data.
+ */
+- (void)testlinkCredentialSuccess {
+ [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID
+ federatedID:kFacebookID
+ displayName:kFacebookDisplayName
+ profile:[[self class] googleProfile]
+ providerIDToken:kFacebookIDToken
+ providerAccessToken:kFacebookAccessToken];
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ FIRAuthCredential *facebookCredential =
+ [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken];
+ [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential
+ completion:^(FIRAuthDataResult *_Nullable authResult,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ [self assertUserFacebook:authResult.user];
+ XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]);
+ XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName);
+ XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID);
+ XCTAssertNil(error);
+
+ [self expectVerifyAssertionRequest:FIRGoogleAuthProviderID
+ federatedID:kGoogleID
+ displayName:kGoogleDisplayName
+ profile:[[self class] googleProfile]
+ providerIDToken:kGoogleIDToken
+ providerAccessToken:kGoogleAccessToken];
+
+ FIRAuthCredential *linkGoogleCredential =
+ [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken];
+ [authResult.user linkWithCredential:linkGoogleCredential
+ completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertNil(error);
+ id<FIRUserInfo> userInfo = user.providerData.firstObject;
+ XCTAssertEqual(userInfo.providerID, FIRGoogleAuthProviderID);
+ XCTAssertEqual([FIRAuth auth].currentUser, authResult.user);
+ [expectation fulfill];
+ }];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ [self assertUserGoogle:[FIRAuth auth].currentUser];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testlinkCredentialError
+ @brief Tests the flow of an unsuccessful @c linkWithCredential:completion: call, with an error
+ from the backend.
+ */
+- (void)testlinkCredentialError {
+ [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID
+ federatedID:kFacebookID
+ displayName:kFacebookDisplayName
+ profile:[[self class] googleProfile]
+ providerIDToken:kFacebookIDToken
+ providerAccessToken:kFacebookAccessToken];
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ FIRAuthCredential *facebookCredential =
+ [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken];
+ [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential
+ completion:^(FIRAuthDataResult *_Nullable authResult,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ [self assertUserFacebook:authResult.user];
+ XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]);
+ XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName);
+ XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID);
+ XCTAssertNil(error);
+
+ OCMExpect([_mockBackend verifyAssertion:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRVerifyAssertionRequest *_Nullable request,
+ FIRVerifyAssertionResponseCallback callback) {
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ callback(nil, [FIRAuthErrorUtils userDisabledErrorWithMessage:nil]);
+ });
+ });
+
+ FIRAuthCredential *linkGoogleCredential =
+ [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken];
+ [authResult.user linkWithCredential:linkGoogleCredential
+ completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertNil(user);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeUserDisabled);
+ [expectation fulfill];
+ }];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testlinkCredentialProviderAlreadyLinkedError
+ @brief Tests the flow of an unsuccessful @c linkWithCredential:completion: call, with a client
+ side error.
+ */
+- (void)testlinkCredentialProviderAlreadyLinkedError {
+ [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID
+ federatedID:kFacebookID
+ displayName:kFacebookDisplayName
+ profile:[[self class] googleProfile]
+ providerIDToken:kFacebookIDToken
+ providerAccessToken:kFacebookAccessToken];
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ [[FIRAuth auth] signOut:NULL];
+ FIRAuthCredential *facebookCredential =
+ [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken];
+ [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential
+ completion:^(FIRAuthDataResult *_Nullable authResult,
+ NSError *_Nullable error) {
+ XCTAssertTrue([NSThread isMainThread]);
+ [self assertUserFacebook:authResult.user];
+ XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]);
+ XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName);
+ XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID);
+ XCTAssertNil(error);
+
+ FIRAuthCredential *linkFacebookCredential =
+ [FIRFacebookAuthProvider credentialWithAccessToken:kGoogleAccessToken];
+ [authResult.user linkWithCredential:linkFacebookCredential
+ completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertNil(user);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeProviderAlreadyLinked);
+ [expectation fulfill];
+ }];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testlinkPhoneAuthCredentialSuccess
+ @brief Tests the flow of a successful @c linkAndRetrieveDataWithCredential:completion:
+ call using a phoneAuthCredential.
+ */
+- (void)testlinkPhoneAuthCredentialSuccess {
+ id (^mockUserInfoWithPhoneNumber)(NSString *) = ^(NSString *phoneNumber) {
+ id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+ OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
+ OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
+ if (phoneNumber.length) {
+ NSDictionary *userInfoDictionary = @{ @"providerId" : FIRPhoneAuthProviderID };
+ FIRGetAccountInfoResponseProviderUserInfo *userInfo =
+ [[FIRGetAccountInfoResponseProviderUserInfo alloc] initWithDictionary:userInfoDictionary];
+ OCMStub([mockGetAccountInfoResponseUser providerUserInfo]).andReturn(@[ userInfo ]);
+ OCMStub([mockGetAccountInfoResponseUser phoneNumber]).andReturn(phoneNumber);
+ }
+ return mockGetAccountInfoResponseUser;
+ };
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ id userInfoResponse = mockUserInfoWithPhoneNumber(nil);
+ [self signInWithEmailPasswordWithMockUserInfoResponse:userInfoResponse
+ completion:^(FIRUser *user) {
+ [self expectVerifyPhoneNumberRequestWithPhoneNumber:kPhoneNumber error:nil];
+ id userInfoResponseUpdate = mockUserInfoWithPhoneNumber(kPhoneNumber);
+ [self expectGetAccountInfoWithMockUserInfoResponse:userInfoResponseUpdate];
+
+ FIRPhoneAuthCredential *credential =
+ [[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID
+ verificationCode:kVerificationCode];
+ [user linkAndRetrieveDataWithCredential:credential
+ completion:^(FIRAuthDataResult *_Nullable
+ linkAuthResult,
+ NSError *_Nullable error) {
+ XCTAssertNil(error);
+ XCTAssertEqualObjects([FIRAuth auth].currentUser.providerData.firstObject.providerID,
+ FIRPhoneAuthProviderID);
+ XCTAssertEqualObjects([FIRAuth auth].currentUser.phoneNumber, kPhoneNumber);
+ [expectation fulfill];
+ }];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testUnlinkPhoneAuthCredentialSuccess
+ @brief Tests the flow of a successful @c unlinkFromProvider:completion: call using a
+ @c FIRPhoneAuthProvider.
+ */
+- (void)testUnlinkPhoneAuthCredentialSuccess {
+ id (^mockUserInfoWithPhoneNumber)(NSString *) = ^(NSString *phoneNumber) {
+ id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+ OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
+ OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
+ if (phoneNumber.length) {
+ NSDictionary *userInfoDictionary = @{ @"providerId" : FIRPhoneAuthProviderID };
+ FIRGetAccountInfoResponseProviderUserInfo *userInfo =
+ [[FIRGetAccountInfoResponseProviderUserInfo alloc] initWithDictionary:userInfoDictionary];
+ OCMStub([mockGetAccountInfoResponseUser providerUserInfo]).andReturn(@[ userInfo ]);
+ OCMStub([mockGetAccountInfoResponseUser phoneNumber]).andReturn(phoneNumber);
+ }
+ return mockGetAccountInfoResponseUser;
+ };
+
+ OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRSetAccountInfoRequest *_Nullable request,
+ FIRSetAccountInfoResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.accessToken, kAccessToken);
+ XCTAssertNotNil(request.deleteProviders);
+ XCTAssertNil(request.email);
+ XCTAssertNil(request.localID);
+ XCTAssertNil(request.displayName);
+ XCTAssertNil(request.photoURL);
+ XCTAssertNil(request.password);
+ XCTAssertNil(request.providers);
+ XCTAssertNil(request.deleteAttributes);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockSetAccountInfoResponse = OCMClassMock([FIRSetAccountInfoResponse class]);
+ callback(mockSetAccountInfoResponse, nil);
+ });
+ });
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ id userInfoResponse = mockUserInfoWithPhoneNumber(nil);
+ [self signInWithEmailPasswordWithMockUserInfoResponse:userInfoResponse
+ completion:^(FIRUser *user) {
+ [self expectVerifyPhoneNumberRequestWithPhoneNumber:kPhoneNumber error:nil];
+ id userInfoResponseUpdate = mockUserInfoWithPhoneNumber(kPhoneNumber);
+ [self expectGetAccountInfoWithMockUserInfoResponse:userInfoResponseUpdate];
+
+ FIRPhoneAuthCredential *credential =
+ [[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID
+ verificationCode:kVerificationCode];
+ // Link phone credential.
+ [user linkAndRetrieveDataWithCredential:credential
+ completion:^(FIRAuthDataResult *_Nullable
+ linkAuthResult,
+ NSError *_Nullable error) {
+ XCTAssertNil(error);
+ XCTAssertEqualObjects([FIRAuth auth].currentUser.providerData.firstObject.providerID,
+ FIRPhoneAuthProviderID);
+ XCTAssertEqualObjects([FIRAuth auth].currentUser.phoneNumber, kPhoneNumber);
+ // Immediately unlink the phone auth provider.
+ [user unlinkFromProvider:FIRPhoneAuthProviderID
+ completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
+ XCTAssertNil(error);
+ XCTAssertNil([FIRAuth auth].currentUser.phoneNumber);
+ [expectation fulfill];
+ }];
+ }];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testlinkPhoneAuthCredentialFailure
+ @brief Tests the flow of a failed call to @c linkAndRetrieveDataWithCredential:completion: due
+ to a phone provider already being linked.
+ */
+- (void)testlinkPhoneAuthCredentialFailure {
+ id (^mockUserInfoWithPhoneNumber)(NSString *) = ^(NSString *phoneNumber) {
+ id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+ OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
+ OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
+ if (phoneNumber.length) {
+ OCMStub([mockGetAccountInfoResponseUser phoneNumber]).andReturn(phoneNumber);
+ }
+ return mockGetAccountInfoResponseUser;
+ };
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ id userInfoResponse = mockUserInfoWithPhoneNumber(nil);
+ [self signInWithEmailPasswordWithMockUserInfoResponse:userInfoResponse
+ completion:^(FIRUser *user) {
+ NSError *error = [FIRAuthErrorUtils providerAlreadyLinkedError];
+ [self expectVerifyPhoneNumberRequestWithPhoneNumber:nil error:error];
+ FIRPhoneAuthCredential *credential =
+ [[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID
+ verificationCode:kVerificationCode];
+ [user linkAndRetrieveDataWithCredential:credential
+ completion:^(FIRAuthDataResult *_Nullable
+ linkAuthResult,
+ NSError *_Nullable error) {
+ XCTAssertNotNil(error);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeProviderAlreadyLinked);
+ [expectation fulfill];
+ }];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testlinkPhoneCredentialAlreadyExistsError
+ @brief Tests the flow of @c linkAndRetrieveDataWithCredential:completion:
+ call using a phoneAuthCredential and a credential already exisits error. In this case we
+ should get a FIRAuthCredential in the error object.
+ */
+- (void)testlinkPhoneCredentialAlreadyExistsError {
+ id (^mockUserInfoWithPhoneNumber)(NSString *) = ^(NSString *phoneNumber) {
+ id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+ OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
+ OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
+ if (phoneNumber.length) {
+ OCMStub([mockGetAccountInfoResponseUser phoneNumber]).andReturn(phoneNumber);
+ }
+ return mockGetAccountInfoResponseUser;
+ };
+
+ void (^expectVerifyPhoneNumberRequest)(NSString *) = ^(NSString *phoneNumber) {
+ OCMExpect([_mockBackend verifyPhoneNumber:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRVerifyPhoneNumberRequest *_Nullable request,
+ FIRVerifyPhoneNumberResponseCallback callback) {
+ XCTAssertEqualObjects(request.verificationID, kVerificationID);
+ XCTAssertEqualObjects(request.verificationCode, kVerificationCode);
+ XCTAssertEqualObjects(request.accessToken, kAccessToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ FIRPhoneAuthCredential *credential =
+ [[FIRPhoneAuthCredential alloc] initWithTemporaryProof:kTemporaryProof
+ phoneNumber:kPhoneNumber
+ providerID:FIRPhoneAuthProviderID];
+ callback(nil,
+ [FIRAuthErrorUtils credentialAlreadyInUseErrorWithMessage:nil
+ credential:credential]);
+ });
+ });
+ };
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ id userInfoResponse = mockUserInfoWithPhoneNumber(nil);
+ [self signInWithEmailPasswordWithMockUserInfoResponse:userInfoResponse
+ completion:^(FIRUser *user) {
+ expectVerifyPhoneNumberRequest(kPhoneNumber);
+
+ FIRPhoneAuthCredential *credential =
+ [[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID
+ verificationCode:kVerificationCode];
+ [user linkAndRetrieveDataWithCredential:credential
+ completion:^(FIRAuthDataResult *_Nullable
+ linkAuthResult,
+ NSError *_Nullable error) {
+ XCTAssertNil(linkAuthResult);
+ XCTAssertEqual(error.code, FIRAuthErrorCodeCredentialAlreadyInUse);
+ FIRPhoneAuthCredential *credential = error.userInfo[FIRAuthUpdatedCredentialKey];
+ XCTAssertEqual(credential.temporaryProof, kTemporaryProof);
+ XCTAssertEqual(credential.phoneNumber, kPhoneNumber);
+ [expectation fulfill];
+ }];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
+#pragma mark - Helpers
+
+/** @fn signInWithEmailPasswordWithMockGetAccountInfoResponse:completion:
+ @brief Signs in with an email and password account with mocked backend end calls.
+ @param mockUserInfoResponse A mocked FIRGetAccountInfoResponseUser object.
+ @param completion The completion block that takes the newly signed-in user as the only
+ parameter.
+ */
+- (void)signInWithEmailPasswordWithMockUserInfoResponse:(id)mockUserInfoResponse
+ completion:(void (^)(FIRUser *user))completion {
+ OCMExpect([_mockBackend verifyPassword:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRVerifyPasswordRequest *_Nullable request,
+ FIRVerifyPasswordResponseCallback callback) {
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockVeriyPasswordResponse = OCMClassMock([FIRVerifyPasswordResponse class]);
+ OCMStub([mockVeriyPasswordResponse IDToken]).andReturn(kAccessToken);
+ OCMStub([mockVeriyPasswordResponse approximateExpirationDate])
+ .andReturn([NSDate dateWithTimeIntervalSinceNow:kAccessTokenTimeToLive]);
+ OCMStub([mockVeriyPasswordResponse refreshToken]).andReturn(kRefreshToken);
+ callback(mockVeriyPasswordResponse, nil);
+ });
+ });
+ [self expectGetAccountInfoWithMockUserInfoResponse:mockUserInfoResponse];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] signInWithEmail:kEmail password:kPassword completion:^(FIRUser *_Nullable user,
+ NSError *_Nullable error) {
+ XCTAssertNotNil(user);
+ XCTAssertNil(error);
+ completion(user);
+ }];
+}
+
+/** @fn expectGetAccountInfoWithMockUserInfoResponse:
+ @brief Expects a GetAccountInfo request on the mock backend and calls back with provided
+ fake account data.
+ @param mockUserInfoResponse A mock @c FIRGetAccountInfoResponseUser object containing user info.
+ */
+- (void)expectGetAccountInfoWithMockUserInfoResponse:(id)mockUserInfoResponse {
+ OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRGetAccountInfoRequest *_Nullable request,
+ FIRGetAccountInfoResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.accessToken, kAccessToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockGetAccountInfoResponse = OCMClassMock([FIRGetAccountInfoResponse class]);
+ OCMStub([mockGetAccountInfoResponse users]).andReturn(@[ mockUserInfoResponse ]);
+ callback(mockGetAccountInfoResponse, nil);
+ });
+ });
+}
+
+/** @fn dictionaryWithUserInfoArray:
+ @brief Converts an array of @c FIRUserInfo into a dictionary that indexed by provider IDs.
+ @param userInfoArray An array of @c FIRUserInfo objects.
+ @return A dictionary contains same values as @c userInfoArray does but keyed by their
+ @c providerID .
+ */
+- (NSDictionary<NSString *, id<FIRUserInfo>> *)
+ dictionaryWithUserInfoArray:(NSArray<id<FIRUserInfo>> *)userInfoArray {
+ NSMutableDictionary<NSString *, id<FIRUserInfo>> *map =
+ [NSMutableDictionary dictionaryWithCapacity:userInfoArray.count];
+ for (id<FIRUserInfo> userInfo in userInfoArray) {
+ XCTAssertNil(map[userInfo.providerID]);
+ map[userInfo.providerID] = userInfo;
+ }
+ return map;
+}
+
+/** @fn stubSecureTokensWithMockResponse
+ @brief Creates stubs on the mock response object with access and refresh tokens
+ @param mockResponse The mock response object.
+ */
+- (void)stubTokensWithMockResponse:(id)mockResponse {
+ OCMStub([mockResponse IDToken]).andReturn(kAccessToken);
+ OCMStub([mockResponse approximateExpirationDate])
+ .andReturn([NSDate dateWithTimeIntervalSinceNow:kAccessTokenTimeToLive]);
+ OCMStub([mockResponse refreshToken]).andReturn(kRefreshToken);
+}
+
+/** @fn assertUserGoogle
+ @brief Asserts the given FIRUser matching the fake data returned by
+ @c expectGetAccountInfo:federatedID:displayName: .
+ @param user The user object to be verified.
+ */
+- (void)assertUserGoogle:(FIRUser *)user {
+ XCTAssertNotNil(user);
+ XCTAssertEqualObjects(user.uid, kLocalID);
+ XCTAssertEqualObjects(user.displayName, kGoogleDisplayName);
+ XCTAssertEqual(user.providerData.count, 1u);
+ id<FIRUserInfo> googleUserInfo = user.providerData[0];
+ XCTAssertEqualObjects(googleUserInfo.providerID, FIRGoogleAuthProviderID);
+ XCTAssertEqualObjects(googleUserInfo.uid, kGoogleID);
+ XCTAssertEqualObjects(googleUserInfo.displayName, kGoogleDisplayName);
+ XCTAssertEqualObjects(googleUserInfo.email, kGoogleEmail);
+}
+
+/** @fn assertUserFacebook
+ @brief Asserts the given FIRUser matching the fake data returned by
+ @c expectGetAccountInfo:federatedID:displayName: .
+ @param user The user object to be verified.
+ */
+- (void)assertUserFacebook:(FIRUser *)user {
+ XCTAssertNotNil(user);
+ XCTAssertEqualObjects(user.uid, kLocalID);
+ XCTAssertEqualObjects(user.displayName, kFacebookDisplayName);
+ XCTAssertEqual(user.providerData.count, 1u);
+ id<FIRUserInfo> googleUserInfo = user.providerData[0];
+ XCTAssertEqualObjects(googleUserInfo.providerID, FIRFacebookAuthProviderID);
+ XCTAssertEqualObjects(googleUserInfo.uid, kFacebookID);
+ XCTAssertEqualObjects(googleUserInfo.displayName, kFacebookDisplayName);
+ XCTAssertEqualObjects(googleUserInfo.email, kGoogleEmail);
+}
+
+/** @fn expectGetAccountInfo:federatedID:displayName:
+ @brief Expects a GetAccountInfo request on the mock backend and calls back with fake account
+ data for a Google Sign-In user.
+ */
+- (void)expectGetAccountInfo:(NSString *)providerId
+ federatedID:(NSString *)federatedID
+ displayName:(NSString *)displayName {
+ OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRGetAccountInfoRequest *_Nullable request,
+ FIRGetAccountInfoResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.accessToken, kAccessToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockGoogleUserInfo = OCMClassMock([FIRGetAccountInfoResponseProviderUserInfo class]);
+ OCMStub([mockGoogleUserInfo providerID]).andReturn(providerId);
+ OCMStub([mockGoogleUserInfo displayName]).andReturn(displayName);
+ OCMStub([mockGoogleUserInfo federatedID]).andReturn(federatedID);
+ OCMStub([mockGoogleUserInfo email]).andReturn(kGoogleEmail);
+ id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+ OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(displayName);
+ OCMStub([mockGetAccountInfoResponseUser providerUserInfo])
+ .andReturn((@[ mockGoogleUserInfo ]));
+ id mockGetAccountInfoResponse = OCMClassMock([FIRGetAccountInfoResponse class]);
+ OCMStub([mockGetAccountInfoResponse users]).andReturn(@[ mockGetAccountInfoResponseUser ]);
+ callback(mockGetAccountInfoResponse, nil);
+ });
+ });
+}
+
+/** @fn expectVerifyAssertionRequest:federatedID:displayName:profile:providerAccessToken:
+ @brief Expects a Verify Assertion request on the mock backend and calls back with fake account
+ data.
+ */
+- (void)expectVerifyAssertionRequest:(NSString *)providerId
+ federatedID:(NSString *)federatedID
+ displayName:(NSString *)displayName
+ profile:(NSDictionary *)profile
+ providerIDToken:(nullable NSString *)providerIDToken
+ providerAccessToken:(NSString *)providerAccessToken {
+ OCMExpect([_mockBackend verifyAssertion:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRVerifyAssertionRequest *_Nullable request,
+ FIRVerifyAssertionResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.providerID, providerId);
+ XCTAssertEqualObjects(request.providerIDToken, providerIDToken);
+ XCTAssertEqualObjects(request.providerAccessToken, providerAccessToken);
+ XCTAssertTrue(request.returnSecureToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockVeriyAssertionResponse = OCMClassMock([FIRVerifyAssertionResponse class]);
+ OCMStub([mockVeriyAssertionResponse federatedID]).andReturn(federatedID);
+ OCMStub([mockVeriyAssertionResponse providerID]).andReturn(providerId);
+ OCMStub([mockVeriyAssertionResponse localID]).andReturn(kLocalID);
+ OCMStub([mockVeriyAssertionResponse displayName]).andReturn(displayName);
+ OCMStub([mockVeriyAssertionResponse profile]).andReturn(profile);
+ OCMStub([mockVeriyAssertionResponse username]).andReturn(kUserName);
+ [self stubTokensWithMockResponse:mockVeriyAssertionResponse];
+ callback(mockVeriyAssertionResponse, nil);
+ });
+ });
+ [self expectGetAccountInfo:providerId federatedID:federatedID displayName:displayName];
+}
+
+/** @fn expectVerifyPhoneNumberRequestWithPhoneNumber:error:
+ @brief Expects a verify phone numner request on the mock backend and calls back with fake
+ account data or an error.
+ @param phoneNumber Optionally; The phone number to use in the mocked response.
+ @param error Optionally; The error to return in the mocked response.
+ */
+- (void)expectVerifyPhoneNumberRequestWithPhoneNumber:(nullable NSString *)phoneNumber
+ error:(nullable NSError*)error {
+ OCMExpect([_mockBackend verifyPhoneNumber:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRVerifyPhoneNumberRequest *_Nullable request,
+ FIRVerifyPhoneNumberResponseCallback callback) {
+ XCTAssertEqualObjects(request.verificationID, kVerificationID);
+ XCTAssertEqualObjects(request.verificationCode, kVerificationCode);
+ XCTAssertEqualObjects(request.accessToken, kAccessToken);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ if (error) {
+ callback(nil, error);
+ return;
+ }
+ id mockVerifyPhoneNumberResponse = OCMClassMock([FIRVerifyPhoneNumberResponse class]);
+ OCMStub([mockVerifyPhoneNumberResponse phoneNumber]).andReturn(phoneNumber);
+ callback(mockVerifyPhoneNumberResponse, nil);
+ });
+ });
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Example/Auth/Tests/FIRVerifyAssertionRequestTests.m b/Example/Auth/Tests/FIRVerifyAssertionRequestTests.m
new file mode 100644
index 0000000..becc420
--- /dev/null
+++ b/Example/Auth/Tests/FIRVerifyAssertionRequestTests.m
@@ -0,0 +1,232 @@
+/*
+ * 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 "FIRAuthErrors.h"
+#import "FIRAuthBackend.h"
+#import "FIRGetOOBConfirmationCodeResponse.h"
+#import "FIRVerifyAssertionRequest.h"
+#import "FIRVerifyAssertionResponse.h"
+#import "FIRFakeBackendRPCIssuer.h"
+#import <GoogleToolboxForMac/GTMNSDictionary+URLArguments.h>
+
+/** @var kTestAPIKey
+ @brief Fake API key used for testing.
+ */
+static NSString *const kTestAPIKey = @"APIKey";
+
+/** @var kTestPostBodyKey
+ @brief The name of the "postBody" property in the response.
+ */
+static NSString *const kPostBodyKey = @"postBody";
+
+/** @var kExpectedAPIURL
+ @brief The expected URL for test calls.
+ */
+static NSString *const kExpectedAPIURL =
+ @"https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAssertion?key=APIKey";
+
+/** @var kIDTokenKey
+ @brief The name of the "idToken" property in the response.
+ */
+static NSString *const kIDTokenKey = @"idToken";
+
+/** @var kTestAccessToken
+ @brief Fake access token used for testing.
+ */
+static NSString *const kTestAccessToken = @"ACCESS_TOKEN";
+
+/** @var kProviderIDKey
+ @brief The key for the "providerId" value in the request.
+ */
+static NSString *const kProviderIDKey = @"providerId";
+
+/** @var kTestProviderID
+ @brief Fake provider ID used for testing.
+ */
+static NSString *const kTestProviderID = @"ProviderID";
+
+/** @var kProviderIDTokenKey
+ @brief The key for the "id_token" value in the request.
+ */
+static NSString *const kProviderIDTokenKey = @"id_token";
+
+/** @var kTestProviderIDToken
+ @brief Fake provider ID token used for testing.
+ */
+static NSString *const kTestProviderIDToken = @"ProviderIDToken";
+
+/** @var kInputEmailKey
+ @brief The key for the "inputEmail" value in the request.
+ */
+static NSString *const kInputEmailKey = @"identifier";
+
+/** @var kTestInputEmail
+ @brief Fake input email used for testing.
+ */
+static NSString *const kTestInputEmail = @"testInputEmail";
+
+/** @var kPendingIDTokenKey
+ @brief The key for the "pendingIdToken" value in the request.
+ */
+static NSString *const kPendingIDTokenKey = @"pendingIdToken";
+
+/** @var kTestPendingToken
+ @brief Fake pending token used for testing.
+ */
+static NSString *const kTestPendingToken = @"testPendingToken";
+
+/** @var kProviderAccessTokenKey
+ @brief The key for the "access_token" value in the request.
+ */
+static NSString *const kProviderAccessTokenKey = @"access_token";
+
+/** @var kTestProviderAccessToken
+ @brief Fake @c providerAccessToken used for testing the request.
+ */
+static NSString *const kTestProviderAccessToken = @"testProviderAccessToken";
+
+/** @var kProviderOAuthTokenSecretKey
+ @brief The key for the "oauth_token_secret" value in the request.
+ */
+static NSString *const kProviderOAuthTokenSecretKey = @"oauth_token_secret";
+
+/** @var kTestProviderOAuthTokenSecret
+ @brief Fake @c providerOAuthTokenSecret used for testing the request.
+ */
+static NSString *const kTestProviderOAuthTokenSecret = @"testProviderOAuthTokenSecret";
+
+/** @var kReturnSecureTokenKey
+ @brief The key for the "returnSecureToken" value in the request.
+ */
+static NSString *const kReturnSecureTokenKey = @"returnSecureToken";
+
+/** @var kAutoCreateKey
+ @brief The key for the "auto-create" value in the request.
+ */
+static NSString *const kAutoCreateKey = @"autoCreate";
+
+/** @class FIRVerifyAssertionRequestTests
+ @brief Tests for @c FIRVerifyAssertionReuqest
+ */
+@interface FIRVerifyAssertionRequestTests : XCTestCase
+@end
+@implementation FIRVerifyAssertionRequestTests{
+ /** @var _RPCIssuer
+ @brief This backend RPC issuer is used to fake network responses for each test in the suite.
+ In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it.
+ */
+ FIRFakeBackendRPCIssuer *_RPCIssuer;
+}
+
+- (void)setUp {
+ [super setUp];
+ FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init];
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer];
+ _RPCIssuer = RPCIssuer;
+}
+
+- (void)tearDown {
+ _RPCIssuer = nil;
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
+ [super tearDown];
+}
+
+/** @fn testVerifyAssertionRequestMissingTokens
+ @brief Tests the request with missing @c providerAccessToken and @c provideIDToken.
+ @remarks The request creation will raise an @c NSInvalidArgumentException exception when both
+ these tokens are missing.
+ */
+- (void)testVerifyAssertionRequestMissingTokens {
+ FIRVerifyAssertionRequest *request =
+ [[FIRVerifyAssertionRequest alloc] initWithAPIKey:kTestAPIKey providerID:kTestProviderID];
+
+ FIRVerifyAssertionResponseCallback callback =
+ ^(FIRVerifyAssertionResponse *_Nullable response, NSError *_Nullable error) {};
+ void (^verifyAssertionBlock)(void) = ^{
+ [FIRAuthBackend verifyAssertion:request callback:callback];
+ };
+ XCTAssertThrowsSpecificNamed(verifyAssertionBlock(), NSException, NSInvalidArgumentException,
+ @"Either IDToken or accessToken must be supplied.");
+ XCTAssertNil(_RPCIssuer.decodedRequest[kPostBodyKey]);
+}
+
+/** @fn testVerifyAssertionRequestProviderAccessToken
+ @brief Tests the verify assertion request with the @c providerAccessToken field set.
+ @remarks The presence of the @c providerAccessToken will prevent an @c
+ NSInvalidArgumentException exception from being raised.
+ */
+- (void)testVerifyAssertionRequestProviderAccessToken {
+ FIRVerifyAssertionRequest *request =
+ [[FIRVerifyAssertionRequest alloc] initWithAPIKey:kTestAPIKey providerID:kTestProviderID];
+ request.providerAccessToken = kTestProviderAccessToken;
+ request.returnSecureToken = NO;
+ [FIRAuthBackend verifyAssertion:request
+ callback:^(FIRVerifyAssertionResponse *_Nullable response,
+ NSError *_Nullable error) {
+ }];
+
+ NSDictionary *postBody = @{
+ kProviderIDKey : kTestProviderID,
+ kProviderAccessTokenKey : kTestProviderAccessToken
+ };
+ NSString *postBodyArgs = [postBody gtm_httpArgumentsString];
+ XCTAssertEqualObjects(_RPCIssuer.requestURL.absoluteString, kExpectedAPIURL);
+ XCTAssertNotNil(_RPCIssuer.decodedRequest[kPostBodyKey]);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kPostBodyKey], postBodyArgs);
+ XCTAssertNil(_RPCIssuer.decodedRequest[kIDTokenKey]);
+ XCTAssertNil(_RPCIssuer.decodedRequest[kReturnSecureTokenKey]);
+ // Auto-create flag Should be true by default.
+ XCTAssertTrue([_RPCIssuer.decodedRequest[kAutoCreateKey] boolValue]);
+}
+
+/** @fn testVerifyAssertionRequestOptionalFields
+ @brief Tests the verify assertion request with all optinal fields set.
+ */
+- (void)testVerifyAssertionRequestOptionalFields {
+ FIRVerifyAssertionRequest *request =
+ [[FIRVerifyAssertionRequest alloc] initWithAPIKey:kTestAPIKey providerID:kTestProviderID];
+ request.providerIDToken = kTestProviderIDToken;
+ request.providerAccessToken = kTestProviderAccessToken;
+ request.accessToken = kTestAccessToken;
+ request.inputEmail = kTestInputEmail;
+ request.pendingIDToken = kTestPendingToken;
+ request.providerOAuthTokenSecret = kTestProviderOAuthTokenSecret;
+ request.autoCreate = NO;
+
+ [FIRAuthBackend verifyAssertion:request
+ callback:^(FIRVerifyAssertionResponse *_Nullable response,
+ NSError *_Nullable error) {
+ }];
+
+ NSDictionary *postBody = @{
+ kProviderIDKey : kTestProviderID,
+ kProviderIDTokenKey : kTestProviderIDToken,
+ kProviderAccessTokenKey : kTestProviderAccessToken,
+ kProviderOAuthTokenSecretKey : kTestProviderOAuthTokenSecret,
+ kInputEmailKey : kTestInputEmail
+ };
+ NSString *postBodyArgs = [postBody gtm_httpArgumentsString];
+ XCTAssertEqualObjects(_RPCIssuer.requestURL.absoluteString, kExpectedAPIURL);
+ XCTAssertNotNil(_RPCIssuer.decodedRequest[kPostBodyKey]);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kPostBodyKey], postBodyArgs);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kIDTokenKey], kTestAccessToken);
+ XCTAssertTrue([_RPCIssuer.decodedRequest[kReturnSecureTokenKey] boolValue]);
+ XCTAssertFalse([_RPCIssuer.decodedRequest[kAutoCreateKey] boolValue]);
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRVerifyAssertionResponseTests.m b/Example/Auth/Tests/FIRVerifyAssertionResponseTests.m
new file mode 100644
index 0000000..cd9e771
--- /dev/null
+++ b/Example/Auth/Tests/FIRVerifyAssertionResponseTests.m
@@ -0,0 +1,426 @@
+/*
+ * 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 "FIRAuthErrors.h"
+#import "FIRAuthBackend.h"
+#import "FIRGetOOBConfirmationCodeResponse.h"
+#import "FIRVerifyAssertionRequest.h"
+#import "FIRVerifyAssertionResponse.h"
+#import "FIRFakeBackendRPCIssuer.h"
+
+/** @var kTestAPIKey
+ @brief Fake API key used for testing.
+ */
+static NSString *const kTestAPIKey = @"APIKey";
+
+/** @var kProviderIDKey
+ @brief The name of the "providerId" property in the response.
+ */
+static NSString *const kProviderIDKey = @"providerId";
+
+/** @var kIDTokenKey
+ @brief The name of the "IDToken" property in the response.
+ */
+static NSString *const kIDTokenKey = @"idToken";
+
+/** @var kExpiresInKey
+ @brief The name of the "expiresIn" property in the response.
+ */
+static NSString *const kExpiresInKey = @"expiresIn";
+
+/** @var kRefreshTokenKey
+ @brief The name of the "refreshToken" property in the response.
+ */
+static NSString *const kRefreshTokenKey = @"refreshToken";
+
+/** @var kVerifiedProviderKey
+ @brief The name of the "VerifiedProvider" property in the response.
+ */
+static NSString *const kVerifiedProviderKey = @"verifiedProvider";
+
+/** @var kRawUserInfoKey
+ @brief The name of the "rawUserInfo" property in the response.
+ */
+static NSString *const kRawUserInfoKey = @"rawUserInfo";
+
+/** @var kUsernameKey
+ @brief The name of the "username" property in the response.
+ */
+static NSString *const kUsernameKey = @"username";
+
+/** @var kIsNewUserKey
+ @brief The name of the "isNewUser" property in the response.
+ */
+static NSString *const kIsNewUserKey = @"isNewUser";
+
+/** @var kTestProviderID
+ @brief Fake provider ID used for testing.
+ */
+static NSString *const kTestProviderID = @"ProviderID";
+
+/** @var kTestProviderIDToken
+ @brief Fake provider ID token used for testing.
+ */
+static NSString *const kTestProviderIDToken = @"ProviderIDToken";
+
+/** @var kTestIDToken
+ @brief Testing ID token for verifying assertion.
+ */
+static NSString *const kTestIDToken = @"ID_TOKEN";
+
+/** @var kTestExpiresIn
+ @brief Fake token expiration time.
+ */
+static NSString *const kTestExpiresIn = @"12345";
+
+/** @var kTestRefreshToken
+ @brief Fake refresh token.
+ */
+static NSString *const kTestRefreshToken = @"REFRESH_TOKEN";
+
+/** @var kTestProvider
+ @brief Fake provider used for testing.
+ */
+static NSString *const kTestProvider = @"Provider";
+
+/** @var kPhotoUrlKey
+ @brief The name of the "PhotoUrl" property in the response.
+ */
+static NSString *const kPhotoUrlKey = @"photoUrl";
+
+/** @var kTestPhotoUrl
+ @brief The "PhotoUrl" value for testing the response.
+ */
+static NSString *const kTestPhotoUrl = @"www.example.com";
+
+/** @var kUsername
+ @brief The "username" value for testing the response.
+ */
+static NSString *const kUsername = @"Joe Doe";
+
+/** @var testInvalidCredentialError
+ @brief This is the error message the server will respond with if the IDP token or requestUri is
+ invalid.
+ */
+static NSString *const ktestInvalidCredentialError = @"INVALID_IDP_RESPONSE";
+
+/** @var kUserDisabledErrorMessage
+ @brief This is the error message the server will respond with if the user's account has been
+ disabled.
+ */
+static NSString *const kUserDisabledErrorMessage = @"USER_DISABLED";
+
+/** @var kOperationNotAllowedErrorMessage
+ @brief This is the error message the server will respond with if Admin disables IDP specified by
+ provider.
+ */
+static NSString *const kOperationNotAllowedErrorMessage = @"OPERATION_NOT_ALLOWED";
+
+/** @var kPasswordLoginDisabledErrorMessage
+ @brief This is the error message the server responds with if password login is disabled.
+ */
+static NSString *const kPasswordLoginDisabledErrorMessage = @"PASSWORD_LOGIN_DISABLED";
+
+/** @var kFederatedUserIDAlreadyLinkedMessage
+ @brief This is the error message the server will respond with if the federated user ID has been
+ already linked with another account.
+ */
+static NSString *const kFederatedUserIDAlreadyLinkedMessage = @"FEDERATED_USER_ID_ALREADY_LINKED:";
+
+/** @var kEpsilon
+ @brief Allowed difference when comparing floating point numbers.
+ */
+static const double kEpsilon = 1e-3;
+
+/** @class FIRVerifyAssertionResponseTests
+ @brief Tests for @c FIRVerifyAssertionResponse
+ */
+@interface FIRVerifyAssertionResponseTests : XCTestCase
+@end
+@implementation FIRVerifyAssertionResponseTests {
+ /** @var _RPCIssuer
+ @brief This backend RPC issuer is used to fake network responses for each test in the suite.
+ In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it.
+ */
+ FIRFakeBackendRPCIssuer *_RPCIssuer;
+}
+
+/** @fn profile
+ @brief The "rawUserInfo" value for testing the response.
+ */
++ (NSDictionary *)profile {
+ static NSDictionary *kGoogleProfile = nil;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ kGoogleProfile = @{
+ @"iss": @"https://accounts.google.com\\",
+ @"email": @"test@email.com",
+ @"given_name": @"User",
+ @"family_name": @"Doe"
+ };
+ });
+ return kGoogleProfile;
+}
+
+- (void)setUp {
+ [super setUp];
+ FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init];
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer];
+ _RPCIssuer = RPCIssuer;
+}
+
+- (void)tearDown {
+ _RPCIssuer = nil;
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
+ [super tearDown];
+}
+
+/** @fn testInvalidIDPResponseError
+ @brief This test simulates @c invalidIDPResponseError with @c FIRAuthErrorCodeInvalidIDPResponse
+ error code.
+ */
+- (void)testInvalidIDPResponseError {
+ FIRVerifyAssertionRequest *request =
+ [[FIRVerifyAssertionRequest alloc] initWithAPIKey:kTestAPIKey providerID:kTestProviderID];
+ request.providerIDToken = kTestProviderIDToken;
+
+ __block BOOL callbackInvoked;
+ __block FIRVerifyAssertionResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend verifyAssertion:request
+ callback:^(FIRVerifyAssertionResponse*_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:ktestInvalidCredentialError];
+ XCTAssert(callbackInvoked);
+ XCTAssertNotNil(RPCError);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeInvalidCredential);
+}
+
+/** @fn testUserDisabledError
+ @brief This test simulates @c userDisabledError with @c
+ FIRAuthErrorCodeUserDisabled error code.
+ */
+- (void)testUserDisabledError {
+ FIRVerifyAssertionRequest *request =
+ [[FIRVerifyAssertionRequest alloc] initWithAPIKey:kTestAPIKey providerID:kTestProviderID];
+ request.providerIDToken = kTestProviderIDToken;
+
+ __block BOOL callbackInvoked;
+ __block FIRVerifyAssertionResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend verifyAssertion:request
+ callback:^(FIRVerifyAssertionResponse*_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kUserDisabledErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNotNil(RPCError);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeUserDisabled);
+}
+
+/** @fn testCredentialAlreadyInUseError
+ @brief This test simulates a @c FIRAuthErrorCodeCredentialAlreadyInUse error.
+ */
+- (void)testCredentialAlreadyInUseError {
+ FIRVerifyAssertionRequest *request =
+ [[FIRVerifyAssertionRequest alloc] initWithAPIKey:kTestAPIKey providerID:kTestProviderID];
+ request.providerIDToken = kTestProviderIDToken;
+
+ __block BOOL callbackInvoked;
+ __block FIRVerifyAssertionResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend verifyAssertion:request
+ callback:^(FIRVerifyAssertionResponse*_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kFederatedUserIDAlreadyLinkedMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNotNil(RPCError);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeCredentialAlreadyInUse);
+}
+
+/** @fn testOperationNotAllowedError
+ @brief This test simulates a @c FIRAuthErrorCodeOperationNotAllowed error.
+ */
+- (void)testOperationNotAllowedError {
+ FIRVerifyAssertionRequest *request =
+ [[FIRVerifyAssertionRequest alloc] initWithAPIKey:kTestAPIKey providerID:kTestProviderID];
+ request.providerIDToken = kTestProviderIDToken;
+
+ __block BOOL callbackInvoked;
+ __block FIRVerifyAssertionResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend verifyAssertion:request
+ callback:^(FIRVerifyAssertionResponse*_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kOperationNotAllowedErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNotNil(RPCError);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeOperationNotAllowed);
+}
+
+/** @fn testPasswordLoginDisabledError
+ @brief This test simulates a @c FIRAuthErrorCodeOperationNotAllowed error.
+ */
+- (void)testPasswordLoginDisabledError {
+ FIRVerifyAssertionRequest *request =
+ [[FIRVerifyAssertionRequest alloc] initWithAPIKey:kTestAPIKey providerID:kTestProviderID];
+ request.providerIDToken = kTestProviderIDToken;
+
+ __block BOOL callbackInvoked;
+ __block FIRVerifyAssertionResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend verifyAssertion:request
+ callback:^(FIRVerifyAssertionResponse*_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kPasswordLoginDisabledErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNotNil(RPCError);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeOperationNotAllowed);
+}
+
+/** @fn testSuccessfulVerifyAssertionResponse
+ @brief This test simulates a successful verify assertion flow.
+ */
+- (void)testSuccessfulVerifyAssertionResponse {
+ FIRVerifyAssertionRequest *request =
+ [[FIRVerifyAssertionRequest alloc] initWithAPIKey:kTestAPIKey providerID:kTestProviderID];
+ request.providerIDToken = kTestProviderIDToken;
+
+ __block BOOL callbackInvoked;
+ __block FIRVerifyAssertionResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend verifyAssertion:request
+ callback:^(FIRVerifyAssertionResponse*_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithJSON:@{
+ kProviderIDKey : kTestProviderID,
+ kIDTokenKey : kTestIDToken,
+ kExpiresInKey : kTestExpiresIn,
+ kRefreshTokenKey : kTestRefreshToken,
+ kVerifiedProviderKey : @[ kTestProvider ],
+ kPhotoUrlKey : kTestPhotoUrl,
+ kUsernameKey : kUsername,
+ kIsNewUserKey : @YES,
+ kRawUserInfoKey : [[self class] profile]
+ }];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCError);
+ XCTAssertNotNil(RPCResponse);
+ XCTAssertEqualObjects(RPCResponse.IDToken, kTestIDToken);
+ NSTimeInterval expiresIn = [RPCResponse.approximateExpirationDate timeIntervalSinceNow];
+ XCTAssertLessThanOrEqual(fabs(expiresIn - [kTestExpiresIn doubleValue]), kEpsilon);
+ XCTAssertEqualObjects(RPCResponse.refreshToken, kTestRefreshToken);
+ XCTAssertEqualObjects(RPCResponse.verifiedProvider, @[ kTestProvider ]);
+ XCTAssertEqualObjects(RPCResponse.photoURL, [NSURL URLWithString:kTestPhotoUrl]);
+ XCTAssertEqualObjects(RPCResponse.username, kUsername);
+ XCTAssertEqualObjects(RPCResponse.profile, [[self class] profile]);
+ XCTAssertEqualObjects(RPCResponse.providerID, kTestProviderID);
+ XCTAssertTrue(RPCResponse.isNewUser);
+}
+
+/** @fn testSuccessfulVerifyAssertionResponseWithTextData
+ @brief This test simulates a successful verify assertion flow when response collection
+ fields are sent as text values.
+ */
+- (void)testSuccessfulVerifyAssertionResponseWithTextData {
+ FIRVerifyAssertionRequest *request =
+ [[FIRVerifyAssertionRequest alloc] initWithAPIKey:kTestAPIKey providerID:kTestProviderID];
+ request.providerIDToken = kTestProviderIDToken;
+
+ __block BOOL callbackInvoked;
+ __block FIRVerifyAssertionResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend verifyAssertion:request
+ callback:^(FIRVerifyAssertionResponse*_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+
+ [_RPCIssuer respondWithJSON:@{
+ kProviderIDKey : kTestProviderID,
+ kIDTokenKey : kTestIDToken,
+ kExpiresInKey : kTestExpiresIn,
+ kRefreshTokenKey : kTestRefreshToken,
+ kVerifiedProviderKey : [[self class] convertToJSONString:@[ kTestProvider ]],
+ kPhotoUrlKey : kTestPhotoUrl,
+ kUsernameKey : kUsername,
+ kIsNewUserKey : @NO,
+ kRawUserInfoKey : [[self class] convertToJSONString:[[self class] profile]]
+ }];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCError);
+ XCTAssertNotNil(RPCResponse);
+ XCTAssertEqualObjects(RPCResponse.IDToken, kTestIDToken);
+ NSTimeInterval expiresIn = [RPCResponse.approximateExpirationDate timeIntervalSinceNow];
+ XCTAssertLessThanOrEqual(fabs(expiresIn - [kTestExpiresIn doubleValue]), kEpsilon);
+ XCTAssertEqualObjects(RPCResponse.refreshToken, kTestRefreshToken);
+ XCTAssertEqualObjects(RPCResponse.verifiedProvider, @[ kTestProvider ]);
+ XCTAssertEqualObjects(RPCResponse.photoURL, [NSURL URLWithString:kTestPhotoUrl]);
+ XCTAssertEqualObjects(RPCResponse.username, kUsername);
+ XCTAssertEqualObjects(RPCResponse.profile, [[self class] profile]);
+ XCTAssertEqualObjects(RPCResponse.providerID, kTestProviderID);
+ XCTAssertFalse(RPCResponse.isNewUser);
+}
+
+#pragma mark - Helpers
+
++ (NSString *)convertToJSONString:(NSObject *)object {
+ NSData *objectAsData = [NSJSONSerialization dataWithJSONObject:object
+ options:0
+ error:nil];
+ return [[NSString alloc] initWithData:objectAsData encoding:NSUTF8StringEncoding];
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRVerifyClientRequestTest.m b/Example/Auth/Tests/FIRVerifyClientRequestTest.m
new file mode 100644
index 0000000..dcb00f6
--- /dev/null
+++ b/Example/Auth/Tests/FIRVerifyClientRequestTest.m
@@ -0,0 +1,94 @@
+/*
+ * 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 "FIRAuthBackend.h"
+#import "FIRVerifyClientRequest.h"
+#import "FIRVerifyClientResponse.h"
+#import "FIRFakeBackendRPCIssuer.h"
+
+/** @var kFakeAppToken
+ @brief The fake app token to use in the test request.
+ */
+static NSString *const kFakeAppToken = @"appToken";
+
+/** @var kFakeAPIKey
+ @brief The fake API key to use in the test request.
+ */
+static NSString *const kFakeAPIKey = @"APIKey";
+
+/** @var kAppTokenKey
+ @brief The key for the appToken request paramenter.
+ */
+static NSString *const kAPPTokenKey = @"appToken";
+
+/** @var kIsSandboxKey
+ @brief The key for the isSandbox request parameter
+ */
+static NSString *const kIsSandboxKey = @"isSandbox";
+
+/** @var kExpectedAPIURL
+ @brief The expected URL for the test calls.
+ */
+static NSString *const kExpectedAPIURL =
+ @"https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyClient?key=APIKey";
+
+/** @class FIRVerifyClientRequestTest
+ @brief Tests for @c FIRVerifyClientRequests.
+ */
+@interface FIRVerifyClientRequestTest : XCTestCase
+@end
+
+@implementation FIRVerifyClientRequestTest {
+ /** @var _RPCIssuer
+ @brief This backend RPC issuer is used to fake network responses for each test in the suite.
+ In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it.
+ */
+ FIRFakeBackendRPCIssuer *_RPCIssuer;
+}
+
+- (void)setUp {
+ [super setUp];
+ FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init];
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer];
+ _RPCIssuer = RPCIssuer;
+}
+
+- (void)tearDown {
+ _RPCIssuer = nil;
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
+ [super tearDown];
+}
+
+/** @fn testVerifyClientRequest
+ @brief Tests the verify client request.
+ */
+- (void)testVerifyClientRequest {
+ FIRVerifyClientRequest *request =
+ [[FIRVerifyClientRequest alloc] initWithAppToken:kFakeAppToken
+ isSandbox:YES
+ APIKey:kFakeAPIKey];
+ [FIRAuthBackend verifyClient:request callback:^(FIRVerifyClientResponse *_Nullable response,
+ NSError *_Nullable error) {
+ }];
+ XCTAssertEqualObjects(_RPCIssuer.requestURL.absoluteString, kExpectedAPIURL);
+ XCTAssertNotNil(_RPCIssuer.decodedRequest);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kAPPTokenKey], kFakeAppToken);
+ XCTAssertTrue(_RPCIssuer.decodedRequest[kIsSandboxKey]);
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRVerifyClientResponseTests.m b/Example/Auth/Tests/FIRVerifyClientResponseTests.m
new file mode 100644
index 0000000..68b8feb
--- /dev/null
+++ b/Example/Auth/Tests/FIRVerifyClientResponseTests.m
@@ -0,0 +1,178 @@
+/*
+ * 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 "FIRAuthErrors.h"
+#import "FIRAuthBackend.h"
+#import "FIRVerifyClientRequest.h"
+#import "FIRVerifyClientResponse.h"
+#import "FIRFakeBackendRPCIssuer.h"
+
+/** @var kFakeAppToken
+ @brief The fake app token to use in the test request.
+ */
+static NSString *const kFakeAppToken = @"appToken";
+
+/** @var kFakeAPIKey
+ @brief The fake API key to use in the test request.
+ */
+static NSString *const kFakeAPIKey = @"APIKey";
+
+/** @var kAppTokenKey
+ @brief The key for the appToken request paramenter.
+ */
+static NSString *const kAPPTokenKey = @"appToken";
+
+/** @var kIsSandboxKey
+ @brief The key for the isSandbox request parameter
+ */
+static NSString *const kIsSandboxKey = @"isSandbox";
+
+/** @var kReceiptKey
+ @brief The key for the receipt response paramenter.
+ */
+static NSString *const kReceiptKey = @"receipt";
+
+/** @var kFakeReceipt
+ @brief The fake receipt returned in the response.
+ */
+static NSString *const kFakeReceipt = @"receipt";
+
+/** @var kSuggestedTimeOutKey
+ @brief The key for the suggested timeout response parameter
+ */
+static NSString *const kSuggestedTimeOutKey = @"suggestedTimeout";
+
+/** @var kFakeSuggestedTimeout
+ @brief The fake suggested timeout returned in the response.
+ */
+static NSString *const kFakeSuggestedTimeout = @"1234";
+
+/** @var kEpsilon
+ @brief Allowed difference when comparing floating point numbers.
+ */
+static const double kEpsilon = 1e-3;
+
+/** @var kMissingAppCredentialErrorMessage
+ @brief This is the error message the server will respond with if the APNS token is missing in a
+ verifyClient request is missing.
+ */
+static NSString *const kMissingAppCredentialErrorMessage = @"MISSING_APP_CREDENTIAL";
+
+/** @var kMissingAppCredentialErrorMessage
+ @brief This is the error message the server will respond with if the APNS token is missing in a
+ verifyClient request is invalid.
+ */
+static NSString *const kInvalidAppCredentialErrorMessage = @"INVALID_APP_CREDENTIAL";
+
+@interface FIRVerifyClientResponseTests : XCTestCase
+@end
+
+@implementation FIRVerifyClientResponseTests{
+ /** @var _RPCIssuer
+ @brief This backend RPC issuer is used to fake network responses for each test in the suite.
+ In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it.
+ */
+ FIRFakeBackendRPCIssuer *_RPCIssuer;
+}
+
+- (void)setUp {
+ FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init];
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer];
+ _RPCIssuer = RPCIssuer;
+}
+
+/** @fn testMissingAppCredentialError
+ @brief Tests that @c FIRAuthErrorCodeMissingAppCredential error.
+ */
+- (void)testMissingAppCredentialError {
+ FIRVerifyClientRequest *request =
+ [[FIRVerifyClientRequest alloc] initWithAppToken:kFakeAppToken
+ isSandbox:YES
+ APIKey:kFakeAPIKey];
+ __block BOOL callbackInvoked;
+ __block FIRVerifyClientResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend verifyClient:request
+ callback:^(FIRVerifyClientResponse *_Nullable response,
+ NSError *_Nullable error) {
+ RPCResponse = response;
+ RPCError = error;
+ callbackInvoked = YES;
+ }];
+ [_RPCIssuer respondWithServerErrorMessage:kMissingAppCredentialErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeMissingAppCredential);
+}
+
+/** @fn testInvalidAppCredentialError
+ @brief Tests that @c FIRAuthErrorCodeInvalidAppCredential error.
+ */
+- (void)testInvalidAppCredentialError {
+ FIRVerifyClientRequest *request =
+ [[FIRVerifyClientRequest alloc] initWithAppToken:kFakeAppToken
+ isSandbox:YES
+ APIKey:kFakeAPIKey];
+ __block BOOL callbackInvoked;
+ __block FIRVerifyClientResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend verifyClient:request
+ callback:^(FIRVerifyClientResponse *_Nullable response,
+ NSError *_Nullable error) {
+ RPCResponse = response;
+ RPCError = error;
+ callbackInvoked = YES;
+ }];
+ [_RPCIssuer respondWithServerErrorMessage:kInvalidAppCredentialErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeInvalidAppCredential);
+}
+
+/** @fn testSuccessfulVerifyClientResponse
+ @brief Tests a succesful attempt of the verify password flow.
+ */
+- (void)testSuccessfulVerifyPasswordResponse {
+ FIRVerifyClientRequest *request =
+ [[FIRVerifyClientRequest alloc] initWithAppToken:kFakeAppToken
+ isSandbox:YES
+ APIKey:kFakeAPIKey];
+ __block BOOL callbackInvoked;
+ __block FIRVerifyClientResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend verifyClient:request
+ callback:^(FIRVerifyClientResponse *_Nullable response,
+ NSError *_Nullable error) {
+ RPCResponse = response;
+ RPCError = error;
+ callbackInvoked = YES;
+ }];
+
+ [_RPCIssuer respondWithJSON:@{
+ kReceiptKey : kFakeReceipt,
+ kSuggestedTimeOutKey : kFakeSuggestedTimeout
+ }];
+
+ XCTAssert(callbackInvoked);
+ XCTAssertNotNil(RPCResponse);
+ XCTAssertEqualObjects(RPCResponse.receipt, kFakeReceipt);
+ NSTimeInterval suggestedTimeout = [RPCResponse.suggestedTimeOutDate timeIntervalSinceNow];
+ XCTAssertLessThanOrEqual(fabs(suggestedTimeout - [kFakeSuggestedTimeout doubleValue]), kEpsilon);
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRVerifyCustomTokenRequestTests.m b/Example/Auth/Tests/FIRVerifyCustomTokenRequestTests.m
new file mode 100644
index 0000000..9f65f73
--- /dev/null
+++ b/Example/Auth/Tests/FIRVerifyCustomTokenRequestTests.m
@@ -0,0 +1,110 @@
+/*
+ * 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 "FIRAuthErrors.h"
+#import "FIRAuthBackend.h"
+#import "FIRGetOOBConfirmationCodeResponse.h"
+#import "FIRVerifyCustomTokenRequest.h"
+#import "FIRVerifyCustomTokenResponse.h"
+#import "FIRFakeBackendRPCIssuer.h"
+
+/** @var kTestAPIKey
+ @brief Fake API key used for testing.
+ */
+static NSString *const kTestAPIKey = @"APIKey";
+
+/** @var kTestTokenKey
+ @brief The name of the "token" property in the response.
+ */
+static NSString *const kTestTokenKey = @"token";
+
+/** @var kTestToken
+ @brief testing token.
+ */
+static NSString *const kTestToken = @"test token";
+
+/** @var kReturnSecureTokenKey
+ @brief The key for the "returnSecureToken" value in the request.
+ */
+static NSString *const kReturnSecureTokenKey = @"returnSecureToken";
+
+/** @var kExpectedAPIURL
+ @brief The expected URL for test calls.
+ */
+static NSString *const kExpectedAPIURL =
+ @"https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCustomToken?key=APIKey";
+
+@interface FIRVerifyCustomTokenRequestTests : XCTestCase
+@end
+@implementation FIRVerifyCustomTokenRequestTests {
+ /** @var _RPCIssuer
+ @brief This backend RPC issuer is used to fake network responses for each test in the suite.
+ In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it.
+ */
+ FIRFakeBackendRPCIssuer *_RPCIssuer;
+}
+
+- (void)setUp {
+ [super setUp];
+ FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init];
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer];
+ _RPCIssuer = RPCIssuer;
+}
+
+- (void)tearDown {
+ _RPCIssuer = nil;
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
+ [super tearDown];
+}
+
+/** @fn testVerifyCustomTokenRequest
+ @brief Tests the verify custom token request.
+ */
+- (void)testVerifyCustomTokenRequest {
+ FIRVerifyCustomTokenRequest *request =
+ [[FIRVerifyCustomTokenRequest alloc] initWithToken:kTestToken APIKey:kTestAPIKey];
+ request.returnSecureToken = NO;
+ [FIRAuthBackend verifyCustomToken:request
+ callback:^(FIRVerifyCustomTokenResponse *_Nullable response,
+ NSError *_Nullable error) {
+ }];
+
+ XCTAssertEqualObjects(_RPCIssuer.requestURL.absoluteString, kExpectedAPIURL);
+ XCTAssertNotNil(_RPCIssuer.decodedRequest);
+ XCTAssertNotNil(_RPCIssuer.decodedRequest[kTestTokenKey]);
+ XCTAssertNil(_RPCIssuer.decodedRequest[kReturnSecureTokenKey]);
+}
+
+/** @fn testVerifyCustomTokenRequestOptionalFields
+ @brief Tests the verify custom token request with optional fields.
+ */
+- (void)testVerifyCustomTokenRequestOptionalFields {
+ FIRVerifyCustomTokenRequest *request =
+ [[FIRVerifyCustomTokenRequest alloc] initWithToken:kTestToken APIKey:kTestAPIKey];
+ [FIRAuthBackend verifyCustomToken:request
+ callback:^(FIRVerifyCustomTokenResponse *_Nullable response,
+ NSError *_Nullable error) {
+ }];
+
+ XCTAssertEqualObjects(_RPCIssuer.requestURL.absoluteString, kExpectedAPIURL);
+ XCTAssertNotNil(_RPCIssuer.decodedRequest);
+ XCTAssertNotNil(_RPCIssuer.decodedRequest[kTestTokenKey]);
+ XCTAssertTrue([_RPCIssuer.decodedRequest[kReturnSecureTokenKey] boolValue]);
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRVerifyCustomTokenResponseTests.m b/Example/Auth/Tests/FIRVerifyCustomTokenResponseTests.m
new file mode 100644
index 0000000..7a634ed
--- /dev/null
+++ b/Example/Auth/Tests/FIRVerifyCustomTokenResponseTests.m
@@ -0,0 +1,274 @@
+/*
+ * 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 "FIRAuthErrors.h"
+#import "FIRAuthBackend.h"
+#import "FIRVerifyCustomTokenRequest.h"
+#import "FIRVerifyCustomTokenResponse.h"
+#import "FIRFakeBackendRPCIssuer.h"
+
+/** @var kTestToken
+ @brief testing token.
+ */
+static NSString *const kTestToken = @"test token";
+
+/** @var kTestAPIKey
+ @brief Fake API key used for testing.
+ */
+static NSString *const kTestAPIKey = @"APIKey";
+
+/** @var kIDTokenKey
+ @brief The name of the "IDToken" property in the response.
+ */
+static NSString *const kIDTokenKey = @"idToken";
+
+/** @var kExpiresInKey
+ @brief The name of the "expiresIn" property in the response.
+ */
+static NSString *const kExpiresInKey = @"expiresIn";
+
+/** @var kRefreshTokenKey
+ @brief The name of the "refreshToken" property in the response.
+ */
+static NSString *const kRefreshTokenKey = @"refreshToken";
+
+/** @var kTestIDToken
+ @brief Testing ID token for verifying assertion.
+ */
+static NSString *const kTestIDToken = @"ID_TOKEN";
+
+/** @var kTestExpiresIn
+ @brief Fake token expiration time.
+ */
+static NSString *const kTestExpiresIn = @"12345";
+
+/** @var kTestRefreshToken
+ @brief Fake refresh token.
+ */
+static NSString *const kTestRefreshToken = @"REFRESH_TOKEN";
+
+/** @var kMissingTokenCustomErrorMessage
+ @brief This is the error message the server will respond with if token field is missing in
+ request.
+ */
+static NSString *const kMissingCustomTokenErrorMessage = @"MISSING_CUSTOM_TOKEN";
+
+/** @var kInvalidTokenCustomErrorMessage
+ @brief This is the error message the server will respond with if there is a validation error
+ with the custom token.
+ */
+static NSString *const kInvalidCustomTokenErrorMessage = @"INVALID_CUSTOM_TOKEN";
+
+/** @var kInvalidCustomTokenServerErrorMessage
+ @brief This is the error message the server will respond with if there is a validation error
+ with the custom token. This message contains error details from the server.
+ */
+static NSString *const kInvalidCustomTokenServerErrorMessage =
+ @"INVALID_CUSTOM_TOKEN : Detailed Error";
+
+/** @var kInvalidCustomTokenEmptyServerErrorMessage
+ @brief This is the error message the server will respond with if there is a validation error
+ with the custom token.
+ @remarks This message deliberately has no content where it should contain
+ error details.
+ */
+static NSString *const kInvalidCustomTokenEmptyServerErrorMessage =
+ @"INVALID_CUSTOM_TOKEN :";
+
+/** @var kInvalidCustomTokenErrorDetails
+ @brief This is the test detailed error message that could be returned by the backend.
+ */
+static NSString *const kInvalidCustomTokenErrorDetails = @"Detailed Error";
+
+/** @var kCredentialMismatchErrorMessage
+ @brief This is the error message the server will respond with if the service API key belongs to
+ different projects.
+ */
+static NSString *const kCredentialMismatchErrorMessage = @"CREDENTIAL_MISMATCH:";
+
+/** @var kEpsilon
+ @brief Allowed difference when comparing floating point numbers.
+ */
+static const double kEpsilon = 1e-3;
+
+@interface FIRVerifyCustomTokenResponseTests : XCTestCase
+@end
+@implementation FIRVerifyCustomTokenResponseTests {
+ /** @var _RPCIssuer
+ @brief This backend RPC issuer is used to fake network responses for each test in the suite.
+ In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it.
+ */
+ FIRFakeBackendRPCIssuer *_RPCIssuer;
+}
+
+- (void)setUp {
+ [super setUp];
+ FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init];
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer];
+ _RPCIssuer = RPCIssuer;
+}
+
+- (void)tearDown {
+ _RPCIssuer = nil;
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
+ [super tearDown];
+}
+
+/** @fn testInvalidCustomTokenError
+ @brief This test simulates @c invalidCustomTokenError with @c
+ FIRAuthErrorCodeINvalidCustomToken error code.
+ */
+- (void)testInvalidCustomTokenError {
+ FIRVerifyCustomTokenRequest *request =
+ [[FIRVerifyCustomTokenRequest alloc] initWithToken:kTestToken APIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRVerifyCustomTokenResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend verifyCustomToken:request
+ callback:^(FIRVerifyCustomTokenResponse*_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kInvalidCustomTokenErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNotNil(RPCError);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeInvalidCustomToken);
+}
+
+/** @fn testInvalidCustomTokenServerError
+ @brief This test simulates @c invalidCustomTokenError with @c
+ FIRAuthErrorCodeINvalidCustomToken error code, with a custom message from the server.
+ */
+- (void)testInvalidCustomTokenServerError {
+ FIRVerifyCustomTokenRequest *request =
+ [[FIRVerifyCustomTokenRequest alloc] initWithToken:kTestToken APIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRVerifyCustomTokenResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend verifyCustomToken:request
+ callback:^(FIRVerifyCustomTokenResponse*_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kInvalidCustomTokenServerErrorMessage];
+ NSString *errorDescription = [RPCError.userInfo valueForKey:NSLocalizedDescriptionKey];
+ XCTAssertTrue([errorDescription isEqualToString:kInvalidCustomTokenErrorDetails]);
+ XCTAssert(callbackInvoked);
+ XCTAssertNotNil(RPCError);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeInvalidCustomToken);
+}
+
+/** @fn testEmptyServerDetailMessage
+ @brief This test simulates @c invalidCustomTokenError with @c
+ FIRAuthErrorCodeINvalidCustomToken error code, with an empty custom message from the server.
+ @remarks An empty error message is not valid and therefore should not be added as an error
+ description.
+ */
+- (void)testEmptyServerDetailMessage {
+ FIRVerifyCustomTokenRequest *request =
+ [[FIRVerifyCustomTokenRequest alloc] initWithToken:kTestToken APIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRVerifyCustomTokenResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend verifyCustomToken:request
+ callback:^(FIRVerifyCustomTokenResponse*_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kInvalidCustomTokenEmptyServerErrorMessage];
+ NSString *errorDescription = [RPCError.userInfo valueForKey:NSLocalizedDescriptionKey];
+ XCTAssertFalse([errorDescription isEqualToString:@""]);
+ XCTAssert(callbackInvoked);
+ XCTAssertNotNil(RPCError);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeInvalidCustomToken);
+}
+
+/** @fn testInvalidCredentialMismatchError
+ @brief This test simulates @c credentialMistmatchTokenError with @c
+ FIRAuthErrorCodeCredetialMismatch error code.
+ */
+- (void)testInvalidCredentialMismatchError {
+ FIRVerifyCustomTokenRequest *request =
+ [[FIRVerifyCustomTokenRequest alloc] initWithToken:kTestToken APIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRVerifyCustomTokenResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend verifyCustomToken:request
+ callback:^(FIRVerifyCustomTokenResponse*_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kCredentialMismatchErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNotNil(RPCError);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeCustomTokenMismatch);
+}
+
+/** @fn testSuccessfulVerifyCustomTokenResponse
+ @brief This test simulates a successful @c VerifyCustomToken flow.
+ */
+- (void)testSuccessfulVerifyCustomTokenResponse {
+ FIRVerifyCustomTokenRequest *request =
+ [[FIRVerifyCustomTokenRequest alloc] initWithToken:kTestToken APIKey:kTestAPIKey];
+
+ __block BOOL callbackInvoked;
+ __block FIRVerifyCustomTokenResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend verifyCustomToken:request
+ callback:^(FIRVerifyCustomTokenResponse*_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithJSON:@{
+ kIDTokenKey : kTestIDToken,
+ kExpiresInKey : kTestExpiresIn,
+ kRefreshTokenKey : kTestRefreshToken,
+ }];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCError);
+ XCTAssertNotNil(RPCResponse);
+ XCTAssertEqualObjects(RPCResponse.IDToken, kTestIDToken);
+ NSTimeInterval expiresIn = [RPCResponse.approximateExpirationDate timeIntervalSinceNow];
+ XCTAssertLessThanOrEqual(fabs(expiresIn - [kTestExpiresIn doubleValue]), kEpsilon);
+ XCTAssertEqualObjects(RPCResponse.refreshToken, kTestRefreshToken);
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRVerifyPasswordRequestTest.m b/Example/Auth/Tests/FIRVerifyPasswordRequestTest.m
new file mode 100644
index 0000000..f07afdc
--- /dev/null
+++ b/Example/Auth/Tests/FIRVerifyPasswordRequestTest.m
@@ -0,0 +1,163 @@
+/*
+ * 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 "FIRAuthErrors.h"
+#import "FIRAuthBackend.h"
+#import "FIRVerifyPasswordRequest.h"
+#import "FIRVerifyPasswordResponse.h"
+#import "FIRFakeBackendRPCIssuer.h"
+
+/** @var kTestAPIKey
+ @brief Fake API key used for testing.
+ */
+static NSString *const kTestAPIKey = @"APIKey";
+
+/** @var kEmailKey
+ @brief The key for the "email" value in the request.
+ */
+static NSString *const kEmailKey = @"email";
+
+/** @var kPasswordKey
+ @brief The key for the "password" value in the request.
+ */
+static NSString *const kPasswordKey = @"password";
+
+/** @var kTestEmail
+ @brief Fake email address for testing the request.
+ */
+static NSString *const kTestEmail = @"testEmail.";
+
+/** @var kTestPassword
+ @brief Fake password for testing the request.
+ */
+static NSString *const kTestPassword = @"testPassword";
+
+/** @var kPendingIDTokenKey
+ @brief The key for the "pendingIdToken" value in the request.
+ */
+static NSString *const kPendingIDTokenKey = @"pendingIdToken";
+
+/** @var kTestPendingToken
+ @brief Fake pendingToken for testing the request.
+ */
+static NSString *const kTestPendingToken = @"testPendingToken";
+
+/** @var kCaptchaChallengeKey
+ @brief The key for the "captchaChallenge" value in the request.
+ */
+static NSString *const kCaptchaChallengeKey = @"captchaChallenge";
+
+/** @var kTestCaptchaChallenge
+ @brief Fake captchaChallenge for testing the request.
+ */
+static NSString *const kTestCaptchaChallenge = @"testCaptchaChallenge";
+
+/** @var kCaptchaResponseKey
+ @brief The key for the "captchaResponse" value in the request.
+ */
+static NSString *const kCaptchaResponseKey = @"captchaResponse";
+
+/** @var kTestCaptchaResponse
+ @brief Fake captchaResponse for testing the request.
+ */
+static NSString *const kTestCaptchaResponse = @"captchaResponse";
+
+/** @var kReturnSecureTokenKey
+ @brief The key for the "returnSecureToken" value in the request.
+ */
+static NSString *const kReturnSecureTokenKey = @"returnSecureToken";
+
+/** @var kExpectedAPIURL
+ @brief The expected URL for test calls.
+ */
+static NSString *const kExpectedAPIURL =
+ @"https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=APIKey";
+
+/** @class FIRVerifyPasswordRequestTest
+ @brief Tests for @c FIRVerifyPasswordRequestTest.
+ */
+@interface FIRVerifyPasswordRequestTest : XCTestCase
+@end
+@implementation FIRVerifyPasswordRequestTest {
+ /** @var _RPCIssuer
+ @brief This backend RPC issuer is used to fake network responses for each test in the suite.
+ In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it.
+ */
+ FIRFakeBackendRPCIssuer *_RPCIssuer;
+}
+
+- (void)setUp {
+ [super setUp];
+ FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init];
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer];
+ _RPCIssuer = RPCIssuer;
+}
+
+- (void)tearDown {
+ _RPCIssuer = nil;
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
+ [super tearDown];
+}
+
+/** @fn testVerifyPasswordRequest
+ @brief Tests the verify password request.
+ */
+- (void)testVerifyPasswordRequest {
+ FIRVerifyPasswordRequest * request = [[FIRVerifyPasswordRequest alloc] initWithEmail:kTestEmail
+ password:kTestPassword
+ APIKey:kTestAPIKey];
+ request.returnSecureToken = NO;
+ [FIRAuthBackend verifyPassword:request
+ callback:^(FIRVerifyPasswordResponse *_Nullable response,
+ NSError *_Nullable error) {
+ }];
+
+ XCTAssertEqualObjects(_RPCIssuer.requestURL.absoluteString, kExpectedAPIURL);
+ XCTAssertNotNil(_RPCIssuer.decodedRequest);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kEmailKey], kTestEmail);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kPasswordKey], kTestPassword);
+ XCTAssertNil(_RPCIssuer.decodedRequest[kCaptchaChallengeKey]);
+ XCTAssertNil(_RPCIssuer.decodedRequest[kCaptchaResponseKey]);
+ XCTAssertNil(_RPCIssuer.decodedRequest[kReturnSecureTokenKey]);
+}
+
+/** @fn testVerifyPasswordRequestOptionalFields
+ @brief Tests the verify password request with optional fields.
+ */
+- (void)testVerifyPasswordRequestOptionalFields {
+ FIRVerifyPasswordRequest * request = [[FIRVerifyPasswordRequest alloc] initWithEmail:kTestEmail
+ password:kTestPassword
+ APIKey:kTestAPIKey];
+ request.pendingIDToken = kTestPendingToken;
+ request.captchaChallenge = kTestCaptchaChallenge;
+ request.captchaResponse = kTestCaptchaResponse;
+ [FIRAuthBackend verifyPassword:request
+ callback:^(FIRVerifyPasswordResponse *_Nullable response,
+ NSError *_Nullable error) {
+ }];
+
+ XCTAssertEqualObjects(_RPCIssuer.requestURL.absoluteString, kExpectedAPIURL);
+ XCTAssertNotNil(_RPCIssuer.decodedRequest);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kEmailKey], kTestEmail);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kPasswordKey], kTestPassword);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kCaptchaChallengeKey], kTestCaptchaChallenge);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kCaptchaResponseKey], kTestCaptchaResponse);
+ XCTAssertTrue([_RPCIssuer.decodedRequest[kReturnSecureTokenKey] boolValue]);
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRVerifyPasswordResponseTests.m b/Example/Auth/Tests/FIRVerifyPasswordResponseTests.m
new file mode 100644
index 0000000..949969b
--- /dev/null
+++ b/Example/Auth/Tests/FIRVerifyPasswordResponseTests.m
@@ -0,0 +1,454 @@
+/*
+ * 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 "FIRAuthErrors.h"
+#import "FIRAuthBackend.h"
+#import "FIRVerifyPasswordRequest.h"
+#import "FIRVerifyPasswordResponse.h"
+#import "FIRFakeBackendRPCIssuer.h"
+
+/** @var kTestPassword
+ @brief Testing user password.
+ */
+static NSString *const kTestPassword = @"testpassword";
+
+/** @var kTestAPIKey
+ @brief Fake API key used for testing.
+ */
+static NSString *const kTestAPIKey = @"_test_API_key_";
+
+/** @var kLocalIDKey
+ @brief The name of the 'localID' property in the response.
+ */
+static NSString *const kLocalIDKey = @"localId";
+
+/** @var kTestLocalID
+ @brief The fake localID for testing the response.
+ */
+static NSString *const kTestLocalID = @"testLocalId";
+
+/** @var kEmailKey
+ @brief The name of the 'email' property in the response.
+ */
+static NSString *const kEmailKey = @"email";
+
+/** @var kTestEmail
+ @brief Fake user email for testing the response.
+ */
+static NSString *const kTestEmail = @"test@gmail.com";
+
+/** @var kDisplayNameKey
+ @brief The name of the 'displayName' property in the response.
+ */
+static NSString *const kDisplayNameKey = @"displayName";
+
+/** @var kTestDisplayName
+ @brief Fake displayName for testing the response.
+ */
+static NSString *const kTestDisplayName = @"testDisplayName";
+
+/** @var kIDTokenKey
+ @brief The name of the "IDToken" property in the response.
+ */
+static NSString *const kIDTokenKey = @"idToken";
+
+/** @var kTestIDToken
+ @brief Testing ID token for verifying assertion.
+ */
+static NSString *const kTestIDToken = @"ID_TOKEN";
+
+/** @var kExpiresInKey
+ @brief The name of the "expiresIn" property in the response.
+ */
+static NSString *const kExpiresInKey = @"expiresIn";
+
+/** @var kTestExpiresIn
+ @brief Fake token expiration time.
+ */
+static NSString *const kTestExpiresIn = @"12345";
+
+/** @var kRefreshTokenKey
+ @brief The name of the "refreshToken" property in the response.
+ */
+static NSString *const kRefreshTokenKey = @"refreshToken";
+
+/** @var kTestRefreshToken
+ @brief Fake refresh token.
+ */
+static NSString *const kTestRefreshToken = @"REFRESH_TOKEN";
+
+/** @var kOperationNotAllowedErrorMessage
+ @brief This is the error message the server will respond with if Admin disables IDP specified by
+ provider.
+ */
+static NSString *const kOperationNotAllowedErrorMessage = @"OPERATION_NOT_ALLOWED";
+
+/** @var kPasswordLoginDisabledErrorMessage
+ @brief This is the error message the server responds with if password login is disabled.
+ */
+static NSString *const kPasswordLoginDisabledErrorMessage = @"PASSWORD_LOGIN_DISABLED";
+
+/** @var kPhotoUrlKey
+ @brief The name of the 'photoUrl' property in the response.
+ */
+static NSString *const kPhotoUrlKey = @"photoUrl";
+
+/** @var kTestPhotoUrl
+ @brief Fake photoUrl for testing the response.
+ */
+static NSString *const kTestPhotoUrl = @"www.example.com";
+
+/** @var kUserDisabledErrorMessage
+ @brief This is the error message the server will respond with if the user's account has been
+ disabled.
+ */
+static NSString *const kUserDisabledErrorMessage = @"USER_DISABLED";
+
+/** @var kEmailNotFoundErrorMessage
+ @brief This is the error message the server will respond with if the email entered is not
+ found.
+ */
+static NSString *const kEmailNotFoundErrorMessage = @"EMAIL_NOT_FOUND";
+
+/** @var kWrongPasswordErrorMessage
+ @brief This is the error message the server will respond with if the user entered a wrong
+ password.
+ */
+static NSString *const kWrongPasswordErrorMessage = @"INVALID_PASSWORD";
+
+/** @var kInvalidEmailErrorMessage
+ @brief The error returned by the server if the email is invalid.
+ */
+static NSString *const kInvalidEmailErrorMessage = @"INVALID_EMAIL";
+
+/** @var kBadRequestErrorMessage
+ @brief This is the error message returned when a bad request is made; often due to a bad API
+ Key.
+ */
+static NSString *const kBadRequestErrorMessage = @"Bad Request";
+
+/** @var kInvalidKeyReasonValue
+ @brief The value for the "reason" key indicating an invalid API Key was received by the server.
+ */
+static NSString *const kInvalidKeyReasonValue = @"keyInvalid";
+
+/** @var kAppNotAuthorizedReasonValue
+ @brief The value for the "reason" key indicating the App is not authorized to use Firebase
+ Authentication.
+ */
+static NSString *const kAppNotAuthorizedReasonValue = @"ipRefererBlocked";
+
+/** @var kTooManyAttemptsErrorMessage
+ @brief This is the error message the server will respond with if a user has tried (and failed)
+ to sign in too many times.
+ */
+static NSString *const kTooManyAttemptsErrorMessage = @"TOO_MANY_ATTEMPTS_TRY_LATER:";
+
+/** @var kEpsilon
+ @brief Allowed difference when comparing floating point numbers.
+ */
+static const double kEpsilon = 1e-3;
+
+/** @class FIRVerifyPasswordResponseTests
+ @brief Tests for @c FIRVerifyPasswordResponse.
+ */
+@interface FIRVerifyPasswordResponseTests : XCTestCase
+@end
+@implementation FIRVerifyPasswordResponseTests {
+ /** @var _RPCIssuer
+ @brief This backend RPC issuer is used to fake network responses for each test in the suite.
+ In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it.
+ */
+ FIRFakeBackendRPCIssuer *_RPCIssuer;
+}
+
+- (void)setUp {
+ FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init];
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer];
+ _RPCIssuer = RPCIssuer;
+}
+
+/** @fn testUserDisabledError
+ @brief Tests that @c FIRAuthErrorCodeUserDisabled error is received if the email is disabled.
+ */
+- (void)testUserDisabledError {
+ FIRVerifyPasswordRequest *request = [[FIRVerifyPasswordRequest alloc] initWithEmail:kTestEmail
+ password:kTestPassword
+ APIKey:kTestAPIKey];
+ __block BOOL callbackInvoked;
+ __block FIRVerifyPasswordResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend verifyPassword:request
+ callback:^(FIRVerifyPasswordResponse *_Nullable response,
+ NSError *_Nullable error) {
+ RPCResponse = response;
+ RPCError = error;
+ callbackInvoked = YES;
+ }];
+ [_RPCIssuer respondWithServerErrorMessage:kUserDisabledErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeUserDisabled);
+}
+
+/** @fn testEmailNotFoundError
+ @brief Tests that @c FIRAuthErrorCodeEmailNotFound error is received if the email is not found.
+ */
+- (void)testEmailNotFoundError {
+ FIRVerifyPasswordRequest *request = [[FIRVerifyPasswordRequest alloc] initWithEmail:kTestEmail
+ password:kTestPassword
+ APIKey:kTestAPIKey];
+ __block BOOL callbackInvoked;
+ __block FIRVerifyPasswordResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend verifyPassword:request
+ callback:^(FIRVerifyPasswordResponse *_Nullable response,
+ NSError *_Nullable error) {
+ RPCResponse = response;
+ RPCError = error;
+ callbackInvoked = YES;
+ }];
+ [_RPCIssuer respondWithServerErrorMessage:kEmailNotFoundErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeUserNotFound);
+}
+
+/** @fn testInvalidPasswordError
+ @brief Tests that @c FIRAuthErrorCodeInvalidPassword error is received if the password is
+ invalid.
+ */
+- (void)testInvalidPasswordError {
+ FIRVerifyPasswordRequest *request = [[FIRVerifyPasswordRequest alloc] initWithEmail:kTestEmail
+ password:kTestPassword
+ APIKey:kTestAPIKey];
+ __block BOOL callbackInvoked;
+ __block FIRVerifyPasswordResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend verifyPassword:request
+ callback:^(FIRVerifyPasswordResponse *_Nullable response,
+ NSError *_Nullable error) {
+ RPCResponse = response;
+ RPCError = error;
+ callbackInvoked = YES;
+ }];
+ [_RPCIssuer respondWithServerErrorMessage:kWrongPasswordErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeWrongPassword);
+}
+
+/** @fn testInvalidEmailError
+ @brief Tests that @c FIRAuthErrorCodeInvalidEmail error is received if the email address has an
+ incorrect format.
+ */
+- (void)testInvalidEmailError {
+ FIRVerifyPasswordRequest *request = [[FIRVerifyPasswordRequest alloc] initWithEmail:kTestEmail
+ password:kTestPassword
+ APIKey:kTestAPIKey];
+ __block BOOL callbackInvoked;
+ __block FIRVerifyPasswordResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend verifyPassword:request
+ callback:^(FIRVerifyPasswordResponse *_Nullable response,
+ NSError *_Nullable error) {
+ RPCResponse = response;
+ RPCError = error;
+ callbackInvoked = YES;
+ }];
+ [_RPCIssuer respondWithServerErrorMessage:kInvalidEmailErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeInvalidEmail);
+}
+
+/** @fn testTooManyAttemptsError
+ @brief Tests that @c FIRAuthErrorCodeTooManyRequests error is received if too many sign-in
+ attempts were made.
+ */
+- (void)testTooManySignInAttemptsError {
+ FIRVerifyPasswordRequest *request = [[FIRVerifyPasswordRequest alloc] initWithEmail:kTestEmail
+ password:kTestPassword
+ APIKey:kTestAPIKey];
+ __block BOOL callbackInvoked;
+ __block FIRVerifyPasswordResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend verifyPassword:request
+ callback:^(FIRVerifyPasswordResponse *_Nullable response,
+ NSError *_Nullable error) {
+ RPCResponse = response;
+ RPCError = error;
+ callbackInvoked = YES;
+ }];
+ [_RPCIssuer respondWithServerErrorMessage:kTooManyAttemptsErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeTooManyRequests);
+}
+
+/** @fn testKeyInvalid
+ @brief Tests that @c FIRAuthErrorCodeInvalidApiKey error is received from the server.
+ */
+- (void)testKeyInvalid {
+ FIRVerifyPasswordRequest *request = [[FIRVerifyPasswordRequest alloc] initWithEmail:kTestEmail
+ password:kTestPassword
+ APIKey:kTestAPIKey];
+ __block BOOL callbackInvoked;
+ __block FIRVerifyPasswordResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend verifyPassword:request
+ callback:^(FIRVerifyPasswordResponse *_Nullable response,
+ NSError *_Nullable error) {
+ RPCResponse = response;
+ RPCError = error;
+ callbackInvoked = YES;
+ }];
+
+ NSDictionary *errorDictionary = @{
+ @"error" : @{
+ @"message" : kBadRequestErrorMessage,
+ @"errors" : @[ @{ @"reason" : kInvalidKeyReasonValue } ]
+ }
+ };
+ [_RPCIssuer respondWithJSONError:errorDictionary];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeInvalidAPIKey);
+}
+
+/** @fn testOperationNotAllowedError
+ @brief This test simulates a @c FIRAuthErrorCodeOperationNotAllowed error.
+ */
+- (void)testOperationNotAllowedError {
+ FIRVerifyPasswordRequest *request = [[FIRVerifyPasswordRequest alloc] initWithEmail:kTestEmail
+ password:kTestPassword
+ APIKey:kTestAPIKey];
+ __block BOOL callbackInvoked;
+ __block FIRVerifyPasswordResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend verifyPassword:request
+ callback:^(FIRVerifyPasswordResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kOperationNotAllowedErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNotNil(RPCError);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeOperationNotAllowed);
+}
+
+/** @fn testPasswordLoginDisabledError
+ @brief This test simulates a @c FIRAuthErrorCodeOperationNotAllowed error.
+ */
+- (void)testPasswordLoginDisabledError {
+ FIRVerifyPasswordRequest *request = [[FIRVerifyPasswordRequest alloc] initWithEmail:kTestEmail
+ password:kTestPassword
+ APIKey:kTestAPIKey];
+ __block BOOL callbackInvoked;
+ __block FIRVerifyPasswordResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend verifyPassword:request
+ callback:^(FIRVerifyPasswordResponse *_Nullable response,
+ NSError *_Nullable error) {
+ callbackInvoked = YES;
+ RPCResponse = response;
+ RPCError = error;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kPasswordLoginDisabledErrorMessage];
+ XCTAssert(callbackInvoked);
+ XCTAssertNotNil(RPCError);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeOperationNotAllowed);
+}
+
+/** @fn testAppNotAuthorized
+ @brief Tests that @c FIRAuthErrorCodeAppNotAuthorized error is received from the server.
+ */
+- (void)testAppNotAuthorized {
+ FIRVerifyPasswordRequest *request = [[FIRVerifyPasswordRequest alloc] initWithEmail:kTestEmail
+ password:kTestPassword
+ APIKey:kTestAPIKey];
+ __block BOOL callbackInvoked;
+ __block FIRVerifyPasswordResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend verifyPassword:request
+ callback:^(FIRVerifyPasswordResponse *_Nullable response,
+ NSError *_Nullable error) {
+ RPCResponse = response;
+ RPCError = error;
+ callbackInvoked = YES;
+ }];
+
+ NSDictionary *errorDictionary = @{
+ @"error" : @{
+ @"message" : kBadRequestErrorMessage,
+ @"errors" : @[ @{ @"reason" : kAppNotAuthorizedReasonValue } ]
+ }
+ };
+ [_RPCIssuer respondWithJSONError:errorDictionary];
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeAppNotAuthorized);
+}
+
+/** @fn testSuccessfulVerifyPasswordResponse
+ @brief Tests a succesful attempt of the verify password flow.
+ */
+- (void)testSuccessfulVerifyPasswordResponse {
+ FIRVerifyPasswordRequest *request = [[FIRVerifyPasswordRequest alloc] initWithEmail:kTestEmail
+ password:kTestPassword
+ APIKey:kTestAPIKey];
+ __block BOOL callbackInvoked;
+ __block FIRVerifyPasswordResponse *RPCResponse;
+ __block NSError *RPCError;
+ [FIRAuthBackend verifyPassword:request
+ callback:^(FIRVerifyPasswordResponse *_Nullable response,
+ NSError *_Nullable error) {
+ RPCResponse = response;
+ RPCError = error;
+ callbackInvoked = YES;
+ }];
+
+ [_RPCIssuer respondWithJSON:@{
+ kLocalIDKey : kTestLocalID,
+ kEmailKey : kTestEmail,
+ kDisplayNameKey : kTestDisplayName,
+ kIDTokenKey : kTestIDToken,
+ kExpiresInKey : kTestExpiresIn,
+ kRefreshTokenKey : kTestRefreshToken,
+ kPhotoUrlKey : kTestPhotoUrl
+ }];
+
+ XCTAssert(callbackInvoked);
+ XCTAssertNotNil(RPCResponse);
+ XCTAssertEqualObjects(RPCResponse.email, kTestEmail);
+ XCTAssertEqualObjects(RPCResponse.localID, kTestLocalID);
+ XCTAssertEqualObjects(RPCResponse.displayName, kTestDisplayName);
+ XCTAssertEqualObjects(RPCResponse.IDToken, kTestIDToken);
+ NSTimeInterval expiresIn = [RPCResponse.approximateExpirationDate timeIntervalSinceNow];
+ XCTAssertLessThanOrEqual(fabs(expiresIn - [kTestExpiresIn doubleValue]), kEpsilon);
+ XCTAssertEqualObjects(RPCResponse.refreshToken, kTestRefreshToken);
+ XCTAssertEqualObjects(RPCResponse.photoURL.absoluteString, kTestPhotoUrl );
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRVerifyPhoneNumberRequestTests.m b/Example/Auth/Tests/FIRVerifyPhoneNumberRequestTests.m
new file mode 100644
index 0000000..f51d102
--- /dev/null
+++ b/Example/Auth/Tests/FIRVerifyPhoneNumberRequestTests.m
@@ -0,0 +1,154 @@
+/*
+ * 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 "FIRAuthBackend.h"
+#import "FIRVerifyPhoneNumberRequest.h"
+#import "FIRVerifyPhoneNumberResponse.h"
+#import "FIRFakeBackendRPCIssuer.h"
+
+/** @var kTestAPIKey
+ @brief Fake API key used for testing.
+ */
+static NSString *const kTestAPIKey = @"APIKey";
+
+/** @var kVerificationCode
+ @brief Fake verification code used for testing.
+ */
+static NSString *const kVerificationCode = @"12345678";
+
+/** @var kVerificationID
+ @brief Fake verification ID for testing.
+ */
+static NSString *const kVerificationID = @"55432";
+
+/** @var kPhoneNumber
+ @brief The fake user phone number.
+ */
+static NSString *const kPhoneNumber = @"12345658";
+
+/** @var kTemporaryProof
+ @brief The fake temporary proof.
+ */
+static NSString *const kTemporaryProof = @"12345658";
+
+/** @var kVerificationCodeKey
+ @brief The key for the verification code" value in the request.
+ */
+static NSString *const kVerificationCodeKey = @"code";
+
+/** @var kVerificationIDKey
+ @brief The key for the verification ID" value in the request.
+ */
+static NSString *const kVerificationIDKey = @"sessionInfo";
+
+/** @var kIDTokenKey
+ @brief The key for the "ID Token" value in the request.
+ */
+static NSString *const kIDTokenKey = @"idToken";
+
+/** @var kTestAccessToken
+ @bried Fake acess token for testing.
+ */
+ static NSString *const kTestAccessToken = @"accessToken";
+
+ /** @var kTemporaryProofKey
+ @brief The key for the temporary proof value in the request.
+ */
+static NSString *const kTemporaryProofKey = @"temporaryProof";
+
+/** @var kPhoneNumberKey
+ @brief The key for the phone number value in the request.
+ */
+static NSString *const kPhoneNumberKey = @"phoneNumber";
+
+/** @var kExpectedAPIURL
+ @brief The expected URL for the test calls.
+ */
+static NSString *const kExpectedAPIURL =
+ @"https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPhoneNumber?key=APIKey";
+
+/** @class FIRVerifyPhoneNumberRequestTests
+ @brief Tests for @c FIRVerifyPhoneNumberRequest.
+ */
+@interface FIRVerifyPhoneNumberRequestTests : XCTestCase
+@end
+
+@implementation FIRVerifyPhoneNumberRequestTests {
+ /** @var _RPCIssuer
+ @brief This backend RPC issuer is used to fake network responses for each test in the suite.
+ In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it.
+ */
+ FIRFakeBackendRPCIssuer *_RPCIssuer;
+}
+
+- (void)setUp {
+ [super setUp];
+ FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init];
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer];
+ _RPCIssuer = RPCIssuer;
+}
+
+- (void)tearDown {
+ _RPCIssuer = nil;
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
+ [super tearDown];
+}
+
+/** @fn testVerifyPhoneNumberRequest
+ @brief Tests the verifyPhoneNumber request.
+ */
+- (void)testVerifyPhoneNumberRequest {
+ FIRVerifyPhoneNumberRequest *request =
+ [[FIRVerifyPhoneNumberRequest alloc] initWithVerificationID:kVerificationID
+ verificationCode:kVerificationCode
+ APIKey:kTestAPIKey];
+ request.accessToken = kTestAccessToken;
+ [FIRAuthBackend verifyPhoneNumber:request
+ callback:^(FIRVerifyPhoneNumberResponse *_Nullable response,
+ NSError *_Nullable error) {
+ }];
+
+ XCTAssertEqualObjects(_RPCIssuer.requestURL.absoluteString, kExpectedAPIURL);
+ XCTAssertNotNil(_RPCIssuer.decodedRequest);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kVerificationIDKey], kVerificationID);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kVerificationCodeKey], kVerificationCode);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kIDTokenKey], kTestAccessToken);
+}
+
+/** @fn testVerifyPhoneNumberRequestWithTemporaryProof
+ @brief Tests the verifyPhoneNumber request when created using a temporary proof.
+ */
+- (void)testVerifyPhoneNumberRequestWithTemporaryProof {
+ FIRVerifyPhoneNumberRequest *request =
+ [[FIRVerifyPhoneNumberRequest alloc] initWithTemporaryProof:kTemporaryProof
+ phoneNumber:kPhoneNumber
+ APIKey:kTestAPIKey];
+ request.accessToken = kTestAccessToken;
+ [FIRAuthBackend verifyPhoneNumber:request
+ callback:^(FIRVerifyPhoneNumberResponse *_Nullable response,
+ NSError *_Nullable error) {
+ }];
+
+ XCTAssertEqualObjects(_RPCIssuer.requestURL.absoluteString, kExpectedAPIURL);
+ XCTAssertNotNil(_RPCIssuer.decodedRequest);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kTemporaryProofKey], kTemporaryProof);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kPhoneNumberKey], kPhoneNumber);
+ XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kIDTokenKey], kTestAccessToken);
+}
+
+@end
diff --git a/Example/Auth/Tests/FIRVerifyPhoneNumberResponseTests.m b/Example/Auth/Tests/FIRVerifyPhoneNumberResponseTests.m
new file mode 100644
index 0000000..c647c3d
--- /dev/null
+++ b/Example/Auth/Tests/FIRVerifyPhoneNumberResponseTests.m
@@ -0,0 +1,271 @@
+/*
+ * 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 "Phone/FIRPhoneAuthCredential_Internal.h"
+#import "FIRAuthBackend.h"
+#import "FIRAuthErrors.h"
+#import "FIRVerifyPhoneNumberRequest.h"
+#import "FIRVerifyPhoneNumberResponse.h"
+#import "FIRFakeBackendRPCIssuer.h"
+
+/** @var kTestAPIKey
+ @brief Fake API key used for testing.
+ */
+static NSString *const kTestAPIKey = @"APIKey";
+
+/** @var kVerificationCode
+ @brief Fake verification code used for testing.
+ */
+static NSString *const kVerificationCode = @"12345678";
+
+/** @var kVerificationID
+ @brief Fake verification ID for testing.
+ */
+static NSString *const kVerificationID = @"55432";
+
+/** @var kfakeRefreshToken
+ @brief Fake refresh token for testing.
+ */
+static NSString *const kfakeRefreshToken = @"refreshtoken";
+
+/** @var klocalID
+ @brief Fake local ID for testing.
+ */
+static NSString *const klocalID = @"localID";
+
+/** @var kfakeIDToken
+ @brief Fake ID Token for testing.
+ */
+static NSString *const kfakeIDToken = @"idtoken";
+
+/** @var kTestExpiresIn
+ @brief Fake token expiration time.
+ */
+static NSString *const kTestExpiresIn = @"12345";
+
+/** @var kInvalidVerificationCodeErrorMessage
+ @brief This is the error message the server will respond with if an invalid verification code
+ provided.
+ */
+static NSString *const kInvalidVerificationCodeErrorMessage = @"INVALID_CODE";
+
+/** @var kInvalidSessionInfoErrorMessage
+ @brief This is the error message the server will respond with if an invalid verification ID
+ provided.
+ */
+static NSString *const kInvalidSessionInfoErrorMessage = @"INVALID_SESSION_INFO";
+
+/** @var kSessionExpiredErrorMessage
+ @brief This is the error message the server will respond with if the SMS code has expired before
+ it is used.
+ */
+static NSString *const kSessionExpiredErrorMessage = @"SESSION_EXPIRED";
+
+/** @var kFakePhoneNumber
+ @brief The fake user phone number.
+ */
+static NSString *const kFakePhoneNumber = @"12345658";
+
+/** @var kFakeTemporaryProof
+ @brief The fake temporary proof.
+ */
+static NSString *const kFakeTemporaryProof = @"12345658";
+
+/** @var kEpsilon
+ @brief Allowed difference when comparing floating point numbers.
+ */
+static const double kEpsilon = 1e-3;
+
+/** @class FIRVerifyPhoneNumberResponseTests
+ @brief Tests for @c FIRVerifyPhoneNumberResponse.
+ */
+@interface FIRVerifyPhoneNumberResponseTests : XCTestCase
+
+@end
+
+@implementation FIRVerifyPhoneNumberResponseTests {
+ /** @var _RPCIssuer
+ @brief This backend RPC issuer is used to fake network responses for each test in the suite.
+ In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it.
+ */
+ FIRFakeBackendRPCIssuer *_RPCIssuer;
+}
+
+- (void)setUp {
+ FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init];
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer];
+ _RPCIssuer = RPCIssuer;
+}
+
+- (void)tearDown {
+ _RPCIssuer = nil;
+ [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil];
+ [super tearDown];
+}
+
+/** @fn testInvalidVerificationCodeError
+ @brief Tests invalid verification code error.
+ */
+- (void)testInvalidVerificationCodeError {
+ FIRVerifyPhoneNumberRequest *request =
+ [[FIRVerifyPhoneNumberRequest alloc] initWithVerificationID:kVerificationID
+ verificationCode:kVerificationCode
+ APIKey:kTestAPIKey];
+ __block BOOL callbackInvoked;
+ __block FIRVerifyPhoneNumberResponse *RPCResponse;
+ __block NSError *RPCError;
+
+
+ [FIRAuthBackend verifyPhoneNumber:request
+ callback:^(FIRVerifyPhoneNumberResponse *_Nullable response,
+ NSError *_Nullable error) {
+ RPCResponse = response;
+ RPCError = error;
+ callbackInvoked = YES;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kInvalidVerificationCodeErrorMessage];
+
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeInvalidVerificationCode);
+}
+
+/** @fn testInvalidVerificationIDError
+ @brief Tests invalid verification code error.
+ */
+- (void)testInvalidVerificationIDError {
+ FIRVerifyPhoneNumberRequest *request =
+ [[FIRVerifyPhoneNumberRequest alloc] initWithVerificationID:kVerificationID
+ verificationCode:kVerificationCode
+ APIKey:kTestAPIKey];
+ __block BOOL callbackInvoked;
+ __block FIRVerifyPhoneNumberResponse *RPCResponse;
+ __block NSError *RPCError;
+
+ [FIRAuthBackend verifyPhoneNumber:request
+ callback:^(FIRVerifyPhoneNumberResponse *_Nullable response,
+ NSError *_Nullable error) {
+ RPCResponse = response;
+ RPCError = error;
+ callbackInvoked = YES;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kInvalidSessionInfoErrorMessage];
+
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeInvalidVerificationID);
+}
+
+/** @fn testSessionExpiredError
+ @brief Tests session expired error code.
+ */
+- (void)testSessionExpiredError {
+ FIRVerifyPhoneNumberRequest *request =
+ [[FIRVerifyPhoneNumberRequest alloc] initWithVerificationID:kVerificationID
+ verificationCode:kVerificationCode
+ APIKey:kTestAPIKey];
+ __block BOOL callbackInvoked;
+ __block FIRVerifyPhoneNumberResponse *RPCResponse;
+ __block NSError *RPCError;
+
+ [FIRAuthBackend verifyPhoneNumber:request
+ callback:^(FIRVerifyPhoneNumberResponse *_Nullable response,
+ NSError *_Nullable error) {
+ RPCResponse = response;
+ RPCError = error;
+ callbackInvoked = YES;
+ }];
+
+ [_RPCIssuer respondWithServerErrorMessage:kSessionExpiredErrorMessage];
+
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ XCTAssertEqual(RPCError.code, FIRAuthErrorCodeSessionExpired);
+}
+
+/** @fn testSuccessfulVerifyPhoneNumberResponse
+ @brief Tests a succesful to verify phone number flow.
+ */
+- (void)testSuccessfulVerifyPhoneNumberResponse {
+ FIRVerifyPhoneNumberRequest *request =
+ [[FIRVerifyPhoneNumberRequest alloc] initWithVerificationID:kVerificationID
+ verificationCode:kVerificationCode
+ APIKey:kTestAPIKey];
+ __block BOOL callbackInvoked;
+ __block FIRVerifyPhoneNumberResponse *RPCResponse;
+ __block NSError *RPCError;
+
+ [FIRAuthBackend verifyPhoneNumber:request
+ callback:^(FIRVerifyPhoneNumberResponse *_Nullable response,
+ NSError *_Nullable error) {
+ RPCResponse = response;
+ RPCError = error;
+ callbackInvoked = YES;
+ }];
+
+ [_RPCIssuer respondWithJSON:@{
+ @"idToken" : kfakeIDToken,
+ @"refreshToken" : kfakeRefreshToken,
+ @"localID" : klocalID,
+ @"expiresIn" : kTestExpiresIn,
+ @"isNewUser" : @YES // Set new user flag to true.
+ }];
+
+ XCTAssert(callbackInvoked);
+ XCTAssertNotNil(RPCResponse);
+ XCTAssertEqualObjects(RPCResponse.IDToken, kfakeIDToken);
+ XCTAssertEqualObjects(RPCResponse.refreshToken, kfakeRefreshToken);
+ NSTimeInterval expiresIn = [RPCResponse.approximateExpirationDate timeIntervalSinceNow];
+ XCTAssertLessThanOrEqual(fabs(expiresIn - [kTestExpiresIn doubleValue]), kEpsilon);
+ XCTAssertTrue(RPCResponse.isNewUser);
+}
+
+/** @fn testSuccessfulVerifyPhoneNumberResponseWithTemporaryProof
+ @brief Tests a succesful to verify phone number flow with temporary proof response.
+ */
+- (void)testSuccessfulVerifyPhoneNumberResponseWithTemporaryProof {
+ FIRVerifyPhoneNumberRequest *request =
+ [[FIRVerifyPhoneNumberRequest alloc] initWithTemporaryProof:kFakeTemporaryProof
+ phoneNumber:kFakePhoneNumber
+ APIKey:kTestAPIKey];
+ __block BOOL callbackInvoked;
+ __block FIRVerifyPhoneNumberResponse *RPCResponse;
+ __block NSError *RPCError;
+
+ [FIRAuthBackend verifyPhoneNumber:request
+ callback:^(FIRVerifyPhoneNumberResponse *_Nullable response,
+ NSError *_Nullable error) {
+ RPCResponse = response;
+ RPCError = error;
+ callbackInvoked = YES;
+ }];
+
+ [_RPCIssuer respondWithJSON:@{
+ @"temporaryProof" : kFakeTemporaryProof,
+ @"phoneNumber" : kFakePhoneNumber
+ }];
+
+ XCTAssert(callbackInvoked);
+ XCTAssertNil(RPCResponse);
+ FIRPhoneAuthCredential *credential = RPCError.userInfo[FIRAuthUpdatedCredentialKey];
+ XCTAssertEqualObjects(credential.temporaryProof, kFakeTemporaryProof);
+ XCTAssertEqualObjects(credential.phoneNumber, kFakePhoneNumber);
+}
+
+@end
diff --git a/Example/Auth/Tests/OCMStubRecorder+FIRAuthUnitTests.h b/Example/Auth/Tests/OCMStubRecorder+FIRAuthUnitTests.h
new file mode 100644
index 0000000..61f9935
--- /dev/null
+++ b/Example/Auth/Tests/OCMStubRecorder+FIRAuthUnitTests.h
@@ -0,0 +1,112 @@
+/*
+ * 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 <Foundation/Foundation.h>
+
+#import <OCMock/OCMStubRecorder.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** @typedef FIRAuthGeneralBlock1
+ @brief A general block that takes one id and returns nothing.
+ */
+typedef void (^FIRAuthGeneralBlock1)(id);
+
+/** @typedef FIRAuthGeneralBlock2
+ @brief A general block that takes two nullable ids and returns nothing.
+ */
+typedef void (^FIRAuthGeneralBlock2)(id _Nullable, id _Nullable);
+
+/** @typedef FIRAuthIdDoubleIdBlock
+ @brief A block that takes third parameters with types @c id, @c double, and @c id .
+ */
+typedef void (^FIRAuthIdDoubleIdBlock)(id, double, id);
+
+/** @category OCMStubRecorder(FIRAuthUnitTests)
+ @brief Utility methods and properties use by Firebase Auth unit tests.
+ */
+@interface OCMStubRecorder (FIRAuthUnitTests)
+
+/** @fn andCallBlock1
+ @brief Calls a general block that takes one parameter as the action of the stub.
+ @param block1 A block that takes exactly one 'id'-compatible parameter.
+ @remarks The method being stubbed must take exactly one parameter, which must be
+ compatible with type 'id'.
+ */
+- (id)andCallBlock1:(FIRAuthGeneralBlock1)block1;
+
+/** @fn andCallBlock2
+ @brief Calls a general block that takes two parameters as the action of the stub.
+ @param block2 A block that takes exactly two 'id'-compatible parameters.
+ @remarks The method being stubbed must take exactly two parameters, both of which must be
+ compatible with type 'id'.
+ */
+- (id)andCallBlock2:(FIRAuthGeneralBlock2)block2;
+
+/** @fn andDispatchError2
+ @brief Dispatchs an error to the second callback parameter in the global auth work queue.
+ @param error The error to call back as the second argument to the second parameter block.
+ @remarks The method being stubbed must take exactly two parameters, the first of which must be
+ compatible with type 'id' and the second of which must be a block that takes an
+ 'id'-compatible parameter and an NSError* parameter.
+ */
+- (id)andDispatchError2:(NSError *)error;
+
+/** @fn andCallIdDoubleIdBlock:
+ @brief Calls a block that takes three parameters as the action of the stub.
+ @param block A block that takes exactly three parameters as described.
+ @remarks The method being stubbed must take exactly three parameters. Its first and the third
+ parameters must be compatible with type 'id' and its second parameter must be a 'double'.
+ */
+- (id)andCallIdDoubleIdBlock:(FIRAuthIdDoubleIdBlock)block;
+
+// This macro allows .andCallBlock1 shorthand to match established style of OCMStubRecorder.
+#define andCallBlock1(block1) _andCallBlock1(block1)
+
+// This macro allows .andCallBlock2 shorthand to match established style of OCMStubRecorder.
+#define andCallBlock2(block2) _andCallBlock2(block2)
+
+// This macro allows .andDispatchError2 shorthand to match established style of OCMStubRecorder.
+#define andDispatchError2(block2) _andDispatchError2(block2)
+
+// This macro allows .andCallIdDoubleIdBlock shorthand to match established style of
+// OCMStubRecorder.
+#define andCallIdDoubleIdBlock(block) _andCallIdDoubleIdBlock(block)
+
+
+/** @property _andCallBlock1
+ @brief A block that calls @c andCallBlock1: method on self.
+ */
+@property(nonatomic, readonly) OCMStubRecorder *(^ _andCallBlock1)(FIRAuthGeneralBlock1);
+
+/** @property _andCallBlock2
+ @brief A block that calls @c andCallBlock2: method on self.
+ */
+@property(nonatomic, readonly) OCMStubRecorder *(^ _andCallBlock2)(FIRAuthGeneralBlock2);
+
+/** @property _andDispatchError2
+ @brief A block that calls @c andDispatchError2: method on self.
+ */
+@property(nonatomic, readonly) OCMStubRecorder *(^ _andDispatchError2)(NSError *);
+
+/** @property _andCallIdDoubleIdBlock
+ @brief A block that calls @c andCallBlock2: method on self.
+ */
+@property(nonatomic, readonly) OCMStubRecorder *(^ _andCallIdDoubleIdBlock)(FIRAuthIdDoubleIdBlock);
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Example/Auth/Tests/OCMStubRecorder+FIRAuthUnitTests.m b/Example/Auth/Tests/OCMStubRecorder+FIRAuthUnitTests.m
new file mode 100644
index 0000000..bd43303
--- /dev/null
+++ b/Example/Auth/Tests/OCMStubRecorder+FIRAuthUnitTests.m
@@ -0,0 +1,103 @@
+/*
+ * 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 "OCMStubRecorder+FIRAuthUnitTests.h"
+
+#import "FIRAuthGlobalWorkQueue.h"
+
+/** @fn argumentOf
+ @brief Retrieves a specific argument from a method invocation.
+ @param invocation The Objective-C method invocation.
+ @param position The position of the argument to retrieve, starting from 0.
+ @return The argument at the given position that the method has been invoked with.
+ @remarks The argument type must be compatible with @c id .
+ */
+static id argumentOf(NSInvocation *invocation, int position) {
+ __unsafe_unretained id unretainedArgument;
+ // Indices 0 and 1 indicate the hidden arguments self and _cmd. Actual arguments starts from 2.
+ [invocation getArgument:&unretainedArgument atIndex:position + 2];
+ // The argument needs to be retained, or it will be released along with the invocation object.
+ id argument = unretainedArgument;
+ return argument;
+}
+
+/** @fn doubleArgumentOf
+ @brief Retrieves a specific argument of type 'double' from a method invocation.
+ @param invocation The Objective-C method invocation.
+ @param position The position of the argument to retrieve, starting from 0.
+ @return The argument at the given position that the method has been invoked with.
+ @remarks The argument type must be @c double .
+ */
+static double doubleArgumentOf(NSInvocation *invocation, int position) {
+ double argument;
+ // Indices 0 and 1 indicate the hidden arguments self and _cmd. Actual arguments starts from 2.
+ [invocation getArgument:&argument atIndex:position + 2];
+ return argument;
+}
+
+@implementation OCMStubRecorder (FIRAuthUnitTests)
+
+- (id)andCallBlock1:(FIRAuthGeneralBlock1)block1 {
+ return [self andDo:^(NSInvocation *invocation) {
+ block1(argumentOf(invocation, 0));
+ }];
+}
+
+- (id)andCallBlock2:(FIRAuthGeneralBlock2)block2 {
+ return [self andDo:^(NSInvocation *invocation) {
+ block2(argumentOf(invocation, 0), argumentOf(invocation, 1));
+ }];
+}
+
+- (id)andDispatchError2:(NSError *)error {
+ return [self andCallBlock2:^(id request, FIRAuthGeneralBlock2 callback) {
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ callback(nil, error);
+ });
+ }];
+}
+
+- (id)andCallIdDoubleIdBlock:(FIRAuthIdDoubleIdBlock)block {
+ return [self andDo:^(NSInvocation *invocation) {
+ block(argumentOf(invocation, 0), doubleArgumentOf(invocation, 2), argumentOf(invocation, 2));
+ }];
+}
+
+- (OCMStubRecorder *(^)(FIRAuthGeneralBlock1))_andCallBlock1 {
+ return ^(FIRAuthGeneralBlock1 block1) {
+ return [self andCallBlock1:block1];
+ };
+}
+
+- (OCMStubRecorder *(^)(FIRAuthGeneralBlock2))_andCallBlock2 {
+ return ^(FIRAuthGeneralBlock2 block2) {
+ return [self andCallBlock2:block2];
+ };
+}
+
+- (OCMStubRecorder *(^)(NSError *))_andDispatchError2 {
+ return ^(NSError *error) {
+ return [self andDispatchError2:error];
+ };
+}
+
+- (OCMStubRecorder *(^)(FIRAuthIdDoubleIdBlock))_andCallIdDoubleIdBlock {
+ return ^(FIRAuthIdDoubleIdBlock block) {
+ return [self andCallIdDoubleIdBlock:block];
+ };
+}
+
+@end
diff --git a/Example/Auth/Tests/Tests-Info.plist b/Example/Auth/Tests/Tests-Info.plist
new file mode 100644
index 0000000..169b6f7
--- /dev/null
+++ b/Example/Auth/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>