FIRInstanceIDTokenManagerTest.m 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544
  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 <OCMock/OCMock.h>
  18. #import "FIRInstanceIDFakeKeychain.h"
  19. #import "FIRInstanceIDTokenManager+Test.h"
  20. #import "Firebase/InstanceID/FIRInstanceIDBackupExcludedPlist.h"
  21. #import "Firebase/InstanceID/FIRInstanceIDCheckinPreferences+Internal.h"
  22. #import "Firebase/InstanceID/FIRInstanceIDCheckinStore.h"
  23. #import "Firebase/InstanceID/FIRInstanceIDStore.h"
  24. #import "Firebase/InstanceID/FIRInstanceIDTokenDeleteOperation.h"
  25. #import "Firebase/InstanceID/FIRInstanceIDTokenFetchOperation.h"
  26. #import "Firebase/InstanceID/FIRInstanceIDTokenInfo.h"
  27. #import "Firebase/InstanceID/FIRInstanceIDTokenManager.h"
  28. #import "Firebase/InstanceID/FIRInstanceIDTokenOperation.h"
  29. #import "Firebase/InstanceID/FIRInstanceIDTokenStore.h"
  30. static NSString *const kSubDirectoryName = @"FirebaseInstanceIDTokenManagerTest";
  31. static NSString *const kAuthorizedEntity = @"test-authorized-entity";
  32. static NSString *const kScope = @"test-scope";
  33. static NSString *const kToken = @"test-token";
  34. // Use a string (which is converted to NSData) as a placeholder for an actual APNs device token.
  35. static NSString *const kNewAPNSTokenString = @"newAPNSData";
  36. @interface FIRInstanceIDTokenOperation ()
  37. - (void)performTokenOperation;
  38. - (void)finishWithResult:(FIRInstanceIDTokenOperationResult)result
  39. token:(nullable NSString *)token
  40. error:(nullable NSError *)error;
  41. @end
  42. @interface FIRInstanceIDTokenManager (ExposedForTests)
  43. - (BOOL)checkForTokenRefreshPolicy;
  44. - (void)updateToAPNSDeviceToken:(NSData *)deviceToken isSandbox:(BOOL)isSandbox;
  45. /**
  46. * Create a fetch operation. This method can be stubbed to return a particular operation instance,
  47. * which makes it easier to unit test different behaviors.
  48. */
  49. - (FIRInstanceIDTokenFetchOperation *)
  50. createFetchOperationWithAuthorizedEntity:(NSString *)authorizedEntity
  51. scope:(NSString *)scope
  52. options:(NSDictionary<NSString *, NSString *> *)options
  53. keyPair:(FIRInstanceIDKeyPair *)keyPair;
  54. /**
  55. * Create a delete operation. This method can be stubbed to return a particular operation instance,
  56. * which makes it easier to unit test different behaviors.
  57. */
  58. - (FIRInstanceIDTokenDeleteOperation *)
  59. createDeleteOperationWithAuthorizedEntity:(NSString *)authorizedEntity
  60. scope:(NSString *)scope
  61. checkinPreferences:(FIRInstanceIDCheckinPreferences *)checkinPreferences
  62. keyPair:(FIRInstanceIDKeyPair *)keyPair
  63. action:(FIRInstanceIDTokenAction)action;
  64. @end
  65. @interface FIRInstanceIDTokenManagerTest : XCTestCase
  66. @property(nonatomic, readwrite, strong) FIRInstanceIDTokenManager *tokenManager;
  67. @property(nonatomic, readwrite, strong) id mockTokenManager;
  68. @property(nonatomic, readwrite, strong) FIRInstanceIDBackupExcludedPlist *checkinPlist;
  69. @property(nonatomic, readwrite, strong) FIRInstanceIDFakeKeychain *fakeKeyChain;
  70. @property(nonatomic, readwrite, strong) FIRInstanceIDTokenStore *tokenStore;
  71. @property(nonatomic, readwrite, strong) FIRInstanceIDCheckinPreferences *fakeCheckin;
  72. @end
  73. @implementation FIRInstanceIDTokenManagerTest
  74. - (void)setUp {
  75. [super setUp];
  76. [FIRInstanceIDStore createSubDirectory:kSubDirectoryName];
  77. NSString *checkinPlistFilename = @"com.google.test.IIDCheckinTest";
  78. self.checkinPlist =
  79. [[FIRInstanceIDBackupExcludedPlist alloc] initWithFileName:checkinPlistFilename
  80. subDirectory:kSubDirectoryName];
  81. // checkin store
  82. FIRInstanceIDFakeKeychain *fakeCheckinKeychain = [[FIRInstanceIDFakeKeychain alloc] init];
  83. FIRInstanceIDCheckinStore *checkinStore =
  84. [[FIRInstanceIDCheckinStore alloc] initWithCheckinPlist:self.checkinPlist
  85. keychain:fakeCheckinKeychain];
  86. // token store
  87. self.fakeKeyChain = [[FIRInstanceIDFakeKeychain alloc] init];
  88. self.tokenStore = [[FIRInstanceIDTokenStore alloc] initWithKeychain:_fakeKeyChain];
  89. self.tokenManager = [[FIRInstanceIDTokenManager alloc] initWithCheckinStore:checkinStore
  90. tokenStore:self.tokenStore];
  91. self.mockTokenManager = OCMPartialMock(self.tokenManager);
  92. self.fakeCheckin = [[FIRInstanceIDCheckinPreferences alloc] initWithDeviceID:@"fakeDeviceID"
  93. secretToken:@"fakeSecretToken"];
  94. }
  95. - (void)tearDown {
  96. self.fakeCheckin = nil;
  97. [self.mockTokenManager stopMocking];
  98. self.mockTokenManager = nil;
  99. self.tokenManager = nil;
  100. self.tokenStore = nil;
  101. self.fakeKeyChain = nil;
  102. NSError *error;
  103. if (![self.checkinPlist deleteFile:&error]) {
  104. XCTFail(@"Failed to delete checkin plist %@", error);
  105. }
  106. self.checkinPlist = nil;
  107. [FIRInstanceIDStore removeSubDirectory:kSubDirectoryName error:nil];
  108. [super tearDown];
  109. }
  110. /**
  111. * Tests that when a new InstanceID token is successfully produced,
  112. * the callback is invoked with a token that is not an empty string and with no error.
  113. */
  114. - (void)testNewTokenSuccess {
  115. XCTestExpectation *tokenExpectation =
  116. [self expectationWithDescription:@"New token handler invoked."];
  117. NSDictionary *tokenOptions = [NSDictionary dictionary];
  118. // Create a fake operation that always returns success
  119. FIRInstanceIDTokenFetchOperation *operation =
  120. [[FIRInstanceIDTokenFetchOperation alloc] initWithAuthorizedEntity:kAuthorizedEntity
  121. scope:kScope
  122. options:tokenOptions
  123. checkinPreferences:self.fakeCheckin
  124. keyPair:[OCMArg any]];
  125. id mockOperation = OCMPartialMock(operation);
  126. [[[mockOperation stub] andDo:^(NSInvocation *invocation) {
  127. [invocation.target finishWithResult:FIRInstanceIDTokenOperationSucceeded
  128. token:kToken
  129. error:nil];
  130. }] performTokenOperation];
  131. XCTestExpectation *operationFinishExpectation =
  132. [self expectationWithDescription:@"operationFinishExpectation"];
  133. operation.completionBlock = ^{
  134. [operationFinishExpectation fulfill];
  135. };
  136. // Return our fake operation when asked for an operation
  137. [[[self.mockTokenManager stub] andReturn:operation]
  138. createFetchOperationWithAuthorizedEntity:[OCMArg any]
  139. scope:[OCMArg any]
  140. options:[OCMArg any]
  141. keyPair:[OCMArg any]];
  142. [self.tokenManager fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity
  143. scope:kScope
  144. keyPair:[OCMArg any]
  145. options:tokenOptions
  146. handler:^(NSString *token, NSError *error) {
  147. XCTAssertNotNil(token);
  148. XCTAssertGreaterThan(token.length, 0);
  149. XCTAssertNil(error);
  150. [tokenExpectation fulfill];
  151. }];
  152. [self waitForExpectations:@[ tokenExpectation, operationFinishExpectation ] timeout:1];
  153. // Make sure the partial mock stops mocking before `operation` is deallocated to avoid crash.
  154. [mockOperation stopMocking];
  155. // Keep 'operation' alive, so it's not prematurely destroyed
  156. XCTAssertNotNil(operation);
  157. }
  158. /**
  159. * Tests that when a new InstanceID token is fetched from the server but unsuccessfully
  160. * saved on the client we should return an error instead of the fetched token.
  161. */
  162. - (void)testNewTokenSaveFailure {
  163. XCTestExpectation *tokenExpectation =
  164. [self expectationWithDescription:@"New token handler invoked."];
  165. NSDictionary *tokenOptions = [NSDictionary dictionary];
  166. // Simulate write to keychain failure.
  167. self.fakeKeyChain.cannotWriteToKeychain = YES;
  168. // Create a fake operation that always returns success
  169. FIRInstanceIDTokenFetchOperation *operation =
  170. [[FIRInstanceIDTokenFetchOperation alloc] initWithAuthorizedEntity:kAuthorizedEntity
  171. scope:kScope
  172. options:tokenOptions
  173. checkinPreferences:self.fakeCheckin
  174. keyPair:[OCMArg any]];
  175. id mockOperation = OCMPartialMock(operation);
  176. [[[mockOperation stub] andDo:^(NSInvocation *invocation) {
  177. [invocation.target finishWithResult:FIRInstanceIDTokenOperationSucceeded
  178. token:kToken
  179. error:nil];
  180. }] performTokenOperation];
  181. XCTestExpectation *operationFinishExpectation =
  182. [self expectationWithDescription:@"operationFinishExpectation"];
  183. operation.completionBlock = ^{
  184. [operationFinishExpectation fulfill];
  185. };
  186. // Return our fake operation when asked for an operation
  187. [[[self.mockTokenManager stub] andReturn:operation]
  188. createFetchOperationWithAuthorizedEntity:[OCMArg any]
  189. scope:[OCMArg any]
  190. options:[OCMArg any]
  191. keyPair:[OCMArg any]];
  192. [self.tokenManager fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity
  193. scope:kScope
  194. keyPair:[OCMArg any]
  195. options:tokenOptions
  196. handler:^(NSString *token, NSError *error) {
  197. XCTAssertNil(token);
  198. XCTAssertNotNil(error);
  199. [tokenExpectation fulfill];
  200. }];
  201. [self waitForExpectations:@[ tokenExpectation, operationFinishExpectation ] timeout:1];
  202. // Make sure the partial mock stops mocking before `operation` is deallocated to avoid crash.
  203. [mockOperation stopMocking];
  204. // Keep 'operation' alive, so it's not prematurely destroyed
  205. XCTAssertNotNil(operation);
  206. }
  207. /**
  208. * Tests that when there is a failure in producing a new InstanceID token,
  209. * the callback is invoked with an error and a nil token.
  210. */
  211. - (void)testNewTokenFailure {
  212. XCTestExpectation *tokenExpectation =
  213. [self expectationWithDescription:@"New token handler invoked."];
  214. NSDictionary *tokenOptions = [NSDictionary dictionary];
  215. // Create a fake operation that always returns failure
  216. FIRInstanceIDTokenFetchOperation *operation =
  217. [[FIRInstanceIDTokenFetchOperation alloc] initWithAuthorizedEntity:kAuthorizedEntity
  218. scope:kScope
  219. options:tokenOptions
  220. checkinPreferences:self.fakeCheckin
  221. keyPair:[OCMArg any]];
  222. id mockOperation = OCMPartialMock(operation);
  223. [[[mockOperation stub] andDo:^(NSInvocation *invocation) {
  224. NSError *someError = [[NSError alloc] initWithDomain:@"InstanceIDUnitTest" code:0 userInfo:nil];
  225. [invocation.target finishWithResult:FIRInstanceIDTokenOperationError token:nil error:someError];
  226. }] performTokenOperation];
  227. XCTestExpectation *operationFinishExpectation =
  228. [self expectationWithDescription:@"operationFinishExpectation"];
  229. operation.completionBlock = ^{
  230. [operationFinishExpectation fulfill];
  231. };
  232. // Return our fake operation when asked for an operation
  233. [[[self.mockTokenManager stub] andReturn:operation]
  234. createFetchOperationWithAuthorizedEntity:[OCMArg any]
  235. scope:[OCMArg any]
  236. options:[OCMArg any]
  237. keyPair:[OCMArg any]];
  238. [self.tokenManager fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity
  239. scope:kScope
  240. keyPair:[OCMArg any]
  241. options:tokenOptions
  242. handler:^(NSString *token, NSError *error) {
  243. XCTAssertNil(token);
  244. XCTAssertNotNil(error);
  245. [tokenExpectation fulfill];
  246. }];
  247. [self waitForExpectations:@[ tokenExpectation, operationFinishExpectation ] timeout:1];
  248. // Make sure the partial mock stops mocking before `operation` is deallocated to avoid crash.
  249. [mockOperation stopMocking];
  250. // Keep 'operation' alive, so it's not prematurely destroyed
  251. XCTAssertNotNil(operation);
  252. }
  253. /**
  254. * Tests that when a token is deleted successfully, the callback is invoked with no error.
  255. */
  256. - (void)testDeleteTokenSuccess {
  257. XCTestExpectation *deleteExpectation =
  258. [self expectationWithDescription:@"Delete handler invoked."];
  259. // Create a fake operation that always succeeds
  260. FIRInstanceIDTokenDeleteOperation *operation = [[FIRInstanceIDTokenDeleteOperation alloc]
  261. initWithAuthorizedEntity:kAuthorizedEntity
  262. scope:kScope
  263. checkinPreferences:self.fakeCheckin
  264. keyPair:[OCMArg any]
  265. action:FIRInstanceIDTokenActionDeleteToken];
  266. id mockOperation = OCMPartialMock(operation);
  267. [[[mockOperation stub] andDo:^(NSInvocation *invocation) {
  268. [invocation.target finishWithResult:FIRInstanceIDTokenOperationSucceeded token:nil error:nil];
  269. }] performTokenOperation];
  270. XCTestExpectation *operationFinishExpectation =
  271. [self expectationWithDescription:@"operationFinishExpectation"];
  272. operation.completionBlock = ^{
  273. [operationFinishExpectation fulfill];
  274. };
  275. // Return our fake operation when asked for an operation
  276. [[[self.mockTokenManager stub] andReturn:operation]
  277. createDeleteOperationWithAuthorizedEntity:[OCMArg any]
  278. scope:[OCMArg any]
  279. checkinPreferences:[OCMArg any]
  280. keyPair:[OCMArg any]
  281. action:FIRInstanceIDTokenActionDeleteToken];
  282. [self.tokenManager deleteTokenWithAuthorizedEntity:kAuthorizedEntity
  283. scope:kScope
  284. keyPair:[OCMArg any]
  285. handler:^(NSError *error) {
  286. XCTAssertNil(error);
  287. [deleteExpectation fulfill];
  288. }];
  289. [self waitForExpectations:@[ deleteExpectation, operationFinishExpectation ] timeout:1];
  290. // Make sure the partial mock stops mocking before `operation` is deallocated to avoid crash.
  291. [mockOperation stopMocking];
  292. // Keep 'operation' alive, so it's not prematurely destroyed
  293. XCTAssertNotNil(operation);
  294. }
  295. /**
  296. * Tests that when a token deletion fails, the callback is invoked with an error.
  297. */
  298. - (void)testDeleteTokenFailure {
  299. XCTestExpectation *deleteExpectation =
  300. [self expectationWithDescription:@"Delete handler invoked."];
  301. // Create a fake operation that always fails
  302. FIRInstanceIDTokenDeleteOperation *operation = [[FIRInstanceIDTokenDeleteOperation alloc]
  303. initWithAuthorizedEntity:kAuthorizedEntity
  304. scope:kScope
  305. checkinPreferences:self.fakeCheckin
  306. keyPair:[OCMArg any]
  307. action:FIRInstanceIDTokenActionDeleteToken];
  308. id mockOperation = OCMPartialMock(operation);
  309. [[[mockOperation stub] andDo:^(NSInvocation *invocation) {
  310. NSError *someError = [[NSError alloc] initWithDomain:@"InstanceIDUnitTest" code:0 userInfo:nil];
  311. [invocation.target finishWithResult:FIRInstanceIDTokenOperationError token:nil error:someError];
  312. }] performTokenOperation];
  313. XCTestExpectation *operationFinishExpectation =
  314. [self expectationWithDescription:@"operationFinishExpectation"];
  315. operation.completionBlock = ^{
  316. [operationFinishExpectation fulfill];
  317. };
  318. // Return our fake operation when asked for an operation
  319. [[[self.mockTokenManager stub] andReturn:operation]
  320. createDeleteOperationWithAuthorizedEntity:[OCMArg any]
  321. scope:[OCMArg any]
  322. checkinPreferences:[OCMArg any]
  323. keyPair:[OCMArg any]
  324. action:FIRInstanceIDTokenActionDeleteToken];
  325. [self.tokenManager deleteTokenWithAuthorizedEntity:kAuthorizedEntity
  326. scope:kScope
  327. keyPair:[OCMArg any]
  328. handler:^(NSError *error) {
  329. XCTAssertNotNil(error);
  330. [deleteExpectation fulfill];
  331. }];
  332. [self waitForExpectations:@[ deleteExpectation, operationFinishExpectation ] timeout:1];
  333. // Make sure the partial mock stops mocking before `operation` is deallocated to avoid crash.
  334. [mockOperation stopMocking];
  335. // Keep 'operation' alive, so it's not prematurely destroyed
  336. XCTAssertNotNil(operation);
  337. }
  338. #pragma mark - Cached Token Invalidation
  339. - (void)testCachedTokensInvalidatedOnAppVersionChange {
  340. // Write some fake tokens to cache with a old app version "0.9"
  341. NSArray<NSString *> *entities = @[ @"entity1", @"entity2" ];
  342. for (NSString *entity in entities) {
  343. FIRInstanceIDTokenInfo *info =
  344. [[FIRInstanceIDTokenInfo alloc] initWithAuthorizedEntity:entity
  345. scope:kScope
  346. token:@"abcdef"
  347. appVersion:@"0.9"
  348. firebaseAppID:nil];
  349. [self.tokenStore saveTokenInfo:info handler:nil];
  350. }
  351. // Ensure they tokens now exist.
  352. for (NSString *entity in entities) {
  353. FIRInstanceIDTokenInfo *cachedTokenInfo =
  354. [self.tokenManager cachedTokenInfoWithAuthorizedEntity:entity scope:kScope];
  355. XCTAssertNotNil(cachedTokenInfo);
  356. }
  357. // Trigger a potential reset, the current app version is 1.0 which is newer than
  358. // the one set in tokenInfo.
  359. [self.tokenManager checkForTokenRefreshPolicy];
  360. // Ensure that token data is now missing
  361. for (NSString *entity in entities) {
  362. FIRInstanceIDTokenInfo *cachedTokenInfo =
  363. [self.tokenManager cachedTokenInfoWithAuthorizedEntity:entity scope:kScope];
  364. XCTAssertNil(cachedTokenInfo);
  365. }
  366. }
  367. - (void)testCachedTokensInvalidatedOnAPNSAddition {
  368. // Write some fake tokens to cache, which have no APNs info
  369. NSArray<NSString *> *entities = @[ @"entity1", @"entity2" ];
  370. for (NSString *entity in entities) {
  371. FIRInstanceIDTokenInfo *info =
  372. [[FIRInstanceIDTokenInfo alloc] initWithAuthorizedEntity:entity
  373. scope:kScope
  374. token:@"abcdef"
  375. appVersion:nil
  376. firebaseAppID:nil];
  377. [self.tokenStore saveTokenInfo:info handler:nil];
  378. }
  379. // Ensure the tokens now exist.
  380. for (NSString *entity in entities) {
  381. FIRInstanceIDTokenInfo *cachedTokenInfo =
  382. [self.tokenManager cachedTokenInfoWithAuthorizedEntity:entity scope:kScope];
  383. XCTAssertNotNil(cachedTokenInfo);
  384. }
  385. // Trigger a potential reset.
  386. [self triggerAPNSTokenChange];
  387. // Ensure that token data is now missing
  388. for (NSString *entity in entities) {
  389. FIRInstanceIDTokenInfo *cachedTokenInfo =
  390. [self.tokenManager cachedTokenInfoWithAuthorizedEntity:entity scope:kScope];
  391. XCTAssertNil(cachedTokenInfo);
  392. }
  393. }
  394. - (void)testCachedTokensInvalidatedOnAPNSChange {
  395. // Write some fake tokens to cache
  396. NSArray<NSString *> *entities = @[ @"entity1", @"entity2" ];
  397. NSData *oldAPNSData = [@"oldAPNSToken" dataUsingEncoding:NSUTF8StringEncoding];
  398. for (NSString *entity in entities) {
  399. FIRInstanceIDTokenInfo *info =
  400. [[FIRInstanceIDTokenInfo alloc] initWithAuthorizedEntity:entity
  401. scope:kScope
  402. token:@"abcdef"
  403. appVersion:nil
  404. firebaseAppID:nil];
  405. info.APNSInfo = [[FIRInstanceIDAPNSInfo alloc] initWithDeviceToken:oldAPNSData isSandbox:NO];
  406. [self.tokenStore saveTokenInfo:info handler:nil];
  407. }
  408. // Ensure the tokens now exist.
  409. for (NSString *entity in entities) {
  410. FIRInstanceIDTokenInfo *cachedTokenInfo =
  411. [self.tokenManager cachedTokenInfoWithAuthorizedEntity:entity scope:kScope];
  412. XCTAssertNotNil(cachedTokenInfo);
  413. }
  414. // Trigger a potential reset.
  415. [self triggerAPNSTokenChange];
  416. // Ensure that token data is now missing
  417. for (NSString *entity in entities) {
  418. FIRInstanceIDTokenInfo *cachedTokenInfo =
  419. [self.tokenManager cachedTokenInfoWithAuthorizedEntity:entity scope:kScope];
  420. XCTAssertNil(cachedTokenInfo);
  421. }
  422. }
  423. - (void)testCachedTokensNotInvalidatedIfAPNSSame {
  424. // Write some fake tokens to cache, with the current APNs token
  425. NSArray<NSString *> *entities = @[ @"entity1", @"entity2" ];
  426. NSString *apnsDataString = kNewAPNSTokenString;
  427. NSData *currentAPNSData = [apnsDataString dataUsingEncoding:NSUTF8StringEncoding];
  428. for (NSString *entity in entities) {
  429. FIRInstanceIDTokenInfo *info =
  430. [[FIRInstanceIDTokenInfo alloc] initWithAuthorizedEntity:entity
  431. scope:kScope
  432. token:@"abcdef"
  433. appVersion:nil
  434. firebaseAppID:nil];
  435. info.APNSInfo = [[FIRInstanceIDAPNSInfo alloc] initWithDeviceToken:currentAPNSData
  436. isSandbox:NO];
  437. [self.tokenStore saveTokenInfo:info handler:nil];
  438. }
  439. // Ensure the tokens now exist.
  440. for (NSString *entity in entities) {
  441. FIRInstanceIDTokenInfo *cachedTokenInfo =
  442. [self.tokenManager cachedTokenInfoWithAuthorizedEntity:entity scope:kScope];
  443. XCTAssertNotNil(cachedTokenInfo);
  444. }
  445. // Trigger a potential reset.
  446. [self triggerAPNSTokenChange];
  447. // Ensure that token data is still there
  448. for (NSString *entity in entities) {
  449. FIRInstanceIDTokenInfo *cachedTokenInfo =
  450. [self.tokenManager cachedTokenInfoWithAuthorizedEntity:entity scope:kScope];
  451. XCTAssertNotNil(cachedTokenInfo);
  452. }
  453. }
  454. - (void)triggerAPNSTokenChange {
  455. // Trigger a potential reset.
  456. NSData *deviceToken = [kNewAPNSTokenString dataUsingEncoding:NSUTF8StringEncoding];
  457. [self.tokenManager updateTokensToAPNSDeviceToken:deviceToken isSandbox:NO];
  458. }
  459. @end