FIRInstanceIDTest.m 62 KB

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