FIRInstanceIDTest.m 53 KB

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