FIRInstanceIDTest.m 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206
  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. NSString *token = [self.mockInstanceID token];
  458. XCTAssertNil(token);
  459. [self.mockInstanceID stopMocking];
  460. OCMVerify([self.mockInstanceID defaultTokenWithHandler:nil]);
  461. }
  462. /**
  463. * Tests that when we have a cached default token, calling `getToken` returns that token
  464. * without hitting the network.
  465. */
  466. - (void)testDefaultToken_validCachedToken {
  467. [[[self.mockTokenManager stub] andReturn:sTokenInfo]
  468. cachedTokenInfoWithAuthorizedEntity:kAuthorizedEntity
  469. scope:@"*"];
  470. [[self.mockInstanceID reject] defaultTokenWithHandler:nil];
  471. NSString *token = [self.mockInstanceID token];
  472. XCTAssertEqualObjects(token, kToken);
  473. }
  474. /**
  475. * Test that when we fetch a new default token and cache it successfully we post a
  476. * tokenRefresh notification which allows to fetch the cached token.
  477. */
  478. - (void)testDefaultTokenFetch_returnValidToken {
  479. XCTestExpectation *defaultTokenExpectation =
  480. [self expectationWithDescription:@"Successfully got default token."];
  481. __block FIRInstanceIDTokenInfo *cachedTokenInfo = nil;
  482. [self stubKeyPairStoreToReturnValidKeypair];
  483. [self mockAuthServiceToAlwaysReturnValidCheckin];
  484. // Mock Token manager to always succeed the token fetch, and return
  485. // a particular cached value.
  486. // Return a dynamic cachedToken variable whenever the cached is checked.
  487. // This uses an invocation-based mock because the |cachedToken| pointer
  488. // will change. Normal stubbing will always return the initial pointer,
  489. // which in this case is 0x0 (nil).
  490. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  491. [invocation setReturnValue:&cachedTokenInfo];
  492. }] cachedTokenInfoWithAuthorizedEntity:kAuthorizedEntity scope:kFIRInstanceIDDefaultTokenScope];
  493. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  494. self.newTokenCompletion(kToken, nil);
  495. }] fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity
  496. scope:kFIRInstanceIDDefaultTokenScope
  497. keyPair:[OCMArg any]
  498. options:[OCMArg any]
  499. handler:[OCMArg checkWithBlock:^BOOL(id obj) {
  500. self.newTokenCompletion = obj;
  501. return obj != nil;
  502. }]];
  503. __block int notificationPostCount = 0;
  504. __block NSString *notificationToken = nil;
  505. NSString *notificationName = kFIRInstanceIDTokenRefreshNotification;
  506. self.tokenRefreshNotificationObserver = [[NSNotificationCenter defaultCenter]
  507. addObserverForName:notificationName
  508. object:nil
  509. queue:nil
  510. usingBlock:^(NSNotification *_Nonnull note) {
  511. // Should have saved token to cache
  512. cachedTokenInfo = sTokenInfo;
  513. notificationPostCount++;
  514. notificationToken = [[self.instanceID token] copy];
  515. [defaultTokenExpectation fulfill];
  516. }];
  517. NSString *token = [self.mockInstanceID token];
  518. [self waitForExpectationsWithTimeout:10.0 handler:nil];
  519. [[NSNotificationCenter defaultCenter] removeObserver:self.tokenRefreshNotificationObserver];
  520. XCTAssertNil(token);
  521. XCTAssertEqualObjects(notificationToken, kToken);
  522. }
  523. /**
  524. * Tests that if we fail to fetch the token from the server for the first time we retry again
  525. * later with exponential backoff unless we succeed.
  526. */
  527. - (void)testDefaultTokenFetch_retryFetchToken {
  528. const int trialsBeforeSuccess = 3;
  529. __block int newTokenFetchCount = 0;
  530. __block int64_t lastFetchTimestampInSeconds;
  531. XCTestExpectation *defaultTokenExpectation =
  532. [self expectationWithDescription:@"Successfully got default token."];
  533. __block FIRInstanceIDTokenInfo *cachedTokenInfo = nil;
  534. [self stubKeyPairStoreToReturnValidKeypair];
  535. [self mockAuthServiceToAlwaysReturnValidCheckin];
  536. // Mock Token manager.
  537. // Return a dynamic cachedToken variable whenever the cached is checked.
  538. // This uses an invocation-based mock because the |cachedToken| pointer
  539. // will change. Normal stubbing will always return the initial pointer,
  540. // which in this case is 0x0 (nil).
  541. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  542. [invocation setReturnValue:&cachedTokenInfo];
  543. }] cachedTokenInfoWithAuthorizedEntity:kAuthorizedEntity scope:kFIRInstanceIDDefaultTokenScope];
  544. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  545. newTokenFetchCount++;
  546. int64_t delaySinceLastFetchInSeconds =
  547. FIRInstanceIDCurrentTimestampInSeconds() - lastFetchTimestampInSeconds;
  548. // Test exponential backoff.
  549. if (newTokenFetchCount > 1) {
  550. XCTAssertLessThanOrEqual(1 << (newTokenFetchCount - 1), delaySinceLastFetchInSeconds);
  551. }
  552. lastFetchTimestampInSeconds = FIRInstanceIDCurrentTimestampInSeconds();
  553. if (newTokenFetchCount < trialsBeforeSuccess) {
  554. NSError *error = [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeTimeout];
  555. self.newTokenCompletion(nil, error);
  556. } else {
  557. self.newTokenCompletion(kToken, nil);
  558. }
  559. }] fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity
  560. scope:kFIRInstanceIDDefaultTokenScope
  561. keyPair:[OCMArg any]
  562. options:[OCMArg any]
  563. handler:[OCMArg checkWithBlock:^BOOL(id obj) {
  564. self.newTokenCompletion = obj;
  565. return obj != nil;
  566. }]];
  567. __block int notificationPostCount = 0;
  568. __block NSString *notificationToken = nil;
  569. NSString *notificationName = kFIRInstanceIDTokenRefreshNotification;
  570. self.tokenRefreshNotificationObserver = [[NSNotificationCenter defaultCenter]
  571. addObserverForName:notificationName
  572. object:nil
  573. queue:nil
  574. usingBlock:^(NSNotification *_Nonnull note) {
  575. // Should have saved token to cache
  576. cachedTokenInfo = sTokenInfo;
  577. notificationPostCount++;
  578. notificationToken = [[self.instanceID token] copy];
  579. [defaultTokenExpectation fulfill];
  580. }];
  581. NSString *token = [self.mockInstanceID token];
  582. [self waitForExpectationsWithTimeout:20.0 handler:nil];
  583. [[NSNotificationCenter defaultCenter] removeObserver:self.tokenRefreshNotificationObserver];
  584. XCTAssertNil(token);
  585. XCTAssertEqualObjects(notificationToken, kToken);
  586. XCTAssertEqual(notificationPostCount, 1);
  587. XCTAssertEqual(newTokenFetchCount, trialsBeforeSuccess);
  588. }
  589. /**
  590. * Tests that when we don't have a cached default token multiple invocations to `getToken`
  591. * lead to a single networking call to fetch the token. Also verify that we post one unique
  592. * TokenRefresh notification for multiple invocations.
  593. */
  594. - (void)testDefaultToken_multipleInvocations {
  595. __block int newTokenFetchCount = 0;
  596. XCTestExpectation *defaultTokenExpectation =
  597. [self expectationWithDescription:@"Successfully got default token."];
  598. __block FIRInstanceIDTokenInfo *cachedTokenInfo = nil;
  599. [self stubKeyPairStoreToReturnValidKeypair];
  600. [self mockAuthServiceToAlwaysReturnValidCheckin];
  601. // Mock Token manager.
  602. // Return a dynamic cachedToken variable whenever the cached is checked.
  603. // This uses an invocation-based mock because the |cachedToken| pointer
  604. // will change. Normal stubbing will always return the initial pointer,
  605. // which in this case is 0x0 (nil).
  606. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  607. [invocation setReturnValue:&cachedTokenInfo];
  608. }] cachedTokenInfoWithAuthorizedEntity:kAuthorizedEntity scope:kFIRInstanceIDDefaultTokenScope];
  609. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  610. // Invoke callback after some delay (network delay)
  611. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)),
  612. dispatch_get_main_queue(), ^{
  613. self.newTokenCompletion(kToken, nil);
  614. });
  615. newTokenFetchCount++;
  616. XCTAssertEqual(newTokenFetchCount, 1);
  617. }] fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity
  618. scope:kFIRInstanceIDDefaultTokenScope
  619. keyPair:[OCMArg any]
  620. options:[OCMArg any]
  621. handler:[OCMArg checkWithBlock:^BOOL(id obj) {
  622. self.newTokenCompletion = obj;
  623. return obj != nil;
  624. }]];
  625. __block int notificationPostCount = 0;
  626. __block NSString *notificationToken = nil;
  627. NSString *notificationName = kFIRInstanceIDTokenRefreshNotification;
  628. self.tokenRefreshNotificationObserver = [[NSNotificationCenter defaultCenter]
  629. addObserverForName:notificationName
  630. object:nil
  631. queue:nil
  632. usingBlock:^(NSNotification *_Nonnull note) {
  633. // Should have saved token to cache
  634. cachedTokenInfo = sTokenInfo;
  635. notificationPostCount++;
  636. notificationToken = [[self.instanceID token] copy];
  637. [defaultTokenExpectation fulfill];
  638. }];
  639. NSString *token = [self.mockInstanceID token];
  640. // Invoke get token again with some delay. Our initial request to getToken hasn't yet
  641. // returned from the server.
  642. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)),
  643. dispatch_get_main_queue(), ^{
  644. XCTAssertNil([self.mockInstanceID token]);
  645. });
  646. // Invoke again after further delay.
  647. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)),
  648. dispatch_get_main_queue(), ^{
  649. XCTAssertNil([self.mockInstanceID token]);
  650. });
  651. [self waitForExpectationsWithTimeout:15.0 handler:nil];
  652. [[NSNotificationCenter defaultCenter] removeObserver:self.tokenRefreshNotificationObserver];
  653. XCTAssertNil(token);
  654. XCTAssertEqualObjects(notificationToken, kToken);
  655. XCTAssertEqual(notificationPostCount, 1);
  656. XCTAssertEqual(newTokenFetchCount, 1);
  657. }
  658. - (void)testDefaultToken_maxRetries {
  659. __block int newTokenFetchCount = 0;
  660. XCTestExpectation *defaultTokenExpectation =
  661. [self expectationWithDescription:@"Did retry maximum times to fetch default token."];
  662. [self stubKeyPairStoreToReturnValidKeypair];
  663. [self mockAuthServiceToAlwaysReturnValidCheckin];
  664. // Mock Token manager.
  665. [[[self.mockTokenManager stub] andReturn:nil]
  666. cachedTokenInfoWithAuthorizedEntity:kAuthorizedEntity
  667. scope:kFIRInstanceIDDefaultTokenScope];
  668. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  669. newTokenFetchCount++;
  670. NSError *error = [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeNetwork];
  671. self.newTokenCompletion(nil, error);
  672. if (newTokenFetchCount == [FIRInstanceID maxRetryCountForDefaultToken]) {
  673. [defaultTokenExpectation fulfill];
  674. }
  675. }] fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity
  676. scope:kFIRInstanceIDDefaultTokenScope
  677. keyPair:[OCMArg any]
  678. options:[OCMArg any]
  679. handler:[OCMArg checkWithBlock:^BOOL(id obj) {
  680. self.newTokenCompletion = obj;
  681. return obj != nil;
  682. }]];
  683. // Mock Instance ID's retry interval to 0, to vastly speed up this test.
  684. [[[self.mockInstanceID stub] andReturnValue:@(0)] retryIntervalToFetchDefaultToken];
  685. // Try to fetch token once. It should set off retries since we mock failure.
  686. NSString *token = [self.mockInstanceID token];
  687. [self waitForExpectationsWithTimeout:1.0 handler:nil];
  688. XCTAssertNil(token);
  689. XCTAssertEqual(newTokenFetchCount, [FIRInstanceID maxRetryCountForDefaultToken]);
  690. }
  691. - (void)testInstanceIDWithHandler_WhileRequesting_Success {
  692. [self stubKeyPairStoreToReturnValidKeypair];
  693. [self mockAuthServiceToAlwaysReturnValidCheckin];
  694. // Expect `fetchNewTokenWithAuthorizedEntity` to be called once
  695. XCTestExpectation *fetchNewTokenExpectation =
  696. [self expectationWithDescription:@"fetchNewTokenExpectation"];
  697. __block FIRInstanceIDTokenHandler tokenHandler;
  698. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  699. [invocation getArgument:&tokenHandler atIndex:6];
  700. [fetchNewTokenExpectation fulfill];
  701. }] fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity
  702. scope:kFIRInstanceIDDefaultTokenScope
  703. keyPair:[OCMArg any]
  704. options:[OCMArg any]
  705. handler:[OCMArg any]];
  706. // Make 1st call
  707. XCTestExpectation *handlerExpectation1 = [self expectationWithDescription:@"handlerExpectation1"];
  708. FIRInstanceIDResultHandler handler1 =
  709. ^(FIRInstanceIDResult *_Nullable result, NSError *_Nullable error) {
  710. [handlerExpectation1 fulfill];
  711. XCTAssertNotNil(result);
  712. XCTAssertEqual(result.token, kToken);
  713. XCTAssertNil(error);
  714. };
  715. [self.mockInstanceID instanceIDWithHandler:handler1];
  716. // Make 2nd call
  717. XCTestExpectation *handlerExpectation2 = [self expectationWithDescription:@"handlerExpectation1"];
  718. FIRInstanceIDResultHandler handler2 =
  719. ^(FIRInstanceIDResult *_Nullable result, NSError *_Nullable error) {
  720. [handlerExpectation2 fulfill];
  721. XCTAssertNotNil(result);
  722. XCTAssertEqual(result.token, kToken);
  723. XCTAssertNil(error);
  724. };
  725. [self.mockInstanceID instanceIDWithHandler:handler2];
  726. // Wait for `fetchNewTokenWithAuthorizedEntity` to be performed
  727. [self waitForExpectations:@[ fetchNewTokenExpectation ] timeout:1 enforceOrder:false];
  728. // Finish token fetch request
  729. tokenHandler(kToken, nil);
  730. // Wait for completion handlers for both calls to be performed
  731. [self waitForExpectationsWithTimeout:1 handler:NULL];
  732. }
  733. - (void)testInstanceIDWithHandler_WhileRequesting_RetrySuccess {
  734. [self stubKeyPairStoreToReturnValidKeypair];
  735. [self mockAuthServiceToAlwaysReturnValidCheckin];
  736. // Expect `fetchNewTokenWithAuthorizedEntity` to be called twice
  737. XCTestExpectation *fetchNewTokenExpectation1 =
  738. [self expectationWithDescription:@"fetchNewTokenExpectation1"];
  739. XCTestExpectation *fetchNewTokenExpectation2 =
  740. [self expectationWithDescription:@"fetchNewTokenExpectation2"];
  741. NSArray *fetchNewTokenExpectations = @[ fetchNewTokenExpectation1, fetchNewTokenExpectation2 ];
  742. __block NSInteger fetchNewTokenCallCount = 0;
  743. __block FIRInstanceIDTokenHandler tokenHandler;
  744. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  745. [invocation getArgument:&tokenHandler atIndex:6];
  746. [fetchNewTokenExpectations[fetchNewTokenCallCount] fulfill];
  747. fetchNewTokenCallCount += 1;
  748. }] fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity
  749. scope:kFIRInstanceIDDefaultTokenScope
  750. keyPair:[OCMArg any]
  751. options:[OCMArg any]
  752. handler:[OCMArg any]];
  753. // Mock Instance ID's retry interval to 0, to vastly speed up this test.
  754. [[[self.mockInstanceID stub] andReturnValue:@(0)] retryIntervalToFetchDefaultToken];
  755. // Make 1st call
  756. XCTestExpectation *handlerExpectation1 = [self expectationWithDescription:@"handlerExpectation1"];
  757. FIRInstanceIDResultHandler handler1 =
  758. ^(FIRInstanceIDResult *_Nullable result, NSError *_Nullable error) {
  759. [handlerExpectation1 fulfill];
  760. XCTAssertNotNil(result);
  761. XCTAssertEqual(result.token, kToken);
  762. XCTAssertNil(error);
  763. };
  764. [self.mockInstanceID instanceIDWithHandler:handler1];
  765. // Make 2nd call
  766. XCTestExpectation *handlerExpectation2 = [self expectationWithDescription:@"handlerExpectation1"];
  767. FIRInstanceIDResultHandler handler2 =
  768. ^(FIRInstanceIDResult *_Nullable result, NSError *_Nullable error) {
  769. [handlerExpectation2 fulfill];
  770. XCTAssertNotNil(result);
  771. XCTAssertEqual(result.token, kToken);
  772. XCTAssertNil(error);
  773. };
  774. [self.mockInstanceID instanceIDWithHandler:handler2];
  775. // Wait for the 1st `fetchNewTokenWithAuthorizedEntity` to be performed
  776. [self waitForExpectations:@[ fetchNewTokenExpectation1 ] timeout:1 enforceOrder:false];
  777. // Fail for the 1st time
  778. tokenHandler(nil, [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeUnknown]);
  779. // Wait for the 2nd token feth
  780. [self waitForExpectations:@[ fetchNewTokenExpectation2 ] timeout:1 enforceOrder:false];
  781. // Finish with success
  782. tokenHandler(kToken, nil);
  783. // Wait for completion handlers for both calls to be performed
  784. [self waitForExpectationsWithTimeout:1 handler:NULL];
  785. }
  786. - (void)testInstanceIDWithHandler_WhileRequesting_RetryFailure {
  787. [self stubKeyPairStoreToReturnValidKeypair];
  788. [self mockAuthServiceToAlwaysReturnValidCheckin];
  789. // Expect `fetchNewTokenWithAuthorizedEntity` to be called once
  790. NSMutableArray<XCTestExpectation *> *fetchNewTokenExpectations = [NSMutableArray array];
  791. for (NSInteger i = 0; i < [[self.instanceID class] maxRetryCountForDefaultToken]; ++i) {
  792. NSString *name = [NSString stringWithFormat:@"fetchNewTokenExpectation-%ld", (long)i];
  793. [fetchNewTokenExpectations addObject:[self expectationWithDescription:name]];
  794. }
  795. __block NSInteger fetchNewTokenCallCount = 0;
  796. __block FIRInstanceIDTokenHandler tokenHandler;
  797. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  798. [invocation getArgument:&tokenHandler atIndex:6];
  799. [fetchNewTokenExpectations[fetchNewTokenCallCount] fulfill];
  800. fetchNewTokenCallCount += 1;
  801. }] fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity
  802. scope:kFIRInstanceIDDefaultTokenScope
  803. keyPair:[OCMArg any]
  804. options:[OCMArg any]
  805. handler:[OCMArg any]];
  806. // Mock Instance ID's retry interval to 0, to vastly speed up this test.
  807. [[[self.mockInstanceID stub] andReturnValue:@(0)] retryIntervalToFetchDefaultToken];
  808. // Make 1st call
  809. XCTestExpectation *handlerExpectation1 = [self expectationWithDescription:@"handlerExpectation1"];
  810. FIRInstanceIDResultHandler handler1 =
  811. ^(FIRInstanceIDResult *_Nullable result, NSError *_Nullable error) {
  812. [handlerExpectation1 fulfill];
  813. XCTAssertNil(result);
  814. XCTAssertNotNil(error);
  815. };
  816. [self.mockInstanceID instanceIDWithHandler:handler1];
  817. // Make 2nd call
  818. XCTestExpectation *handlerExpectation2 = [self expectationWithDescription:@"handlerExpectation1"];
  819. FIRInstanceIDResultHandler handler2 =
  820. ^(FIRInstanceIDResult *_Nullable result, NSError *_Nullable error) {
  821. [handlerExpectation2 fulfill];
  822. XCTAssertNil(result);
  823. XCTAssertNotNil(error);
  824. };
  825. [self.mockInstanceID instanceIDWithHandler:handler2];
  826. for (NSInteger i = 0; i < [[self.instanceID class] maxRetryCountForDefaultToken]; ++i) {
  827. // Wait for the i `fetchNewTokenWithAuthorizedEntity` to be performed
  828. [self waitForExpectations:@[ fetchNewTokenExpectations[i] ] timeout:1 enforceOrder:false];
  829. // Fail for the i time
  830. tokenHandler(nil, [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeUnknown]);
  831. }
  832. // Wait for completion handlers for both calls to be performed
  833. [self waitForExpectationsWithTimeout:1 handler:NULL];
  834. }
  835. /**
  836. * Tests a Keychain read failure while we try to fetch a new InstanceID token. If the Keychain
  837. * read fails we won't be able to fetch the public key which is required while fetching a new
  838. * token. In such a case we should return KeyPair failure.
  839. */
  840. - (void)testNewTokenFetch_keyChainError {
  841. XCTestExpectation *tokenExpectation =
  842. [self expectationWithDescription:@"New token handler invoked."];
  843. [self mockAuthServiceToAlwaysReturnValidCheckin];
  844. // Simulate keypair fetch/generation failure.
  845. [[[self.mockKeyPairStore stub] andReturn:nil] loadKeyPairWithError:[OCMArg anyObjectRef]];
  846. [[self.mockTokenManager reject] fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity
  847. scope:kScope
  848. keyPair:[OCMArg any]
  849. options:[OCMArg any]
  850. handler:[OCMArg any]];
  851. [self.instanceID tokenWithAuthorizedEntity:kAuthorizedEntity
  852. scope:kScope
  853. options:nil
  854. handler:^(NSString *token, NSError *error) {
  855. XCTAssertNil(token);
  856. XCTAssertNotNil(error);
  857. [tokenExpectation fulfill];
  858. }];
  859. [self waitForExpectationsWithTimeout:1 handler:nil];
  860. OCMVerifyAll(self.mockTokenManager);
  861. }
  862. /**
  863. * If a token fetch includes in its options an "apns_token" object, but not a "apns_sandbox" key,
  864. * ensure that an "apns_sandbox" key is added to the token options (via automatic detection).
  865. */
  866. - (void)testTokenFetchAPNSServerTypeIsIncludedIfAPNSTokenProvided {
  867. XCTestExpectation *apnsServerTypeExpectation =
  868. [self expectationWithDescription:@"apns_sandbox key was included in token options"];
  869. [self stubKeyPairStoreToReturnValidKeypair];
  870. [self mockAuthServiceToAlwaysReturnValidCheckin];
  871. NSData *apnsToken = [kFakeAPNSToken dataUsingEncoding:NSUTF8StringEncoding];
  872. // Option is purposefully missing the apns_sandbox key
  873. NSDictionary *tokenOptions = @{kFIRInstanceIDTokenOptionsAPNSKey : apnsToken};
  874. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  875. // Inspect
  876. NSDictionary *options;
  877. [invocation getArgument:&options atIndex:5];
  878. if (options[kFIRInstanceIDTokenOptionsAPNSIsSandboxKey] != nil) {
  879. [apnsServerTypeExpectation fulfill];
  880. }
  881. self.newTokenCompletion(kToken, nil);
  882. }] fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity
  883. scope:kScope
  884. keyPair:[OCMArg any]
  885. options:[OCMArg any]
  886. handler:[OCMArg checkWithBlock:^BOOL(id obj) {
  887. self.newTokenCompletion = obj;
  888. return obj != nil;
  889. }]];
  890. [self.instanceID tokenWithAuthorizedEntity:kAuthorizedEntity
  891. scope:kScope
  892. options:tokenOptions
  893. handler:^(NSString *token, NSError *error){
  894. }];
  895. [self waitForExpectationsWithTimeout:60.0
  896. handler:^(NSError *error) {
  897. XCTAssertNil(error);
  898. }];
  899. }
  900. /**
  901. * Tests that if a token was fetched, but during the fetch the APNs data was set, that a new
  902. * token is fetched to associate the APNs data, and is not returned from the cache.
  903. */
  904. - (void)testTokenFetch_ignoresCacheIfAPNSInfoDifferent {
  905. XCTestExpectation *tokenRequestExpectation =
  906. [self expectationWithDescription:@"Token was fetched from the network"];
  907. // Initialize a token in the cache *WITHOUT* APNSInfo
  908. // This token is |kToken|, but we will simulate that a fetch will return another token
  909. NSString *oldCachedToken = kToken;
  910. NSString *fetchedToken = @"abcd123_newtoken";
  911. __block FIRInstanceIDTokenInfo *cachedTokenInfo =
  912. [[FIRInstanceIDTokenInfo alloc] initWithAuthorizedEntity:kAuthorizedEntity
  913. scope:kFIRInstanceIDDefaultTokenScope
  914. token:oldCachedToken
  915. appVersion:@"1.0"
  916. firebaseAppID:@"firebaseAppID"];
  917. [self stubKeyPairStoreToReturnValidKeypair];
  918. [self mockAuthServiceToAlwaysReturnValidCheckin];
  919. // During this test use the default scope ("*") to simulate the default token behavior.
  920. // Return a dynamic cachedToken variable whenever the cached is checked.
  921. // This uses an invocation-based mock because the |cachedToken| pointer
  922. // will change. Normal stubbing will always return the initial pointer,
  923. // which in this case is 0x0 (nil).
  924. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  925. [invocation setReturnValue:&cachedTokenInfo];
  926. }] cachedTokenInfoWithAuthorizedEntity:kAuthorizedEntity scope:kFIRInstanceIDDefaultTokenScope];
  927. // Mock the network request to return |fetchedToken|, so we can clearly see if the token is
  928. // is different than what was cached.
  929. [[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
  930. [tokenRequestExpectation fulfill];
  931. self.newTokenCompletion(fetchedToken, nil);
  932. }] fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity
  933. scope:kFIRInstanceIDDefaultTokenScope
  934. keyPair:[OCMArg any]
  935. options:[OCMArg any]
  936. handler:[OCMArg checkWithBlock:^BOOL(id obj) {
  937. self.newTokenCompletion = obj;
  938. return obj != nil;
  939. }]];
  940. // Begin request
  941. // Token options has APNS data, which is not associated with the cached token
  942. NSDictionary *tokenOptions = @{
  943. kFIRInstanceIDTokenOptionsAPNSKey : [@"apns" dataUsingEncoding:NSUTF8StringEncoding],
  944. kFIRInstanceIDTokenOptionsAPNSIsSandboxKey : @(NO)
  945. };
  946. [self.instanceID
  947. tokenWithAuthorizedEntity:kAuthorizedEntity
  948. scope:kFIRInstanceIDDefaultTokenScope
  949. options:tokenOptions
  950. handler:^(NSString *_Nullable token, NSError *_Nullable error) {
  951. XCTAssertEqualObjects(token, fetchedToken);
  952. }];
  953. [self waitForExpectationsWithTimeout:0.5 handler:nil];
  954. }
  955. /**
  956. * Tests that if there is a keychain failure while fetching the InstanceID of the token we should
  957. * return nil for the identity.
  958. */
  959. - (void)testInstanceIDFetch_keyChainError {
  960. XCTestExpectation *tokenExpectation =
  961. [self expectationWithDescription:@"InstanceID fetch handler invoked."];
  962. // Simulate keypair fetch/generation failure.
  963. NSError *error = [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeInvalidKeyPair];
  964. [[[self.mockKeyPairStore stub] andReturn:nil] appIdentityWithError:[OCMArg setTo:error]];
  965. [self.instanceID getIDWithHandler:^(NSString *_Nullable identity, NSError *_Nullable error) {
  966. XCTAssertNil(identity);
  967. XCTAssertNotNil(error);
  968. [tokenExpectation fulfill];
  969. }];
  970. [self waitForExpectationsWithTimeout:1 handler:nil];
  971. }
  972. - (void)testInstanceIDDelete_keyChainError {
  973. XCTestExpectation *tokenExpectation =
  974. [self expectationWithDescription:@"InstanceID deleteID handler invoked."];
  975. // Simulate keypair fetch/generation failure.
  976. NSError *error = [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeInvalidKeyPair];
  977. [[[self.mockKeyPairStore stub] andReturn:nil] appIdentityWithError:[OCMArg setTo:error]];
  978. [self.instanceID deleteIDWithHandler:^(NSError *_Nullable error) {
  979. XCTAssertNotNil(error);
  980. [tokenExpectation fulfill];
  981. }];
  982. [self waitForExpectationsWithTimeout:1 handler:nil];
  983. }
  984. #pragma mark - Private Helpers
  985. - (void)stubKeyPairStoreToReturnValidKeypair {
  986. [[[self.mockKeyPairStore stub] andReturn:[self createValidMockKeypair]]
  987. loadKeyPairWithError:[OCMArg anyObjectRef]];
  988. }
  989. - (id)createValidMockKeypair {
  990. id mockKeypair = OCMClassMock([FIRInstanceIDKeyPair class]);
  991. [[[mockKeypair stub] andReturnValue:@YES] isValid];
  992. return mockKeypair;
  993. }
  994. - (FIRInstanceIDCheckinPreferences *)validCheckinPreferences {
  995. NSDictionary *gservicesData = @{
  996. kFIRInstanceIDVersionInfoStringKey : kVersionInfo,
  997. kFIRInstanceIDLastCheckinTimeKey : @(FIRInstanceIDCurrentTimestampInMilliseconds())
  998. };
  999. FIRInstanceIDCheckinPreferences *checkinPreferences =
  1000. [[FIRInstanceIDCheckinPreferences alloc] initWithDeviceID:kDeviceAuthId
  1001. secretToken:kSecretToken];
  1002. [checkinPreferences updateWithCheckinPlistContents:gservicesData];
  1003. return checkinPreferences;
  1004. }
  1005. - (void)mockAuthServiceToAlwaysReturnValidCheckin {
  1006. FIRInstanceIDCheckinPreferences *validCheckin = [self validCheckinPreferences];
  1007. __block FIRInstanceIDDeviceCheckinCompletion checkinHandler;
  1008. [[[self.mockAuthService stub] andDo:^(NSInvocation *invocation) {
  1009. if (checkinHandler) {
  1010. checkinHandler(validCheckin, nil);
  1011. }
  1012. }] fetchCheckinInfoWithHandler:[OCMArg checkWithBlock:^BOOL(id obj) {
  1013. return (checkinHandler = obj) != nil;
  1014. }]];
  1015. }
  1016. @end