FIRInstanceIDTest.m 64 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506
  1. /*
  2. * Copyright 2019 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <XCTest/XCTest.h>
  17. #import "FirebaseInstallations/Source/Library/Private/FirebaseInstallationsInternal.h"
  18. #import <OCMock/OCMock.h>
  19. #import "Firebase/InstanceID/Private/FIRInstanceID_Private.h"
  20. #import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h"
  21. #import "Firebase/InstanceID/FIRInstanceIDAuthService.h"
  22. #import "Firebase/InstanceID/FIRInstanceIDCheckinPreferences+Internal.h"
  23. #import "Firebase/InstanceID/FIRInstanceIDConstants.h"
  24. #import "Firebase/InstanceID/FIRInstanceIDTokenInfo.h"
  25. #import "Firebase/InstanceID/FIRInstanceIDTokenManager.h"
  26. #import "Firebase/InstanceID/FIRInstanceIDUtilities.h"
  27. #import "Firebase/InstanceID/NSError+FIRInstanceID.h"
  28. #import "Firebase/InstanceID/Private/FIRInstanceID+Private.h"
  29. static NSString *const kFakeIID = @"12345678";
  30. static NSString *const kFakeAPNSToken = @"this is a fake apns token";
  31. static NSString *const kAuthorizedEntity = @"test-audience";
  32. static NSString *const kScope = @"test-scope";
  33. static NSString *const kToken = @"12345678:test-token";
  34. static FIRInstanceIDTokenInfo *sTokenInfo;
  35. // Faking checkin calls
  36. static NSString *const kDeviceAuthId = @"device-id";
  37. static NSString *const kSecretToken = @"secret-token";
  38. static NSString *const kVersionInfo = @"1.0";
  39. // FIRApp configuration.
  40. static NSString *const kGCMSenderID = @"correct_gcm_sender_id";
  41. static NSString *const kGoogleAppID = @"1:123:ios:123abc";
  42. @interface FIRInstanceID (ExposedForTest)
  43. @property(nonatomic, readwrite, strong) FIRInstanceIDTokenManager *tokenManager;
  44. @property(nonatomic, readwrite, strong) FIRInstallations *installations;
  45. @property(nonatomic, readwrite, copy) NSString *fcmSenderID;
  46. @property(nonatomic, readwrite, copy) NSString *firebaseAppID;
  47. @property(nonatomic, readwrite, copy) NSString *defaultFCMToken;
  48. - (NSInteger)retryIntervalToFetchDefaultToken;
  49. - (BOOL)isFCMAutoInitEnabled;
  50. - (void)didCompleteConfigure;
  51. - (NSString *)cachedTokenIfAvailable;
  52. - (void)deleteIdentityWithHandler:(FIRInstanceIDDeleteHandler)handler;
  53. + (FIRInstanceID *)instanceIDForTests;
  54. - (void)defaultTokenWithHandler:(FIRInstanceIDTokenHandler)handler;
  55. - (instancetype)initPrivately;
  56. - (void)start;
  57. + (int64_t)maxRetryCountForDefaultToken;
  58. + (int64_t)minIntervalForDefaultTokenRetry;
  59. + (int64_t)maxRetryIntervalForDefaultTokenInSeconds;
  60. - (void)fetchNewTokenWithAuthorizedEntity:(NSString *)authorizedEntity
  61. scope:(NSString *)scope
  62. instanceID:(NSString *)instanceID
  63. options:(NSDictionary *)options
  64. handler:(FIRInstanceIDTokenHandler)handler;
  65. @end
  66. @interface FIRInstanceIDTest : XCTestCase
  67. @property(nonatomic, readwrite, assign) BOOL hasCheckinInfo;
  68. #pragma clang diagnostic push
  69. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  70. @property(nonatomic, readwrite, strong) FIRInstanceID *instanceID;
  71. #pragma clang diagnostic pop
  72. @property(nonatomic, readwrite, strong) id mockInstanceID;
  73. @property(nonatomic, readwrite, strong) id mockTokenManager;
  74. @property(nonatomic, readwrite, strong) id mockInstallations;
  75. @property(nonatomic, readwrite, strong) id mockAuthService;
  76. @property(nonatomic, readwrite, strong) id<NSObject> tokenRefreshNotificationObserver;
  77. @property(nonatomic, readwrite, copy) FIRInstanceIDTokenHandler newTokenCompletion;
  78. @property(nonatomic, readwrite, copy) FIRInstanceIDDeleteTokenHandler deleteTokenCompletion;
  79. @end
  80. @implementation FIRInstanceIDTest
  81. #pragma clang diagnostic push
  82. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  83. - (void)setUp {
  84. [super setUp];
  85. // `+[FIRInstallations installations]` supposed to be used on `-[FIRInstanceID start]` to get
  86. // `FIRInstallations` default instance. Need to stub it before.
  87. self.mockInstallations = OCMClassMock([FIRInstallations class]);
  88. OCMStub([self.mockInstallations installations]).andReturn(self.mockInstallations);
  89. _instanceID = [[FIRInstanceID alloc] initPrivately];
  90. [_instanceID start];
  91. if (!sTokenInfo) {
  92. sTokenInfo = [[FIRInstanceIDTokenInfo alloc] initWithAuthorizedEntity:kAuthorizedEntity
  93. scope:kScope
  94. token:kToken
  95. appVersion:nil
  96. firebaseAppID:nil];
  97. sTokenInfo.cacheTime = [NSDate date];
  98. }
  99. [self mockInstanceIDObjects];
  100. }
  101. - (void)tearDown {
  102. [[NSNotificationCenter defaultCenter] removeObserver:self.tokenRefreshNotificationObserver];
  103. self.mockInstanceID = nil;
  104. self.instanceID = nil;
  105. self.mockTokenManager = nil;
  106. self.mockInstallations = nil;
  107. [super tearDown];
  108. }
  109. - (void)mockInstanceIDObjects {
  110. // Mock that we have valid checkin info. Individual tests can override this.
  111. self.hasCheckinInfo = YES;
  112. self.mockAuthService = OCMClassMock([FIRInstanceIDAuthService class]);
  113. [[[self.mockAuthService stub] andDo:^(NSInvocation *invocation) {
  114. [invocation setReturnValue:&self->_hasCheckinInfo];
  115. }] hasValidCheckinInfo];
  116. self.mockTokenManager = OCMClassMock([FIRInstanceIDTokenManager class]);
  117. [[[self.mockTokenManager stub] andReturn:self.mockAuthService] authService];
  118. _instanceID.fcmSenderID = kAuthorizedEntity;
  119. self.mockInstanceID = OCMPartialMock(_instanceID);
  120. [self.mockInstanceID setTokenManager:self.mockTokenManager];
  121. #pragma clang diagnostic push
  122. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  123. id instanceIDClassMock = OCMClassMock([FIRInstanceID class]);
  124. #pragma clang diagnostic pop
  125. OCMStub(ClassMethod([instanceIDClassMock minIntervalForDefaultTokenRetry])).andReturn(2);
  126. OCMStub(ClassMethod([instanceIDClassMock maxRetryIntervalForDefaultTokenInSeconds]))
  127. .andReturn(10);
  128. }
  129. /**
  130. * Tests that the FIRInstanceID's sharedInstance class method produces an instance of
  131. * FIRInstanceID with an associated FIRInstanceIDTokenManager.
  132. */
  133. - (void)testSharedInstance {
  134. #pragma clang diagnostic push
  135. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  136. // The shared instance should be `nil` before the app is configured.
  137. XCTAssertNil([FIRInstanceID instanceID]);
  138. // The shared instance relies on the default app being configured. Configure it.
  139. FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:kGoogleAppID
  140. GCMSenderID:kGCMSenderID];
  141. options.APIKey = @"AIzaSy-ApiKeyWithValidFormat_0123456789";
  142. options.projectID = @"project-id";
  143. [FIRApp configureWithName:kFIRDefaultAppName options:options];
  144. FIRInstanceID *instanceID = [FIRInstanceID instanceID];
  145. XCTAssertNotNil(instanceID);
  146. XCTAssertNotNil(instanceID.tokenManager);
  147. // Ensure a second call returns the same instance as the first.
  148. FIRInstanceID *secondInstanceID = [FIRInstanceID instanceID];
  149. XCTAssertEqualObjects(instanceID, secondInstanceID);
  150. // Reset the default app for the next test.
  151. [FIRApp resetApps];
  152. }
  153. - (void)testFCMAutoInitEnabled {
  154. XCTAssertFalse([_instanceID isFCMAutoInitEnabled],
  155. @"When FCM is not available, FCM Auto Init Enabled should be NO.");
  156. }
  157. - (void)testTokenShouldBeRefreshedIfCacheTokenNeedsToBeRefreshed {
  158. [[[self.mockInstanceID stub] andReturn:kToken] cachedTokenIfAvailable];
  159. [[[self.mockTokenManager stub] andReturnValue:@(YES)]
  160. checkTokenRefreshPolicyWithIID:[OCMArg any]];
  161. [[[self.mockInstanceID stub] andDo:^(NSInvocation *invocation){
  162. }] tokenWithAuthorizedEntity:[OCMArg any]
  163. scope:[OCMArg any]
  164. options:[OCMArg any]
  165. handler:[OCMArg any]];
  166. [self expectInstallationsInstallationIDWithFID:kToken error:nil];
  167. [self.mockInstanceID didCompleteConfigure];
  168. OCMVerify([self.mockInstanceID defaultTokenWithHandler:nil]);
  169. XCTAssertEqualObjects([self.mockInstanceID token], kToken);
  170. }
  171. - (void)testTokenShouldBeRefreshedIfNoCacheTokenButAutoInitAllowed {
  172. [[[self.mockInstanceID stub] andReturn:nil] cachedTokenIfAvailable];
  173. [[[self.mockInstanceID stub] andReturnValue:@(YES)] isFCMAutoInitEnabled];
  174. [[[self.mockInstanceID stub] andDo:^(NSInvocation *invocation){
  175. }] tokenWithAuthorizedEntity:[OCMArg any]
  176. scope:[OCMArg any]
  177. options:[OCMArg any]
  178. handler:[OCMArg any]];
  179. [self.mockInstanceID didCompleteConfigure];
  180. OCMVerify([self.mockInstanceID defaultTokenWithHandler:nil]);
  181. }
  182. - (void)testTokenShouldBeRefreshedIfIIDAndTokenAreNotConsistent {
  183. XCTestExpectation *expectation = [self expectationWithDescription:@"token request is complete"];
  184. NSString *APNSKey = kFIRInstanceIDTokenOptionsAPNSKey;
  185. NSString *serverKey = kFIRInstanceIDTokenOptionsAPNSIsSandboxKey;
  186. [self mockAuthServiceToAlwaysReturnValidCheckin];
  187. NSData *fakeAPNSDeviceToken = [kFakeAPNSToken dataUsingEncoding:NSUTF8StringEncoding];
  188. BOOL isSandbox = YES;
  189. NSDictionary *tokenOptions = @{
  190. APNSKey : fakeAPNSDeviceToken,
  191. serverKey : @(isSandbox),
  192. };
  193. FIRInstanceIDAPNSInfo *optionsAPNSInfo =
  194. [[FIRInstanceIDAPNSInfo alloc] initWithTokenOptionsDictionary:tokenOptions];
  195. sTokenInfo.APNSInfo = optionsAPNSInfo;
  196. [[[self.mockTokenManager stub] andReturn:sTokenInfo]
  197. cachedTokenInfoWithAuthorizedEntity:[OCMArg any]
  198. scope:[OCMArg any]];
  199. [[self.mockTokenManager stub]
  200. fetchNewTokenWithAuthorizedEntity:kGCMSenderID
  201. scope:@"*"
  202. instanceID:@"differentIID"
  203. options:tokenOptions
  204. handler:[OCMArg invokeBlockWithArgs:@"differentIID:newToken",
  205. [NSNull null], nil]];
  206. [self expectInstallationsInstallationIDWithFID:@"differentIID" error:nil];
  207. [self.mockInstanceID
  208. tokenWithAuthorizedEntity:kGCMSenderID
  209. scope:@"*"
  210. options:tokenOptions
  211. handler:^(NSString *_Nullable token, NSError *_Nullable error) {
  212. XCTAssertEqualObjects(token, @"differentIID:newToken");
  213. [expectation fulfill];
  214. }];
  215. [self waitForExpectationsWithTimeout:1.0 handler:NULL];
  216. }
  217. - (void)testTokenIsDeletedAlongWithIdentity {
  218. [[[self.mockInstanceID stub] andReturnValue:@(YES)] isFCMAutoInitEnabled];
  219. [[[self.mockInstanceID stub] andDo:^(NSInvocation *invocation){
  220. }] tokenWithAuthorizedEntity:[OCMArg any]
  221. scope:[OCMArg any]
  222. options:[OCMArg any]
  223. handler:[OCMArg any]];
  224. [self.mockInstanceID deleteIdentityWithHandler:^(NSError *_Nullable error) {
  225. XCTAssertNil([self.mockInstanceID token]);
  226. }];
  227. }
  228. - (void)testTokenIsFetchedDuringIIDGeneration {
  229. XCTestExpectation *tokenExpectation = [self
  230. expectationWithDescription:@"Token is refreshed when getID is called to avoid IID conflict."];
  231. [self expectInstallationsInstallationIDWithFID:kFakeIID error:nil];
  232. [self.mockInstanceID getIDWithHandler:^(NSString *identity, NSError *error) {
  233. XCTAssertNotNil(identity);
  234. XCTAssertEqual(identity, kFakeIID);
  235. OCMVerify([self.mockInstanceID token]);
  236. [tokenExpectation fulfill];
  237. }];
  238. [self waitForExpectationsWithTimeout:0.1
  239. handler:^(NSError *error) {
  240. XCTAssertNil(error);
  241. }];
  242. }
  243. /**
  244. * Tests that when a new InstanceID token is successfully produced,
  245. * the callback is invoked with a token that is not an empty string and with no error.
  246. */
  247. - (void)testNewTokenSuccess {
  248. XCTestExpectation *tokenExpectation =
  249. [self expectationWithDescription:@"New token handler invoked."];
  250. NSString *APNSKey = kFIRInstanceIDTokenOptionsAPNSKey;
  251. NSString *serverKey = kFIRInstanceIDTokenOptionsAPNSIsSandboxKey;
  252. [self stubInstallationsToReturnValidID];
  253. [self mockAuthServiceToAlwaysReturnValidCheckin];
  254. NSData *fakeAPNSDeviceToken = [kFakeAPNSToken dataUsingEncoding:NSUTF8StringEncoding];
  255. BOOL isSandbox = YES;
  256. NSDictionary *tokenOptions = @{
  257. APNSKey : fakeAPNSDeviceToken,
  258. serverKey : @(isSandbox),
  259. };
  260. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  261. self.newTokenCompletion(kToken, nil);
  262. }] fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity
  263. scope:kScope
  264. instanceID:[OCMArg any]
  265. options:[OCMArg checkWithBlock:^BOOL(id obj) {
  266. NSDictionary *options = (NSDictionary *)obj;
  267. XCTAssertTrue([options[APNSKey] isEqual:fakeAPNSDeviceToken]);
  268. XCTAssertTrue([options[serverKey] isEqual:@(isSandbox)]);
  269. return YES;
  270. }]
  271. handler:[OCMArg checkWithBlock:^BOOL(id obj) {
  272. self.newTokenCompletion = obj;
  273. return obj != nil;
  274. }]];
  275. [self.instanceID tokenWithAuthorizedEntity:kAuthorizedEntity
  276. scope:kScope
  277. options:tokenOptions
  278. handler:^(NSString *token, NSError *error) {
  279. XCTAssertNotNil(token);
  280. XCTAssertGreaterThan(token.length, 0);
  281. XCTAssertNil(error);
  282. [tokenExpectation fulfill];
  283. }];
  284. [self waitForExpectationsWithTimeout:1
  285. handler:^(NSError *error) {
  286. XCTAssertNil(error);
  287. }];
  288. }
  289. /**
  290. * Get Token should fail if we do not have valid checkin info and are unable to
  291. * retreive one.
  292. */
  293. - (void)testNewTokenCheckinFailure {
  294. self.hasCheckinInfo = NO;
  295. __block FIRInstanceIDDeviceCheckinCompletion checkinHandler;
  296. [[[self.mockAuthService stub] andDo:^(NSInvocation *invocation) {
  297. if (checkinHandler) {
  298. FIRInstanceIDErrorCode code = kFIRInstanceIDErrorCodeUnknown;
  299. NSError *error = [NSError errorWithFIRInstanceIDErrorCode:code];
  300. checkinHandler(nil, error);
  301. }
  302. }] fetchCheckinInfoWithHandler:[OCMArg checkWithBlock:^BOOL(id obj) {
  303. return (checkinHandler = obj) != nil;
  304. }]];
  305. XCTestExpectation *tokenExpectation =
  306. [self expectationWithDescription:@"New token handler invoked."];
  307. NSDictionary *tokenOptions = @{
  308. kFIRInstanceIDTokenOptionsAPNSKey : [kFakeAPNSToken dataUsingEncoding:NSUTF8StringEncoding],
  309. kFIRInstanceIDTokenOptionsAPNSIsSandboxKey : @(YES),
  310. };
  311. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  312. self.newTokenCompletion(kToken, nil);
  313. }] fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity
  314. scope:kScope
  315. instanceID:[OCMArg any]
  316. options:[OCMArg any]
  317. handler:[OCMArg checkWithBlock:^BOOL(id obj) {
  318. self.newTokenCompletion = obj;
  319. return obj != nil;
  320. }]];
  321. [self.instanceID tokenWithAuthorizedEntity:kAuthorizedEntity
  322. scope:kScope
  323. options:tokenOptions
  324. handler:^(NSString *token, NSError *error) {
  325. XCTAssertNil(token);
  326. XCTAssertNotNil(error);
  327. [tokenExpectation fulfill];
  328. }];
  329. [self waitForExpectationsWithTimeout:60.0
  330. handler:^(NSError *error) {
  331. XCTAssertNil(error);
  332. }];
  333. }
  334. /**
  335. * Get token with no valid checkin should wait for any existing checkin operation to finish.
  336. * If the checkin succeeds within a stipulated amount of time period getting the token should
  337. * also succeed.
  338. */
  339. - (void)testNewTokenSuccessAfterWaiting {
  340. self.hasCheckinInfo = NO;
  341. __block FIRInstanceIDDeviceCheckinCompletion checkinHandler;
  342. [[[self.mockAuthService stub] andDo:^(NSInvocation *invocation) {
  343. if (checkinHandler) {
  344. FIRInstanceIDErrorCode code = kFIRInstanceIDErrorCodeUnknown;
  345. NSError *error = [NSError errorWithFIRInstanceIDErrorCode:code];
  346. checkinHandler(nil, error);
  347. }
  348. }] fetchCheckinInfoWithHandler:[OCMArg checkWithBlock:^BOOL(id obj) {
  349. return (checkinHandler = obj) != nil;
  350. }]];
  351. XCTestExpectation *tokenExpectation =
  352. [self expectationWithDescription:@"New token handler invoked."];
  353. NSDictionary *tokenOptions = @{
  354. kFIRInstanceIDTokenOptionsAPNSKey : [kFakeAPNSToken dataUsingEncoding:NSUTF8StringEncoding],
  355. kFIRInstanceIDTokenOptionsAPNSIsSandboxKey : @(YES),
  356. };
  357. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  358. self.newTokenCompletion(kToken, nil);
  359. }] fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity
  360. scope:kScope
  361. instanceID:[OCMArg any]
  362. options:[OCMArg any]
  363. handler:[OCMArg checkWithBlock:^BOOL(id obj) {
  364. self.newTokenCompletion = obj;
  365. return obj != nil;
  366. }]];
  367. [self.instanceID tokenWithAuthorizedEntity:kAuthorizedEntity
  368. scope:kScope
  369. options:tokenOptions
  370. handler:^(NSString *token, NSError *error) {
  371. XCTAssertNil(token);
  372. XCTAssertNotNil(error);
  373. [tokenExpectation fulfill];
  374. }];
  375. [self waitForExpectationsWithTimeout:60.0
  376. handler:^(NSError *error) {
  377. XCTAssertNil(error);
  378. }];
  379. }
  380. /**
  381. * Test that the prod APNS token is correctly prefixed with "prod".
  382. */
  383. - (void)testAPNSTokenIsPrefixedCorrectlyForServerType {
  384. NSString *APNSKey = kFIRInstanceIDTokenOptionsAPNSKey;
  385. NSString *serverTypeKey = kFIRInstanceIDTokenOptionsAPNSIsSandboxKey;
  386. NSDictionary *prodTokenOptions = @{
  387. APNSKey : [kFakeAPNSToken dataUsingEncoding:NSUTF8StringEncoding],
  388. serverTypeKey : @(NO),
  389. };
  390. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation){
  391. }] fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity
  392. scope:kScope
  393. instanceID:[OCMArg any]
  394. options:[OCMArg checkWithBlock:^BOOL(id obj) {
  395. NSDictionary *options = (NSDictionary *)obj;
  396. XCTAssertTrue([options[APNSKey] hasPrefix:@"p_"]);
  397. XCTAssertFalse([options[serverTypeKey] boolValue]);
  398. return YES;
  399. }]
  400. handler:OCMOCK_ANY];
  401. [self.instanceID tokenWithAuthorizedEntity:kAuthorizedEntity
  402. scope:kScope
  403. options:prodTokenOptions
  404. handler:^(NSString *token, NSError *error){
  405. }];
  406. }
  407. /**
  408. * Tests that when there is a failure in producing a new InstanceID token,
  409. * the callback is invoked with an error and a nil token.
  410. */
  411. - (void)testNewTokenFailure {
  412. XCTestExpectation *tokenExpectation =
  413. [self expectationWithDescription:@"New token handler invoked."];
  414. NSDictionary *tokenOptions = [NSDictionary dictionary];
  415. [self mockAuthServiceToAlwaysReturnValidCheckin];
  416. [self stubInstallationsToReturnValidID];
  417. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  418. NSError *someError = [[NSError alloc] initWithDomain:@"InstanceIDUnitTest" code:0 userInfo:nil];
  419. self.newTokenCompletion(nil, someError);
  420. }] fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity
  421. scope:kScope
  422. instanceID:[OCMArg any]
  423. options:tokenOptions
  424. handler:[OCMArg checkWithBlock:^BOOL(id obj) {
  425. self.newTokenCompletion = obj;
  426. return obj != nil;
  427. }]];
  428. [self.instanceID tokenWithAuthorizedEntity:kAuthorizedEntity
  429. scope:kScope
  430. options:tokenOptions
  431. handler:^(NSString *token, NSError *error) {
  432. XCTAssertNil(token);
  433. XCTAssertNotNil(error);
  434. [tokenExpectation fulfill];
  435. }];
  436. [self waitForExpectationsWithTimeout:1
  437. handler:^(NSError *error) {
  438. XCTAssertNil(error);
  439. }];
  440. }
  441. /**
  442. * Tests that when a token is deleted successfully, the callback is invoked with no error.
  443. */
  444. - (void)testDeleteTokenSuccess {
  445. XCTestExpectation *deleteExpectation =
  446. [self expectationWithDescription:@"Delete handler invoked."];
  447. [self stubInstallationsToReturnValidID];
  448. [self mockAuthServiceToAlwaysReturnValidCheckin];
  449. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  450. #pragma clang diagnostic push
  451. #pragma clang diagnostic ignored "-Wnonnull"
  452. self.deleteTokenCompletion(nil);
  453. #pragma clang diagnostic pop
  454. }] deleteTokenWithAuthorizedEntity:kAuthorizedEntity
  455. scope:kScope
  456. instanceID:[OCMArg any]
  457. handler:[OCMArg checkWithBlock:^BOOL(id obj) {
  458. self.deleteTokenCompletion = obj;
  459. return obj != nil;
  460. }]];
  461. [self.instanceID deleteTokenWithAuthorizedEntity:kAuthorizedEntity
  462. scope:kScope
  463. handler:^(NSError *error) {
  464. XCTAssertNil(error);
  465. [deleteExpectation fulfill];
  466. }];
  467. [self waitForExpectationsWithTimeout:1
  468. handler:^(NSError *error) {
  469. XCTAssertNil(error);
  470. }];
  471. }
  472. /**
  473. * Tests that when a token deletion fails, the callback is invoked with an error.
  474. */
  475. - (void)testDeleteTokenFailure {
  476. XCTestExpectation *deleteExpectation =
  477. [self expectationWithDescription:@"Delete handler invoked."];
  478. [self stubInstallationsToReturnValidID];
  479. [self mockAuthServiceToAlwaysReturnValidCheckin];
  480. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  481. NSError *someError = [[NSError alloc] initWithDomain:@"InstanceIDUnitTest" code:0 userInfo:nil];
  482. self.deleteTokenCompletion(someError);
  483. }] deleteTokenWithAuthorizedEntity:kAuthorizedEntity
  484. scope:kScope
  485. instanceID:[OCMArg any]
  486. handler:[OCMArg checkWithBlock:^BOOL(id obj) {
  487. self.deleteTokenCompletion = obj;
  488. return obj != nil;
  489. }]];
  490. [self.instanceID deleteTokenWithAuthorizedEntity:kAuthorizedEntity
  491. scope:kScope
  492. handler:^(NSError *error) {
  493. XCTAssertNotNil(error);
  494. [deleteExpectation fulfill];
  495. }];
  496. [self waitForExpectationsWithTimeout:1
  497. handler:^(NSError *error) {
  498. XCTAssertNil(error);
  499. }];
  500. }
  501. /**
  502. * Tests that not having a senderID will fetch a `nil` default token.
  503. */
  504. - (void)testDefaultToken_noSenderID {
  505. _instanceID.fcmSenderID = nil;
  506. XCTAssertNil([self.mockInstanceID token]);
  507. }
  508. /**
  509. * Tests that not having a cached token results in trying to fetch a new default token.
  510. */
  511. - (void)testDefaultToken_noCachedToken {
  512. [[[self.mockTokenManager stub] andReturn:nil]
  513. cachedTokenInfoWithAuthorizedEntity:kAuthorizedEntity
  514. scope:@"*"];
  515. OCMExpect([self.mockInstanceID defaultTokenWithHandler:nil]);
  516. XCTAssertNil([self.mockInstanceID token]);
  517. OCMVerify([self.mockInstanceID defaultTokenWithHandler:nil]);
  518. [self.mockInstanceID stopMocking];
  519. }
  520. /**
  521. * Tests that when we have a cached default token, calling `getToken` returns that token
  522. * without hitting the network.
  523. */
  524. - (void)testDefaultToken_validCachedToken {
  525. [[[self.mockTokenManager stub] andReturn:sTokenInfo]
  526. cachedTokenInfoWithAuthorizedEntity:kAuthorizedEntity
  527. scope:@"*"];
  528. [[self.mockInstanceID reject] defaultTokenWithHandler:nil];
  529. XCTAssertEqualObjects([self.mockInstanceID token], kToken);
  530. }
  531. /**
  532. * Tests that the callback handler will be invoked when the default token is fetched
  533. * despite the token being unchanged.
  534. */
  535. - (void)testDefaultToken_callbackInvokedForUnchangedToken {
  536. XCTestExpectation *defaultTokenExpectation =
  537. [self expectationWithDescription:@"Token fetch was successful."];
  538. __block FIRInstanceIDTokenInfo *cachedTokenInfo = nil;
  539. [self stubInstallationsToReturnValidID];
  540. [self mockAuthServiceToAlwaysReturnValidCheckin];
  541. // Mock Token manager to always succeed the token fetch, and return
  542. // a particular cached value.
  543. // Return a dynamic cachedToken variable whenever the cached is checked.
  544. // This uses an invocation-based mock because the |cachedToken| pointer
  545. // will change. Normal stubbing will always return the initial pointer,
  546. // which in this case is 0x0 (nil).
  547. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  548. [invocation setReturnValue:&cachedTokenInfo];
  549. }] cachedTokenInfoWithAuthorizedEntity:kAuthorizedEntity scope:kFIRInstanceIDDefaultTokenScope];
  550. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  551. self.newTokenCompletion(kToken, nil);
  552. }] fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity
  553. scope:kFIRInstanceIDDefaultTokenScope
  554. instanceID:[OCMArg any]
  555. options:[OCMArg any]
  556. handler:[OCMArg checkWithBlock:^BOOL(id obj) {
  557. self.newTokenCompletion = obj;
  558. return obj != nil;
  559. }]];
  560. __block NSString *notificationToken = nil;
  561. // Fetch token once to store token state
  562. NSString *notificationName = kFIRInstanceIDTokenRefreshNotification;
  563. self.tokenRefreshNotificationObserver = [[NSNotificationCenter defaultCenter]
  564. addObserverForName:notificationName
  565. object:nil
  566. queue:nil
  567. usingBlock:^(NSNotification *_Nonnull note) {
  568. // Should have saved token to cache
  569. cachedTokenInfo = sTokenInfo;
  570. notificationToken = [[self.instanceID token] copy];
  571. [defaultTokenExpectation fulfill];
  572. }];
  573. XCTAssertNil([self.mockInstanceID token]);
  574. [self waitForExpectationsWithTimeout:10.0 handler:nil];
  575. [[NSNotificationCenter defaultCenter] removeObserver:self.tokenRefreshNotificationObserver];
  576. XCTAssertEqualObjects(notificationToken, kToken);
  577. // Fetch default handler again without any token changes
  578. XCTestExpectation *tokenCallback = [self expectationWithDescription:@"Callback was invoked."];
  579. [self.mockInstanceID defaultTokenWithHandler:^(NSString *token, NSError *error) {
  580. notificationToken = token;
  581. [tokenCallback fulfill];
  582. }];
  583. [self waitForExpectationsWithTimeout:10.0 handler:nil];
  584. XCTAssertEqualObjects(notificationToken, kToken);
  585. }
  586. /**
  587. * Test that when we fetch a new default token and cache it successfully we post a
  588. * tokenRefresh notification which allows to fetch the cached token.
  589. */
  590. - (void)testDefaultTokenFetch_returnValidToken {
  591. XCTestExpectation *defaultTokenExpectation =
  592. [self expectationWithDescription:@"Successfully got default token."];
  593. __block FIRInstanceIDTokenInfo *cachedTokenInfo = nil;
  594. [self stubInstallationsToReturnValidID];
  595. [self mockAuthServiceToAlwaysReturnValidCheckin];
  596. // Mock Token manager to always succeed the token fetch, and return
  597. // a particular cached value.
  598. // Return a dynamic cachedToken variable whenever the cached is checked.
  599. // This uses an invocation-based mock because the |cachedToken| pointer
  600. // will change. Normal stubbing will always return the initial pointer,
  601. // which in this case is 0x0 (nil).
  602. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  603. [invocation setReturnValue:&cachedTokenInfo];
  604. }] cachedTokenInfoWithAuthorizedEntity:kAuthorizedEntity scope:kFIRInstanceIDDefaultTokenScope];
  605. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  606. self.newTokenCompletion(kToken, nil);
  607. }] fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity
  608. scope:kFIRInstanceIDDefaultTokenScope
  609. instanceID:[OCMArg any]
  610. options:[OCMArg any]
  611. handler:[OCMArg checkWithBlock:^BOOL(id obj) {
  612. self.newTokenCompletion = obj;
  613. return obj != nil;
  614. }]];
  615. __block NSString *notificationToken = nil;
  616. NSString *notificationName = kFIRInstanceIDTokenRefreshNotification;
  617. self.tokenRefreshNotificationObserver = [[NSNotificationCenter defaultCenter]
  618. addObserverForName:notificationName
  619. object:nil
  620. queue:nil
  621. usingBlock:^(NSNotification *_Nonnull note) {
  622. // Should have saved token to cache
  623. cachedTokenInfo = sTokenInfo;
  624. notificationToken = [[self.instanceID token] copy];
  625. [defaultTokenExpectation fulfill];
  626. }];
  627. XCTAssertNil([self.mockInstanceID token]);
  628. [self waitForExpectationsWithTimeout:10.0 handler:nil];
  629. [[NSNotificationCenter defaultCenter] removeObserver:self.tokenRefreshNotificationObserver];
  630. XCTAssertEqualObjects(notificationToken, kToken);
  631. }
  632. /**
  633. * Tests that if we fail to fetch the token from the server for the first time we retry again
  634. * later with exponential backoff unless we succeed.
  635. */
  636. - (void)testDefaultTokenFetch_retryFetchToken {
  637. const int trialsBeforeSuccess = 3;
  638. __block int newTokenFetchCount = 0;
  639. __block int64_t lastFetchTimestampInSeconds;
  640. XCTestExpectation *defaultTokenExpectation =
  641. [self expectationWithDescription:@"Successfully got default token."];
  642. __block FIRInstanceIDTokenInfo *cachedTokenInfo = nil;
  643. [self stubInstallationsToReturnValidID];
  644. [self mockAuthServiceToAlwaysReturnValidCheckin];
  645. // Mock Token manager.
  646. // Return a dynamic cachedToken variable whenever the cached is checked.
  647. // This uses an invocation-based mock because the |cachedToken| pointer
  648. // will change. Normal stubbing will always return the initial pointer,
  649. // which in this case is 0x0 (nil).
  650. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  651. [invocation setReturnValue:&cachedTokenInfo];
  652. }] cachedTokenInfoWithAuthorizedEntity:kAuthorizedEntity scope:kFIRInstanceIDDefaultTokenScope];
  653. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  654. newTokenFetchCount++;
  655. int64_t delaySinceLastFetchInSeconds =
  656. FIRInstanceIDCurrentTimestampInSeconds() - lastFetchTimestampInSeconds;
  657. // Test exponential backoff.
  658. if (newTokenFetchCount > 1) {
  659. XCTAssertLessThanOrEqual(1 << (newTokenFetchCount - 1), delaySinceLastFetchInSeconds);
  660. }
  661. lastFetchTimestampInSeconds = FIRInstanceIDCurrentTimestampInSeconds();
  662. if (newTokenFetchCount < trialsBeforeSuccess) {
  663. NSError *error = [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeTimeout];
  664. self.newTokenCompletion(nil, error);
  665. } else {
  666. self.newTokenCompletion(kToken, nil);
  667. }
  668. }] fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity
  669. scope:kFIRInstanceIDDefaultTokenScope
  670. instanceID:[OCMArg any]
  671. options:[OCMArg any]
  672. handler:[OCMArg checkWithBlock:^BOOL(id obj) {
  673. self.newTokenCompletion = obj;
  674. return obj != nil;
  675. }]];
  676. __block int notificationPostCount = 0;
  677. __block NSString *notificationToken = nil;
  678. NSString *notificationName = kFIRInstanceIDTokenRefreshNotification;
  679. self.tokenRefreshNotificationObserver = [[NSNotificationCenter defaultCenter]
  680. addObserverForName:notificationName
  681. object:nil
  682. queue:nil
  683. usingBlock:^(NSNotification *_Nonnull note) {
  684. // Should have saved token to cache
  685. cachedTokenInfo = sTokenInfo;
  686. notificationPostCount++;
  687. notificationToken = [[self.instanceID token] copy];
  688. [defaultTokenExpectation fulfill];
  689. }];
  690. XCTAssertNil([self.mockInstanceID token]);
  691. [self waitForExpectationsWithTimeout:20.0 handler:nil];
  692. [[NSNotificationCenter defaultCenter] removeObserver:self.tokenRefreshNotificationObserver];
  693. XCTAssertEqualObjects(notificationToken, kToken);
  694. XCTAssertEqual(notificationPostCount, 1);
  695. XCTAssertEqual(newTokenFetchCount, trialsBeforeSuccess);
  696. }
  697. /**
  698. * Tests that when we don't have a cached default token multiple invocations to `getToken`
  699. * lead to a single networking call to fetch the token. Also verify that we post one unique
  700. * TokenRefresh notification for multiple invocations.
  701. */
  702. - (void)testDefaultToken_multipleInvocations {
  703. __block int newTokenFetchCount = 0;
  704. XCTestExpectation *defaultTokenExpectation =
  705. [self expectationWithDescription:@"Successfully got default token."];
  706. __block FIRInstanceIDTokenInfo *cachedTokenInfo = nil;
  707. [self stubInstallationsToReturnValidID];
  708. [self mockAuthServiceToAlwaysReturnValidCheckin];
  709. // Mock Token manager.
  710. // Return a dynamic cachedToken variable whenever the cached is checked.
  711. // This uses an invocation-based mock because the |cachedToken| pointer
  712. // will change. Normal stubbing will always return the initial pointer,
  713. // which in this case is 0x0 (nil).
  714. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  715. [invocation setReturnValue:&cachedTokenInfo];
  716. }] cachedTokenInfoWithAuthorizedEntity:kAuthorizedEntity scope:kFIRInstanceIDDefaultTokenScope];
  717. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  718. // Invoke callback after some delay (network delay)
  719. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)),
  720. dispatch_get_main_queue(), ^{
  721. self.newTokenCompletion(kToken, nil);
  722. });
  723. newTokenFetchCount++;
  724. XCTAssertEqual(newTokenFetchCount, 1);
  725. }] fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity
  726. scope:kFIRInstanceIDDefaultTokenScope
  727. instanceID:[OCMArg any]
  728. options:[OCMArg any]
  729. handler:[OCMArg checkWithBlock:^BOOL(id obj) {
  730. self.newTokenCompletion = obj;
  731. return obj != nil;
  732. }]];
  733. __block int notificationPostCount = 0;
  734. __block NSString *notificationToken = nil;
  735. NSString *notificationName = kFIRInstanceIDTokenRefreshNotification;
  736. self.tokenRefreshNotificationObserver = [[NSNotificationCenter defaultCenter]
  737. addObserverForName:notificationName
  738. object:nil
  739. queue:nil
  740. usingBlock:^(NSNotification *_Nonnull note) {
  741. // Should have saved token to cache
  742. cachedTokenInfo = sTokenInfo;
  743. notificationPostCount++;
  744. notificationToken = [[self.instanceID token] copy];
  745. [defaultTokenExpectation fulfill];
  746. }];
  747. XCTAssertNil([self.mockInstanceID token]);
  748. // Invoke get token again with some delay. Our initial request to getToken hasn't yet
  749. // returned from the server.
  750. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)),
  751. dispatch_get_main_queue(), ^{
  752. XCTAssertNil([self.mockInstanceID token]);
  753. });
  754. // Invoke again after further delay.
  755. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)),
  756. dispatch_get_main_queue(), ^{
  757. XCTAssertNil([self.mockInstanceID token]);
  758. });
  759. [self waitForExpectationsWithTimeout:15.0 handler:nil];
  760. [[NSNotificationCenter defaultCenter] removeObserver:self.tokenRefreshNotificationObserver];
  761. XCTAssertEqualObjects(notificationToken, kToken);
  762. XCTAssertEqual(notificationPostCount, 1);
  763. XCTAssertEqual(newTokenFetchCount, 1);
  764. }
  765. - (void)testDefaultToken_maxRetries {
  766. __block int newTokenFetchCount = 0;
  767. XCTestExpectation *defaultTokenExpectation =
  768. [self expectationWithDescription:@"Did retry maximum times to fetch default token."];
  769. [self stubInstallationsToReturnValidID];
  770. [self mockAuthServiceToAlwaysReturnValidCheckin];
  771. // Mock Token manager.
  772. [[[self.mockTokenManager stub] andReturn:nil]
  773. cachedTokenInfoWithAuthorizedEntity:kAuthorizedEntity
  774. scope:kFIRInstanceIDDefaultTokenScope];
  775. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  776. newTokenFetchCount++;
  777. NSError *error = [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeNetwork];
  778. self.newTokenCompletion(nil, error);
  779. if (newTokenFetchCount == [FIRInstanceID maxRetryCountForDefaultToken]) {
  780. [defaultTokenExpectation fulfill];
  781. }
  782. }] fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity
  783. scope:kFIRInstanceIDDefaultTokenScope
  784. instanceID:[OCMArg any]
  785. options:[OCMArg any]
  786. handler:[OCMArg checkWithBlock:^BOOL(id obj) {
  787. self.newTokenCompletion = obj;
  788. return obj != nil;
  789. }]];
  790. // Mock Instance ID's retry interval to 0, to vastly speed up this test.
  791. [[[self.mockInstanceID stub] andReturnValue:@(0)] retryIntervalToFetchDefaultToken];
  792. // Try to fetch token once. It should set off retries since we mock failure.
  793. XCTAssertNil([self.mockInstanceID token]);
  794. [self waitForExpectationsWithTimeout:1.0 handler:nil];
  795. XCTAssertEqual(newTokenFetchCount, [FIRInstanceID maxRetryCountForDefaultToken]);
  796. }
  797. - (void)testInstanceIDWithHandler_WhileRequesting_Success {
  798. [self stubInstallationsToReturnValidID];
  799. [self mockAuthServiceToAlwaysReturnValidCheckin];
  800. // Expect `fetchNewTokenWithAuthorizedEntity` to be called once
  801. XCTestExpectation *fetchNewTokenExpectation =
  802. [self expectationWithDescription:@"fetchNewTokenExpectation"];
  803. __block FIRInstanceIDTokenHandler tokenHandler;
  804. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  805. [invocation getArgument:&tokenHandler atIndex:6];
  806. [fetchNewTokenExpectation fulfill];
  807. }] fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity
  808. scope:kFIRInstanceIDDefaultTokenScope
  809. instanceID:[OCMArg any]
  810. options:[OCMArg any]
  811. handler:[OCMArg any]];
  812. // Make 1st call
  813. XCTestExpectation *handlerExpectation1 = [self expectationWithDescription:@"handlerExpectation1"];
  814. FIRInstanceIDResultHandler handler1 =
  815. ^(FIRInstanceIDResult *_Nullable result, NSError *_Nullable error) {
  816. [handlerExpectation1 fulfill];
  817. XCTAssertNotNil(result);
  818. XCTAssertEqual(result.token, kToken);
  819. XCTAssertNil(error);
  820. };
  821. [self.mockInstanceID instanceIDWithHandler:handler1];
  822. // Make 2nd call
  823. XCTestExpectation *handlerExpectation2 = [self expectationWithDescription:@"handlerExpectation1"];
  824. FIRInstanceIDResultHandler handler2 =
  825. ^(FIRInstanceIDResult *_Nullable result, NSError *_Nullable error) {
  826. [handlerExpectation2 fulfill];
  827. XCTAssertNotNil(result);
  828. XCTAssertEqual(result.token, kToken);
  829. XCTAssertNil(error);
  830. };
  831. [self.mockInstanceID instanceIDWithHandler:handler2];
  832. // Wait for `fetchNewTokenWithAuthorizedEntity` to be performed
  833. [self waitForExpectations:@[ fetchNewTokenExpectation ] timeout:1 enforceOrder:false];
  834. // Finish token fetch request
  835. tokenHandler(kToken, nil);
  836. // Wait for completion handlers for both calls to be performed
  837. [self waitForExpectationsWithTimeout:1 handler:NULL];
  838. }
  839. - (void)testInstanceIDWithHandler_WhileRequesting_RetrySuccess {
  840. [self stubInstallationsToReturnValidID];
  841. [self mockAuthServiceToAlwaysReturnValidCheckin];
  842. // Expect `fetchNewTokenWithAuthorizedEntity` to be called twice
  843. XCTestExpectation *fetchNewTokenExpectation1 =
  844. [self expectationWithDescription:@"fetchNewTokenExpectation1"];
  845. XCTestExpectation *fetchNewTokenExpectation2 =
  846. [self expectationWithDescription:@"fetchNewTokenExpectation2"];
  847. NSArray *fetchNewTokenExpectations = @[ fetchNewTokenExpectation1, fetchNewTokenExpectation2 ];
  848. __block NSInteger fetchNewTokenCallCount = 0;
  849. __block FIRInstanceIDTokenHandler tokenHandler;
  850. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  851. [invocation getArgument:&tokenHandler atIndex:6];
  852. [fetchNewTokenExpectations[fetchNewTokenCallCount] fulfill];
  853. fetchNewTokenCallCount += 1;
  854. }] fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity
  855. scope:kFIRInstanceIDDefaultTokenScope
  856. instanceID:[OCMArg any]
  857. options:[OCMArg any]
  858. handler:[OCMArg any]];
  859. // Mock Instance ID's retry interval to 0, to vastly speed up this test.
  860. [[[self.mockInstanceID stub] andReturnValue:@(0)] retryIntervalToFetchDefaultToken];
  861. // Make 1st call
  862. XCTestExpectation *handlerExpectation1 = [self expectationWithDescription:@"handlerExpectation1"];
  863. FIRInstanceIDResultHandler handler1 =
  864. ^(FIRInstanceIDResult *_Nullable result, NSError *_Nullable error) {
  865. [handlerExpectation1 fulfill];
  866. XCTAssertNotNil(result);
  867. XCTAssertEqual(result.token, kToken);
  868. XCTAssertNil(error);
  869. };
  870. [self.mockInstanceID instanceIDWithHandler:handler1];
  871. // Make 2nd call
  872. XCTestExpectation *handlerExpectation2 = [self expectationWithDescription:@"handlerExpectation1"];
  873. FIRInstanceIDResultHandler handler2 =
  874. ^(FIRInstanceIDResult *_Nullable result, NSError *_Nullable error) {
  875. [handlerExpectation2 fulfill];
  876. XCTAssertNotNil(result);
  877. XCTAssertEqual(result.token, kToken);
  878. XCTAssertNil(error);
  879. };
  880. [self.mockInstanceID instanceIDWithHandler:handler2];
  881. // Wait for the 1st `fetchNewTokenWithAuthorizedEntity` to be performed
  882. [self waitForExpectations:@[ fetchNewTokenExpectation1 ] timeout:1 enforceOrder:false];
  883. // Fail for the 1st time
  884. tokenHandler(nil, [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeUnknown]);
  885. // Wait for the 2nd token feth
  886. [self waitForExpectations:@[ fetchNewTokenExpectation2 ] timeout:1 enforceOrder:false];
  887. // Finish with success
  888. tokenHandler(kToken, nil);
  889. // Wait for completion handlers for both calls to be performed
  890. [self waitForExpectationsWithTimeout:1 handler:NULL];
  891. }
  892. - (void)testInstanceIDWithHandler_WhileRequesting_RetryFailure {
  893. [self stubInstallationsToReturnValidID];
  894. [self mockAuthServiceToAlwaysReturnValidCheckin];
  895. // Expect `fetchNewTokenWithAuthorizedEntity` to be called once
  896. NSMutableArray<XCTestExpectation *> *fetchNewTokenExpectations = [NSMutableArray array];
  897. for (NSInteger i = 0; i < [[self.instanceID class] maxRetryCountForDefaultToken]; ++i) {
  898. NSString *name = [NSString stringWithFormat:@"fetchNewTokenExpectation-%ld", (long)i];
  899. [fetchNewTokenExpectations addObject:[self expectationWithDescription:name]];
  900. }
  901. __block NSInteger fetchNewTokenCallCount = 0;
  902. __block FIRInstanceIDTokenHandler tokenHandler;
  903. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  904. [invocation getArgument:&tokenHandler atIndex:6];
  905. [fetchNewTokenExpectations[fetchNewTokenCallCount] fulfill];
  906. fetchNewTokenCallCount += 1;
  907. }] fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity
  908. scope:kFIRInstanceIDDefaultTokenScope
  909. instanceID:[OCMArg any]
  910. options:[OCMArg any]
  911. handler:[OCMArg any]];
  912. // Mock Instance ID's retry interval to 0, to vastly speed up this test.
  913. [[[self.mockInstanceID stub] andReturnValue:@(0)] retryIntervalToFetchDefaultToken];
  914. // Make 1st call
  915. XCTestExpectation *handlerExpectation1 = [self expectationWithDescription:@"handlerExpectation1"];
  916. FIRInstanceIDResultHandler handler1 =
  917. ^(FIRInstanceIDResult *_Nullable result, NSError *_Nullable error) {
  918. [handlerExpectation1 fulfill];
  919. XCTAssertNil(result);
  920. XCTAssertNotNil(error);
  921. };
  922. [self.mockInstanceID instanceIDWithHandler:handler1];
  923. // Make 2nd call
  924. XCTestExpectation *handlerExpectation2 = [self expectationWithDescription:@"handlerExpectation1"];
  925. FIRInstanceIDResultHandler handler2 =
  926. ^(FIRInstanceIDResult *_Nullable result, NSError *_Nullable error) {
  927. [handlerExpectation2 fulfill];
  928. XCTAssertNil(result);
  929. XCTAssertNotNil(error);
  930. };
  931. [self.mockInstanceID instanceIDWithHandler:handler2];
  932. for (NSInteger i = 0; i < [[self.instanceID class] maxRetryCountForDefaultToken]; ++i) {
  933. // Wait for the i `fetchNewTokenWithAuthorizedEntity` to be performed
  934. [self waitForExpectations:@[ fetchNewTokenExpectations[i] ] timeout:1 enforceOrder:false];
  935. // Fail for the i time
  936. tokenHandler(nil, [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeUnknown]);
  937. }
  938. // Wait for completion handlers for both calls to be performed
  939. [self waitForExpectationsWithTimeout:1 handler:NULL];
  940. }
  941. /**
  942. * Tests a Keychain read failure while we try to fetch a new InstanceID token. If the Keychain
  943. * read fails we won't be able to fetch the public key which is required while fetching a new
  944. * token. In such a case we should return KeyPair failure.
  945. */
  946. - (void)testNewTokenFetch_keyChainError {
  947. XCTestExpectation *tokenExpectation =
  948. [self expectationWithDescription:@"New token handler invoked."];
  949. [self mockAuthServiceToAlwaysReturnValidCheckin];
  950. // Simulate keypair fetch/generation failure.
  951. NSError *installationIDError = [NSError errorWithDomain:@"Test" code:-1 userInfo:nil];
  952. [self expectInstallationsInstallationIDWithFID:nil error:installationIDError];
  953. [[self.mockTokenManager reject] fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity
  954. scope:kScope
  955. instanceID:[OCMArg any]
  956. options:[OCMArg any]
  957. handler:[OCMArg any]];
  958. [self.instanceID tokenWithAuthorizedEntity:kAuthorizedEntity
  959. scope:kScope
  960. options:nil
  961. handler:^(NSString *token, NSError *error) {
  962. XCTAssertNil(token);
  963. XCTAssertNotNil(error);
  964. [tokenExpectation fulfill];
  965. }];
  966. [self waitForExpectationsWithTimeout:1 handler:nil];
  967. OCMVerifyAll(self.mockTokenManager);
  968. }
  969. /**
  970. * If a token fetch includes in its options an "apns_token" object, but not a "apns_sandbox" key,
  971. * ensure that an "apns_sandbox" key is added to the token options (via automatic detection).
  972. */
  973. - (void)testTokenFetchAPNSServerTypeIsIncludedIfAPNSTokenProvided {
  974. XCTestExpectation *apnsServerTypeExpectation =
  975. [self expectationWithDescription:@"apns_sandbox key was included in token options"];
  976. [self stubInstallationsToReturnValidID];
  977. [self mockAuthServiceToAlwaysReturnValidCheckin];
  978. NSData *apnsToken = [kFakeAPNSToken dataUsingEncoding:NSUTF8StringEncoding];
  979. // Option is purposefully missing the apns_sandbox key
  980. NSDictionary *tokenOptions = @{kFIRInstanceIDTokenOptionsAPNSKey : apnsToken};
  981. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  982. // Inspect
  983. NSDictionary *options;
  984. [invocation getArgument:&options atIndex:5];
  985. if (options[kFIRInstanceIDTokenOptionsAPNSIsSandboxKey] != nil) {
  986. [apnsServerTypeExpectation fulfill];
  987. }
  988. self.newTokenCompletion(kToken, nil);
  989. }] fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity
  990. scope:kScope
  991. instanceID:[OCMArg any]
  992. options:[OCMArg any]
  993. handler:[OCMArg checkWithBlock:^BOOL(id obj) {
  994. self.newTokenCompletion = obj;
  995. return obj != nil;
  996. }]];
  997. [self.instanceID tokenWithAuthorizedEntity:kAuthorizedEntity
  998. scope:kScope
  999. options:tokenOptions
  1000. handler:^(NSString *token, NSError *error){
  1001. }];
  1002. [self waitForExpectationsWithTimeout:60.0
  1003. handler:^(NSError *error) {
  1004. XCTAssertNil(error);
  1005. }];
  1006. }
  1007. /**
  1008. * Tests that if a token was fetched, but during the fetch the APNs data was set, that a new
  1009. * token is fetched to associate the APNs data, and is not returned from the cache.
  1010. */
  1011. - (void)testTokenFetch_ignoresCacheIfAPNSInfoDifferent {
  1012. XCTestExpectation *tokenRequestExpectation =
  1013. [self expectationWithDescription:@"Token was fetched from the network"];
  1014. // Initialize a token in the cache *WITHOUT* APNSInfo
  1015. // This token is |kToken|, but we will simulate that a fetch will return another token
  1016. NSString *oldCachedToken = kToken;
  1017. NSString *fetchedToken = @"abcd123_newtoken";
  1018. __block FIRInstanceIDTokenInfo *cachedTokenInfo =
  1019. [[FIRInstanceIDTokenInfo alloc] initWithAuthorizedEntity:kAuthorizedEntity
  1020. scope:kFIRInstanceIDDefaultTokenScope
  1021. token:oldCachedToken
  1022. appVersion:@"1.0"
  1023. firebaseAppID:@"firebaseAppID"];
  1024. [self stubInstallationsToReturnValidID];
  1025. [self mockAuthServiceToAlwaysReturnValidCheckin];
  1026. // During this test use the default scope ("*") to simulate the default token behavior.
  1027. // Return a dynamic cachedToken variable whenever the cached is checked.
  1028. // This uses an invocation-based mock because the |cachedToken| pointer
  1029. // will change. Normal stubbing will always return the initial pointer,
  1030. // which in this case is 0x0 (nil).
  1031. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  1032. [invocation setReturnValue:&cachedTokenInfo];
  1033. }] cachedTokenInfoWithAuthorizedEntity:kAuthorizedEntity scope:kFIRInstanceIDDefaultTokenScope];
  1034. // Mock the network request to return |fetchedToken|, so we can clearly see if the token is
  1035. // is different than what was cached.
  1036. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  1037. [tokenRequestExpectation fulfill];
  1038. self.newTokenCompletion(fetchedToken, nil);
  1039. }] fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity
  1040. scope:kFIRInstanceIDDefaultTokenScope
  1041. instanceID:[OCMArg any]
  1042. options:[OCMArg any]
  1043. handler:[OCMArg checkWithBlock:^BOOL(id obj) {
  1044. self.newTokenCompletion = obj;
  1045. return obj != nil;
  1046. }]];
  1047. // Begin request
  1048. // Token options has APNS data, which is not associated with the cached token
  1049. NSDictionary *tokenOptions = @{
  1050. kFIRInstanceIDTokenOptionsAPNSKey : [@"apns" dataUsingEncoding:NSUTF8StringEncoding],
  1051. kFIRInstanceIDTokenOptionsAPNSIsSandboxKey : @(NO)
  1052. };
  1053. [self.instanceID
  1054. tokenWithAuthorizedEntity:kAuthorizedEntity
  1055. scope:kFIRInstanceIDDefaultTokenScope
  1056. options:tokenOptions
  1057. handler:^(NSString *_Nullable token, NSError *_Nullable error) {
  1058. XCTAssertEqualObjects(token, fetchedToken);
  1059. }];
  1060. [self waitForExpectationsWithTimeout:0.5 handler:nil];
  1061. }
  1062. /**
  1063. * Tests that if there is a keychain failure while fetching the InstanceID of the token we should
  1064. * return nil for the identity.
  1065. */
  1066. - (void)testInstanceIDFetch_keyChainError {
  1067. XCTestExpectation *tokenExpectation =
  1068. [self expectationWithDescription:@"InstanceID fetch handler invoked."];
  1069. // Simulate keypair fetch/generation failure.
  1070. NSError *error = [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeInvalidKeyPair];
  1071. [self expectInstallationsInstallationIDWithFID:nil error:error];
  1072. [self.instanceID getIDWithHandler:^(NSString *_Nullable identity, NSError *_Nullable error) {
  1073. XCTAssertNil(identity);
  1074. XCTAssertNotNil(error);
  1075. [tokenExpectation fulfill];
  1076. }];
  1077. [self waitForExpectationsWithTimeout:1 handler:nil];
  1078. }
  1079. - (void)testInstanceIDDeleteSuccess {
  1080. XCTestExpectation *tokenExpectation =
  1081. [self expectationWithDescription:@"InstanceID deleteID handler invoked."];
  1082. NSString *instanceID = @"validID";
  1083. [self expectInstallationsInstallationIDWithFID:instanceID error:nil];
  1084. [self expectTokenManagerDeleteAllTokensWithIID:instanceID completeWithError:nil];
  1085. [self expectTokenManagerDeleteAllTokensLocallyWithError:nil];
  1086. [self expectInstallationsDeleteWithError:nil];
  1087. [self expectAuthServiceResetCheckinWithError:nil];
  1088. [self.instanceID deleteIDWithHandler:^(NSError *_Nullable error) {
  1089. XCTAssertNil(error);
  1090. [tokenExpectation fulfill];
  1091. }];
  1092. [self waitForExpectationsWithTimeout:1 handler:nil];
  1093. OCMVerifyAll(self.mockInstallations);
  1094. OCMVerifyAll(self.mockTokenManager);
  1095. }
  1096. - (void)testInstanceIDDelete_keyChainError {
  1097. XCTestExpectation *tokenExpectation =
  1098. [self expectationWithDescription:@"InstanceID deleteID handler invoked."];
  1099. NSString *instanceID = @"validID";
  1100. [self expectInstallationsInstallationIDWithFID:instanceID error:nil];
  1101. [self expectTokenManagerDeleteAllTokensWithIID:instanceID completeWithError:nil];
  1102. [self expectTokenManagerDeleteAllTokensLocallyWithError:nil];
  1103. [self expectAuthServiceResetCheckinWithError:nil];
  1104. // Simulate keychain fetch/generation failure.
  1105. NSError *error = [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeInvalidKeyPair];
  1106. [self expectInstallationsDeleteWithError:error];
  1107. [self.instanceID deleteIDWithHandler:^(NSError *_Nullable error) {
  1108. XCTAssertNotNil(error);
  1109. [tokenExpectation fulfill];
  1110. }];
  1111. [self waitForExpectationsWithTimeout:1 handler:nil];
  1112. OCMVerifyAll(self.mockInstallations);
  1113. OCMVerifyAll(self.mockTokenManager);
  1114. }
  1115. - (void)testRefreshDifferentTokenFromMessaging {
  1116. _instanceID.defaultFCMToken = kToken;
  1117. XCTAssertEqualObjects(_instanceID.defaultFCMToken, kToken);
  1118. NSString *newTokenFromMessaging = @"a_new_token_from_messaging";
  1119. FIRInstanceIDTokenInfo *cachedTokenInfo =
  1120. [[FIRInstanceIDTokenInfo alloc] initWithAuthorizedEntity:kAuthorizedEntity
  1121. scope:kFIRInstanceIDDefaultTokenScope
  1122. token:kToken
  1123. appVersion:@""
  1124. firebaseAppID:kGoogleAppID];
  1125. OCMStub([self.mockTokenManager
  1126. cachedTokenInfoWithAuthorizedEntity:kAuthorizedEntity
  1127. scope:kFIRInstanceIDDefaultTokenScope])
  1128. .andReturn(cachedTokenInfo);
  1129. OCMExpect([self.mockTokenManager saveDefaultToken:newTokenFromMessaging
  1130. withOptions:[OCMArg any]]);
  1131. [[NSNotificationCenter defaultCenter]
  1132. postNotificationName:kFIRInstanceIDMessagingUpdateTokenNotification
  1133. object:newTokenFromMessaging];
  1134. OCMVerifyAll(self.mockTokenManager);
  1135. XCTAssertEqualObjects(_instanceID.defaultFCMToken, newTokenFromMessaging);
  1136. }
  1137. - (void)testRefreshTheSameTokenFromMessaging {
  1138. _instanceID.defaultFCMToken = kToken;
  1139. XCTAssertEqualObjects(_instanceID.defaultFCMToken, kToken);
  1140. NSString *newTokenFromMessaging = kToken;
  1141. FIRInstanceIDTokenInfo *cachedTokenInfo =
  1142. [[FIRInstanceIDTokenInfo alloc] initWithAuthorizedEntity:kAuthorizedEntity
  1143. scope:kFIRInstanceIDDefaultTokenScope
  1144. token:kToken
  1145. appVersion:@""
  1146. firebaseAppID:kGoogleAppID];
  1147. OCMStub([self.mockTokenManager
  1148. cachedTokenInfoWithAuthorizedEntity:kAuthorizedEntity
  1149. scope:kFIRInstanceIDDefaultTokenScope])
  1150. .andReturn(cachedTokenInfo);
  1151. OCMReject([self.mockTokenManager saveDefaultToken:newTokenFromMessaging
  1152. withOptions:[OCMArg any]]);
  1153. [[NSNotificationCenter defaultCenter]
  1154. postNotificationName:kFIRInstanceIDMessagingUpdateTokenNotification
  1155. object:newTokenFromMessaging];
  1156. OCMVerifyAll(self.mockTokenManager);
  1157. XCTAssertEqualObjects(_instanceID.defaultFCMToken, newTokenFromMessaging);
  1158. }
  1159. - (void)testRefreshDifferentTokenInInstanceIDStorage {
  1160. _instanceID.defaultFCMToken = kToken;
  1161. XCTAssertEqualObjects(_instanceID.defaultFCMToken, kToken);
  1162. // New token from messaging is the same as local cache in InstanceID
  1163. // But the token in InstanceID storage is different
  1164. NSString *newTokenFromMessaging = kToken;
  1165. FIRInstanceIDTokenInfo *cachedTokenInfo =
  1166. [[FIRInstanceIDTokenInfo alloc] initWithAuthorizedEntity:kAuthorizedEntity
  1167. scope:kFIRInstanceIDDefaultTokenScope
  1168. token:@"a_outdated_token_in_storage"
  1169. appVersion:@""
  1170. firebaseAppID:kGoogleAppID];
  1171. OCMStub([self.mockTokenManager
  1172. cachedTokenInfoWithAuthorizedEntity:kAuthorizedEntity
  1173. scope:kFIRInstanceIDDefaultTokenScope])
  1174. .andReturn(cachedTokenInfo);
  1175. OCMExpect([self.mockTokenManager saveDefaultToken:newTokenFromMessaging
  1176. withOptions:[OCMArg any]]);
  1177. [[NSNotificationCenter defaultCenter]
  1178. postNotificationName:kFIRInstanceIDMessagingUpdateTokenNotification
  1179. object:newTokenFromMessaging];
  1180. OCMVerifyAll(self.mockTokenManager);
  1181. XCTAssertEqualObjects(_instanceID.defaultFCMToken, newTokenFromMessaging);
  1182. }
  1183. - (void)testRefreshNullTokenFromMessaging {
  1184. _instanceID.defaultFCMToken = kToken;
  1185. XCTAssertEqualObjects(_instanceID.defaultFCMToken, kToken);
  1186. // New token from messaging is the same as local cache in InstanceID
  1187. // But the token in InstanceID storage is different
  1188. NSString *newTokenFromMessaging = nil;
  1189. FIRInstanceIDTokenInfo *cachedTokenInfo =
  1190. [[FIRInstanceIDTokenInfo alloc] initWithAuthorizedEntity:kAuthorizedEntity
  1191. scope:kFIRInstanceIDDefaultTokenScope
  1192. token:kToken
  1193. appVersion:@""
  1194. firebaseAppID:kGoogleAppID];
  1195. OCMStub([self.mockTokenManager
  1196. cachedTokenInfoWithAuthorizedEntity:kAuthorizedEntity
  1197. scope:kFIRInstanceIDDefaultTokenScope])
  1198. .andReturn(cachedTokenInfo);
  1199. OCMExpect([self.mockTokenManager saveDefaultToken:newTokenFromMessaging
  1200. withOptions:[OCMArg any]]);
  1201. [[NSNotificationCenter defaultCenter]
  1202. postNotificationName:kFIRInstanceIDMessagingUpdateTokenNotification
  1203. object:newTokenFromMessaging];
  1204. OCMVerifyAll(self.mockTokenManager);
  1205. XCTAssertEqualObjects(_instanceID.defaultFCMToken, newTokenFromMessaging);
  1206. }
  1207. #pragma mark - Private Helpers
  1208. - (void)stubInstallationsToReturnValidID {
  1209. OCMStub([self.mockInstallations
  1210. installationIDWithCompletion:[OCMArg
  1211. checkWithBlock:^BOOL(FIRInstallationsIDHandler completion) {
  1212. completion(@"validID", nil);
  1213. return YES;
  1214. }]]);
  1215. }
  1216. - (FIRInstanceIDCheckinPreferences *)validCheckinPreferences {
  1217. NSDictionary *gservicesData = @{
  1218. kFIRInstanceIDVersionInfoStringKey : kVersionInfo,
  1219. kFIRInstanceIDLastCheckinTimeKey : @(FIRInstanceIDCurrentTimestampInMilliseconds())
  1220. };
  1221. FIRInstanceIDCheckinPreferences *checkinPreferences =
  1222. [[FIRInstanceIDCheckinPreferences alloc] initWithDeviceID:kDeviceAuthId
  1223. secretToken:kSecretToken];
  1224. [checkinPreferences updateWithCheckinPlistContents:gservicesData];
  1225. return checkinPreferences;
  1226. }
  1227. - (void)mockAuthServiceToAlwaysReturnValidCheckin {
  1228. FIRInstanceIDCheckinPreferences *validCheckin = [self validCheckinPreferences];
  1229. __block FIRInstanceIDDeviceCheckinCompletion checkinHandler;
  1230. [[[self.mockAuthService stub] andDo:^(NSInvocation *invocation) {
  1231. if (checkinHandler) {
  1232. checkinHandler(validCheckin, nil);
  1233. }
  1234. }] fetchCheckinInfoWithHandler:[OCMArg checkWithBlock:^BOOL(id obj) {
  1235. return (checkinHandler = obj) != nil;
  1236. }]];
  1237. }
  1238. - (void)expectInstallationsInstallationIDWithFID:(nullable NSString *)FID
  1239. error:(nullable NSError *)error {
  1240. OCMExpect([self.mockInstallations
  1241. installationIDWithCompletion:[OCMArg
  1242. checkWithBlock:^BOOL(FIRInstallationsIDHandler completion) {
  1243. completion(FID, error);
  1244. return YES;
  1245. }]]);
  1246. }
  1247. - (void)expectInstallationsDeleteWithError:(nullable NSError *)deletionError {
  1248. OCMExpect([self.mockInstallations
  1249. deleteWithCompletion:[self errorCompletionOCMArgCompletingWithError:deletionError]]);
  1250. }
  1251. - (void)expectTokenManagerDeleteAllTokensWithIID:(NSString *)identifier
  1252. completeWithError:(nullable NSError *)error {
  1253. OCMExpect([self.mockTokenManager
  1254. deleteAllTokensWithInstanceID:identifier
  1255. handler:[self errorCompletionOCMArgCompletingWithError:error]]);
  1256. }
  1257. - (void)expectTokenManagerDeleteAllTokensLocallyWithError:(nullable NSError *)error {
  1258. OCMExpect([self.mockTokenManager
  1259. deleteAllTokensLocallyWithHandler:[self errorCompletionOCMArgCompletingWithError:error]]);
  1260. }
  1261. - (void)expectAuthServiceResetCheckinWithError:(NSError *)error {
  1262. OCMStub([self.mockAuthService
  1263. resetCheckinWithHandler:[self errorCompletionOCMArgCompletingWithError:error]]);
  1264. }
  1265. - (id)errorCompletionOCMArgCompletingWithError:(NSError *)errorToComplete {
  1266. return [OCMArg checkWithBlock:^BOOL(void (^completion)(NSError *)) {
  1267. completion(errorToComplete);
  1268. return YES;
  1269. }];
  1270. }
  1271. #pragma clang diagnostic pop
  1272. @end