FIRInstanceIDTokenManagerTest.m 26 KB

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