FIRInstanceIDTokenManagerTest.m 25 KB

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