FIRInstanceIDTest.m 62 KB

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