aboutsummaryrefslogtreecommitdiffhomepage
path: root/Example/Auth/Tests
diff options
context:
space:
mode:
Diffstat (limited to 'Example/Auth/Tests')
-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
52 files changed, 13714 insertions, 0 deletions
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>