FIRMessagingTokenOperationsTest.m 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. /*
  2. * Copyright 2021 Google LLC
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <XCTest/XCTest.h>
  17. #import <OCMock/OCMock.h>
  18. #import "FirebaseCore/Extension/FirebaseCoreInternal.h"
  19. #import "FirebaseInstallations/Source/Library/Private/FirebaseInstallationsInternal.h"
  20. #import "FirebaseMessaging/Sources/FIRMessagingConstants.h"
  21. #import "FirebaseMessaging/Sources/NSError+FIRMessaging.h"
  22. #import "FirebaseMessaging/Sources/Token/FIRMessagingAuthService.h"
  23. #import "FirebaseMessaging/Sources/Token/FIRMessagingCheckinPreferences.h"
  24. #import "FirebaseMessaging/Sources/Token/FIRMessagingCheckinService.h"
  25. #import "FirebaseMessaging/Sources/Token/FIRMessagingCheckinStore.h"
  26. #import "FirebaseMessaging/Sources/Token/FIRMessagingKeychain.h"
  27. #import "FirebaseMessaging/Sources/Token/FIRMessagingTokenDeleteOperation.h"
  28. #import "FirebaseMessaging/Sources/Token/FIRMessagingTokenFetchOperation.h"
  29. #import "FirebaseMessaging/Sources/Token/FIRMessagingTokenOperation.h"
  30. #import "FirebaseMessaging/Sources/Token/FIRMessagingTokenStore.h"
  31. #import "SharedTestUtilities/URLSession/FIRURLSessionOCMockStub.h"
  32. static NSString *kDeviceID = @"fakeDeviceID";
  33. static NSString *kSecretToken = @"fakeSecretToken";
  34. static NSString *kDigestString = @"test-digest";
  35. static NSString *kVersionInfoString = @"version_info-1.0.0";
  36. static NSString *kAuthorizedEntity = @"sender-1234567";
  37. static NSString *kScope = @"fcm";
  38. static NSString *kRegistrationToken = @"token-12345";
  39. @interface FIRMessagingTokenOperation (ExposedForTest)
  40. - (void)performTokenOperation;
  41. + (NSString *)HTTPAuthHeaderFromCheckin:(FIRMessagingCheckinPreferences *)checkin;
  42. @end
  43. @interface FIRInstallationsAuthTokenResult (Tests)
  44. - (instancetype)initWithToken:(NSString *)token expirationDate:(NSDate *)expirationDate;
  45. @end
  46. #pragma mark - Fakes
  47. // A Fake operation that allows us to check that perform was called.
  48. // We are not using mocks here because we have no way of forcing NSOperationQueues to release
  49. // their operations, and this means that there is always going to be a race condition between
  50. // when we "stop" our partial mock vs when NSOperationQueue attempts to access the mock object on a
  51. // separate thread. We had mocks previously.
  52. @interface FIRMessagingTokenOperationFake : FIRMessagingTokenOperation
  53. @property(nonatomic, assign) BOOL performWasCalled;
  54. @end
  55. @implementation FIRMessagingTokenOperationFake
  56. - (void)performTokenOperation {
  57. self.performWasCalled = YES;
  58. }
  59. @end
  60. /// A fake heartbeat logger used for dependency injection during testing.
  61. @interface FIRHeartbeatLoggerFake : NSObject <FIRHeartbeatLoggerProtocol>
  62. @property(nonatomic, copy, nullable) FIRDailyHeartbeatCode (^onHeartbeatCodeForTodayHandler)(void);
  63. @end
  64. @implementation FIRHeartbeatLoggerFake
  65. - (nonnull FIRHeartbeatsPayload *)flushHeartbeatsIntoPayload {
  66. // This API should not be used by the below tests because the Messaging
  67. // SDK uses only the V1 heartbeat API (`heartbeatCodeForToday`) for
  68. // getting a single heartbeat.
  69. [self doesNotRecognizeSelector:_cmd];
  70. return nil;
  71. }
  72. - (FIRDailyHeartbeatCode)heartbeatCodeForToday {
  73. if (self.onHeartbeatCodeForTodayHandler) {
  74. return self.onHeartbeatCodeForTodayHandler();
  75. } else {
  76. return FIRDailyHeartbeatCodeNone;
  77. }
  78. }
  79. - (void)log {
  80. // This API should not be used by the below tests because the Messaging
  81. // SDK does not log heartbeats in it's networking context.
  82. [self doesNotRecognizeSelector:_cmd];
  83. }
  84. @end
  85. #pragma mark - FIRMessagingTokenOperationsTest
  86. @interface FIRMessagingTokenOperationsTest : XCTestCase
  87. @property(nonatomic) id URLSessionMock;
  88. @property(strong, readonly, nonatomic) FIRMessagingAuthService *authService;
  89. @property(strong, readonly, nonatomic) id mockAuthService;
  90. @property(strong, readonly, nonatomic) id mockTokenStore;
  91. @property(strong, readonly, nonatomic) FIRMessagingCheckinService *checkinService;
  92. @property(strong, readonly, nonatomic) id mockCheckinService;
  93. @property(strong, readonly, nonatomic) id mockInstallations;
  94. @property(strong, readonly, nonatomic) id mockHeartbeatInfo;
  95. @property(strong, readonly, nonatomic) NSString *instanceID;
  96. @property(nonatomic, readwrite, strong) FIRMessagingCheckinPreferences *checkinPreferences;
  97. @end
  98. @implementation FIRMessagingTokenOperationsTest
  99. - (void)setUp {
  100. [super setUp];
  101. // Stub NSURLSession constructor before instantiating FIRMessagingCheckinService to inject
  102. // URLSessionMock.
  103. self.URLSessionMock = OCMClassMock([NSURLSession class]);
  104. OCMStub(ClassMethod([self.URLSessionMock sessionWithConfiguration:[OCMArg any]]))
  105. .andReturn(self.URLSessionMock);
  106. _mockTokenStore = OCMClassMock([FIRMessagingTokenStore class]);
  107. _checkinService = [[FIRMessagingCheckinService alloc] init];
  108. _mockCheckinService = OCMPartialMock(_checkinService);
  109. _authService = [[FIRMessagingAuthService alloc] init];
  110. _instanceID = @"instanceID";
  111. // `FIRMessagingTokenOperation` uses `FIRInstallations` under the hood to get FIS auth token.
  112. // Stub `FIRInstallations` to avoid using a real object.
  113. [self stubInstallations];
  114. }
  115. - (void)tearDown {
  116. _authService = nil;
  117. [_mockCheckinService stopMocking];
  118. _mockCheckinService = nil;
  119. _checkinService = nil;
  120. _mockTokenStore = nil;
  121. [_mockInstallations stopMocking];
  122. [_mockHeartbeatInfo stopMocking];
  123. }
  124. - (void)testThatTokenOperationsAuthHeaderStringMatchesCheckin {
  125. int64_t tenHoursAgo = FIRMessagingCurrentTimestampInMilliseconds() - 10 * 60 * 60 * 1000;
  126. FIRMessagingCheckinPreferences *checkin =
  127. [self setCheckinPreferencesWithLastCheckinTime:tenHoursAgo];
  128. NSString *expectedAuthHeader = [FIRMessagingTokenOperation HTTPAuthHeaderFromCheckin:checkin];
  129. XCTestExpectation *authHeaderMatchesCheckinExpectation =
  130. [self expectationWithDescription:@"Auth header string in request matches checkin info"];
  131. FIRMessagingTokenFetchOperation *operation = [[FIRMessagingTokenFetchOperation alloc]
  132. initWithAuthorizedEntity:kAuthorizedEntity
  133. scope:kScope
  134. options:nil
  135. checkinPreferences:checkin
  136. instanceID:self.instanceID
  137. heartbeatLogger:[[FIRHeartbeatLoggerFake alloc] init]];
  138. NSURL *expectedRequestURL = [NSURL URLWithString:FIRMessagingTokenRegisterServer()];
  139. NSHTTPURLResponse *expectedResponse = [[NSHTTPURLResponse alloc] initWithURL:expectedRequestURL
  140. statusCode:200
  141. HTTPVersion:@"HTTP/1.1"
  142. headerFields:nil];
  143. [FIRURLSessionOCMockStub
  144. stubURLSessionDataTaskWithResponse:expectedResponse
  145. body:[self dataForResponseWithValidToken:YES]
  146. error:nil
  147. URLSessionMock:self.URLSessionMock
  148. requestValidationBlock:^BOOL(NSURLRequest *_Nonnull sentRequest) {
  149. NSDictionary<NSString *, NSString *> *headers = sentRequest.allHTTPHeaderFields;
  150. NSString *authHeader = headers[@"Authorization"];
  151. if ([authHeader isEqualToString:expectedAuthHeader]) {
  152. [authHeaderMatchesCheckinExpectation fulfill];
  153. }
  154. return YES;
  155. }];
  156. [operation start];
  157. [self waitForExpectationsWithTimeout:0.25
  158. handler:^(NSError *_Nullable error) {
  159. XCTAssertNil(error.localizedDescription);
  160. }];
  161. }
  162. - (void)testThatTokenOperationWithoutCheckInFails {
  163. // If asserts are enabled, test for the assert to be thrown, otherwise check for the resulting
  164. // error in the completion handler.
  165. XCTestExpectation *failedExpectation =
  166. [self expectationWithDescription:@"Operation failed without checkin info"];
  167. // This will return hasCheckinInfo == NO
  168. FIRMessagingCheckinPreferences *emptyCheckinPreferences =
  169. [[FIRMessagingCheckinPreferences alloc] initWithDeviceID:@"" secretToken:@""];
  170. FIRMessagingTokenOperation *operation =
  171. [[FIRMessagingTokenOperation alloc] initWithAction:FIRMessagingTokenActionFetch
  172. forAuthorizedEntity:kAuthorizedEntity
  173. scope:kScope
  174. options:nil
  175. checkinPreferences:emptyCheckinPreferences
  176. instanceID:self.instanceID
  177. heartbeatLogger:[[FIRHeartbeatLoggerFake alloc] init]];
  178. [operation addCompletionHandler:^(FIRMessagingTokenOperationResult result,
  179. NSString *_Nullable token, NSError *_Nullable error) {
  180. [failedExpectation fulfill];
  181. }];
  182. @try {
  183. [operation start];
  184. } @catch (NSException *exception) {
  185. if (exception.name == NSInternalInconsistencyException) {
  186. [failedExpectation fulfill];
  187. }
  188. } @finally {
  189. }
  190. [self waitForExpectationsWithTimeout:0.25
  191. handler:^(NSError *_Nullable error) {
  192. XCTAssertNil(error.localizedDescription);
  193. }];
  194. }
  195. - (void)testThatAnAlreadyCancelledOperationFinishesWithoutStarting {
  196. XCTestExpectation *cancelledExpectation =
  197. [self expectationWithDescription:@"Operation finished as cancelled"];
  198. XCTestExpectation *didNotCallPerform =
  199. [self expectationWithDescription:@"Did not call performTokenOperation"];
  200. int64_t tenHoursAgo = FIRMessagingCurrentTimestampInMilliseconds() - 10 * 60 * 60 * 1000;
  201. FIRMessagingCheckinPreferences *checkinPreferences =
  202. [self setCheckinPreferencesWithLastCheckinTime:tenHoursAgo];
  203. FIRMessagingTokenOperationFake *operation =
  204. [[FIRMessagingTokenOperationFake alloc] initWithAction:FIRMessagingTokenActionFetch
  205. forAuthorizedEntity:kAuthorizedEntity
  206. scope:kScope
  207. options:nil
  208. checkinPreferences:checkinPreferences
  209. instanceID:self.instanceID
  210. heartbeatLogger:[[FIRHeartbeatLoggerFake alloc] init]];
  211. operation.performWasCalled = NO;
  212. __weak FIRMessagingTokenOperationFake *weakOperation = operation;
  213. [operation addCompletionHandler:^(FIRMessagingTokenOperationResult result,
  214. NSString *_Nullable token, NSError *_Nullable error) {
  215. if (result == FIRMessagingTokenOperationCancelled) {
  216. [cancelledExpectation fulfill];
  217. }
  218. if (!weakOperation.performWasCalled) {
  219. [didNotCallPerform fulfill];
  220. }
  221. }];
  222. [operation cancel];
  223. [operation start];
  224. [self waitForExpectationsWithTimeout:0.25
  225. handler:^(NSError *_Nullable error) {
  226. XCTAssertNil(error.localizedDescription);
  227. }];
  228. }
  229. - (void)testThatOptionsDictionaryIsIncludedWithFetchRequest {
  230. XCTestExpectation *optionsIncludedExpectation =
  231. [self expectationWithDescription:@"Options keys were included in token URL request"];
  232. int64_t tenHoursAgo = FIRMessagingCurrentTimestampInMilliseconds() - 10 * 60 * 60 * 1000;
  233. FIRMessagingCheckinPreferences *checkinPreferences =
  234. [self setCheckinPreferencesWithLastCheckinTime:tenHoursAgo];
  235. NSData *fakeDeviceToken = [@"fakeAPNSToken" dataUsingEncoding:NSUTF8StringEncoding];
  236. BOOL isSandbox = NO;
  237. NSString *apnsTupleString =
  238. FIRMessagingAPNSTupleStringForTokenAndServerType(fakeDeviceToken, isSandbox);
  239. NSDictionary *options = @{
  240. kFIRMessagingTokenOptionsFirebaseAppIDKey : @"fakeGMPAppID",
  241. kFIRMessagingTokenOptionsAPNSKey : fakeDeviceToken,
  242. kFIRMessagingTokenOptionsAPNSIsSandboxKey : @(isSandbox),
  243. };
  244. FIRMessagingTokenFetchOperation *operation = [[FIRMessagingTokenFetchOperation alloc]
  245. initWithAuthorizedEntity:kAuthorizedEntity
  246. scope:kScope
  247. options:options
  248. checkinPreferences:checkinPreferences
  249. instanceID:self.instanceID
  250. heartbeatLogger:[[FIRHeartbeatLoggerFake alloc] init]];
  251. NSURL *expectedRequestURL = [NSURL URLWithString:FIRMessagingTokenRegisterServer()];
  252. NSHTTPURLResponse *expectedResponse = [[NSHTTPURLResponse alloc] initWithURL:expectedRequestURL
  253. statusCode:200
  254. HTTPVersion:@"HTTP/1.1"
  255. headerFields:nil];
  256. [FIRURLSessionOCMockStub
  257. stubURLSessionDataTaskWithResponse:expectedResponse
  258. body:[self dataForResponseWithValidToken:YES]
  259. error:nil
  260. URLSessionMock:self.URLSessionMock
  261. requestValidationBlock:^BOOL(NSURLRequest *_Nonnull sentRequest) {
  262. NSString *query = [[NSString alloc] initWithData:sentRequest.HTTPBody
  263. encoding:NSUTF8StringEncoding];
  264. NSString *gmpAppIDQueryTuple = [NSString
  265. stringWithFormat:@"%@=%@", kFIRMessagingTokenOptionsFirebaseAppIDKey,
  266. options[kFIRMessagingTokenOptionsFirebaseAppIDKey]];
  267. NSRange gmpAppIDRange = [query rangeOfString:gmpAppIDQueryTuple];
  268. NSString *apnsQueryTuple =
  269. [NSString stringWithFormat:@"%@=%@", kFIRMessagingTokenOptionsAPNSKey,
  270. apnsTupleString];
  271. NSRange apnsRange = [query rangeOfString:apnsQueryTuple];
  272. if (gmpAppIDRange.location != NSNotFound && apnsRange.location != NSNotFound) {
  273. [optionsIncludedExpectation fulfill];
  274. }
  275. return YES;
  276. }];
  277. [operation start];
  278. [self waitForExpectationsWithTimeout:0.25
  279. handler:^(NSError *_Nullable error) {
  280. XCTAssertNil(error.localizedDescription);
  281. }];
  282. }
  283. - (void)testServerResetCommand {
  284. XCTestExpectation *shouldResetIdentityExpectation =
  285. [self expectationWithDescription:
  286. @"When server sends down RST error, clients should return reset identity error."];
  287. int64_t tenHoursAgo = FIRMessagingCurrentTimestampInMilliseconds() - 10 * 60 * 60 * 1000;
  288. FIRMessagingCheckinPreferences *checkinPreferences =
  289. [self setCheckinPreferencesWithLastCheckinTime:tenHoursAgo];
  290. FIRMessagingTokenFetchOperation *operation = [[FIRMessagingTokenFetchOperation alloc]
  291. initWithAuthorizedEntity:kAuthorizedEntity
  292. scope:kScope
  293. options:nil
  294. checkinPreferences:checkinPreferences
  295. instanceID:self.instanceID
  296. heartbeatLogger:[[FIRHeartbeatLoggerFake alloc] init]];
  297. NSURL *expectedRequestURL = [NSURL URLWithString:FIRMessagingTokenRegisterServer()];
  298. NSHTTPURLResponse *expectedResponse = [[NSHTTPURLResponse alloc] initWithURL:expectedRequestURL
  299. statusCode:200
  300. HTTPVersion:@"HTTP/1.1"
  301. headerFields:nil];
  302. [FIRURLSessionOCMockStub
  303. stubURLSessionDataTaskWithResponse:expectedResponse
  304. body:[self dataForResponseWithValidToken:NO]
  305. error:nil
  306. URLSessionMock:self.URLSessionMock
  307. requestValidationBlock:^BOOL(NSURLRequest *_Nonnull sentRequest) {
  308. return YES;
  309. }];
  310. [operation addCompletionHandler:^(FIRMessagingTokenOperationResult result,
  311. NSString *_Nullable token, NSError *_Nullable error) {
  312. XCTAssertEqual(result, FIRMessagingTokenOperationError);
  313. XCTAssertNotNil(error);
  314. XCTAssertEqual(error.code, kFIRMessagingErrorCodeInvalidIdentity);
  315. [shouldResetIdentityExpectation fulfill];
  316. }];
  317. [operation start];
  318. [self waitForExpectationsWithTimeout:0.25
  319. handler:^(NSError *_Nullable error) {
  320. XCTAssertNil(error.localizedDescription);
  321. }];
  322. }
  323. - (void)testHTTPAuthHeaderGenerationFromCheckin {
  324. FIRMessagingCheckinPreferences *checkinPreferences =
  325. [[FIRMessagingCheckinPreferences alloc] initWithDeviceID:kDeviceID secretToken:kSecretToken];
  326. NSString *expectedHeader =
  327. [NSString stringWithFormat:@"AidLogin %@:%@", checkinPreferences.deviceID,
  328. checkinPreferences.secretToken];
  329. NSString *generatedHeader =
  330. [FIRMessagingTokenOperation HTTPAuthHeaderFromCheckin:checkinPreferences];
  331. XCTAssertEqualObjects(generatedHeader, expectedHeader);
  332. }
  333. - (void)testTokenFetchOperationFirebaseUserAgentAndHeartbeatHeader_WhenHeartbeatNeedsSending {
  334. [self assertTokenFetchOperationRequestContainsFirebaseUserAgentAndHeartbeatInfoCode:
  335. FIRDailyHeartbeatCodeSome];
  336. }
  337. - (void)testTokenFetchOperationFirebaseUserAgentAndHeartbeatHeader_WhenNoHeartbeatNeedsSending {
  338. [self assertTokenFetchOperationRequestContainsFirebaseUserAgentAndHeartbeatInfoCode:
  339. FIRDailyHeartbeatCodeNone];
  340. }
  341. #pragma mark - Internal Helpers
  342. - (void)assertTokenFetchOperationRequestContainsFirebaseUserAgentAndHeartbeatInfoCode:
  343. (FIRDailyHeartbeatCode)heartbeatInfoCode {
  344. XCTestExpectation *completionExpectation =
  345. [self expectationWithDescription:@"completionExpectation"];
  346. FIRHeartbeatLoggerFake *heartbeatLoggerFake = [[FIRHeartbeatLoggerFake alloc] init];
  347. XCTestExpectation *heartbeatExpectation =
  348. [self expectationWithDescription:@"heartbeatExpectation"];
  349. heartbeatLoggerFake.onHeartbeatCodeForTodayHandler = ^FIRDailyHeartbeatCode {
  350. [heartbeatExpectation fulfill];
  351. return heartbeatInfoCode;
  352. };
  353. FIRMessagingCheckinPreferences *checkinPreferences =
  354. [self setCheckinPreferencesWithLastCheckinTime:0];
  355. FIRMessagingTokenFetchOperation *operation =
  356. [[FIRMessagingTokenFetchOperation alloc] initWithAuthorizedEntity:kAuthorizedEntity
  357. scope:kScope
  358. options:nil
  359. checkinPreferences:checkinPreferences
  360. instanceID:self.instanceID
  361. heartbeatLogger:heartbeatLoggerFake];
  362. NSURL *expectedRequestURL = [NSURL URLWithString:FIRMessagingTokenRegisterServer()];
  363. NSHTTPURLResponse *expectedResponse = [[NSHTTPURLResponse alloc] initWithURL:expectedRequestURL
  364. statusCode:200
  365. HTTPVersion:@"HTTP/1.1"
  366. headerFields:nil];
  367. [FIRURLSessionOCMockStub
  368. stubURLSessionDataTaskWithResponse:expectedResponse
  369. body:[self dataForResponseWithValidToken:NO]
  370. error:nil
  371. URLSessionMock:self.URLSessionMock
  372. requestValidationBlock:^BOOL(NSURLRequest *_Nonnull sentRequest) {
  373. NSString *userAgentValue =
  374. sentRequest.allHTTPHeaderFields[kFIRMessagingFirebaseUserAgentKey];
  375. XCTAssertEqualObjects(userAgentValue, [FIRApp firebaseUserAgent]);
  376. NSString *heartBeatCode =
  377. sentRequest.allHTTPHeaderFields[kFIRMessagingFirebaseHeartbeatKey];
  378. // It is expected that the global heartbeat matches passed in
  379. // `heartbeatInfoCode`.
  380. XCTAssertEqual(heartBeatCode.integerValue, heartbeatInfoCode);
  381. [completionExpectation fulfill];
  382. return YES;
  383. }];
  384. [operation start];
  385. [self waitForExpectationsWithTimeout:0.25
  386. handler:^(NSError *_Nullable error) {
  387. XCTAssertNil(error.localizedDescription);
  388. }];
  389. }
  390. - (NSData *)dataForResponseWithValidToken:(BOOL)validToken {
  391. NSString *response;
  392. if (validToken) {
  393. response = [NSString stringWithFormat:@"token=%@", kRegistrationToken];
  394. } else {
  395. response = @"Error=RST";
  396. }
  397. return [response dataUsingEncoding:NSUTF8StringEncoding];
  398. }
  399. - (FIRMessagingCheckinPreferences *)setCheckinPreferencesWithLastCheckinTime:(int64_t)time {
  400. FIRMessagingCheckinPreferences *checkinPreferences =
  401. [[FIRMessagingCheckinPreferences alloc] initWithDeviceID:kDeviceID secretToken:kSecretToken];
  402. NSDictionary *checkinPlistContents = @{
  403. kFIRMessagingDigestStringKey : kDigestString,
  404. kFIRMessagingVersionInfoStringKey : kVersionInfoString,
  405. kFIRMessagingLastCheckinTimeKey : @(time)
  406. };
  407. [checkinPreferences updateWithCheckinPlistContents:checkinPlistContents];
  408. // manually initialize the checkin preferences
  409. self.checkinPreferences = checkinPreferences;
  410. return checkinPreferences;
  411. }
  412. - (void)stubInstallations {
  413. _mockInstallations = OCMClassMock([FIRInstallations class]);
  414. OCMStub([_mockInstallations installations]).andReturn(_mockInstallations);
  415. FIRInstallationsAuthTokenResult *authToken =
  416. [[FIRInstallationsAuthTokenResult alloc] initWithToken:@"fis-auth-token"
  417. expirationDate:[NSDate distantFuture]];
  418. id authTokenWithCompletionArg = [OCMArg invokeBlockWithArgs:authToken, [NSNull null], nil];
  419. OCMStub([_mockInstallations authTokenWithCompletion:authTokenWithCompletionArg]);
  420. }
  421. @end