FIRMessagingTokenOperationsTest.m 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  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 its networking context.
  82. [self doesNotRecognizeSelector:_cmd];
  83. }
  84. - (NSString *_Nullable)headerValue {
  85. return @"unimplemented";
  86. }
  87. @end
  88. #pragma mark - FIRMessagingTokenOperationsTest
  89. @interface FIRMessagingTokenOperationsTest : XCTestCase
  90. @property(nonatomic) id URLSessionMock;
  91. @property(strong, readonly, nonatomic) FIRMessagingAuthService *authService;
  92. @property(strong, readonly, nonatomic) id mockAuthService;
  93. @property(strong, readonly, nonatomic) id mockTokenStore;
  94. @property(strong, readonly, nonatomic) FIRMessagingCheckinService *checkinService;
  95. @property(strong, readonly, nonatomic) id mockCheckinService;
  96. @property(strong, readonly, nonatomic) id mockInstallations;
  97. @property(strong, readonly, nonatomic) id mockHeartbeatInfo;
  98. @property(strong, readonly, nonatomic) NSString *instanceID;
  99. @property(nonatomic, readwrite, strong) FIRMessagingCheckinPreferences *checkinPreferences;
  100. @end
  101. @implementation FIRMessagingTokenOperationsTest
  102. - (void)setUp {
  103. [super setUp];
  104. // Stub NSURLSession constructor before instantiating FIRMessagingCheckinService to inject
  105. // URLSessionMock.
  106. self.URLSessionMock = OCMClassMock([NSURLSession class]);
  107. OCMStub(ClassMethod([self.URLSessionMock sessionWithConfiguration:[OCMArg any]]))
  108. .andReturn(self.URLSessionMock);
  109. _mockTokenStore = OCMClassMock([FIRMessagingTokenStore class]);
  110. _checkinService = [[FIRMessagingCheckinService alloc] init];
  111. _mockCheckinService = OCMPartialMock(_checkinService);
  112. _authService = [[FIRMessagingAuthService alloc] init];
  113. _instanceID = @"instanceID";
  114. // `FIRMessagingTokenOperation` uses `FIRInstallations` under the hood to get FIS auth token.
  115. // Stub `FIRInstallations` to avoid using a real object.
  116. [self stubInstallations];
  117. }
  118. - (void)tearDown {
  119. _authService = nil;
  120. [_mockCheckinService stopMocking];
  121. _mockCheckinService = nil;
  122. _checkinService = nil;
  123. _mockTokenStore = nil;
  124. [_mockInstallations stopMocking];
  125. [_mockHeartbeatInfo stopMocking];
  126. }
  127. - (void)testThatTokenOperationsAuthHeaderStringMatchesCheckin {
  128. int64_t tenHoursAgo = FIRMessagingCurrentTimestampInMilliseconds() - 10 * 60 * 60 * 1000;
  129. FIRMessagingCheckinPreferences *checkin =
  130. [self setCheckinPreferencesWithLastCheckinTime:tenHoursAgo];
  131. NSString *expectedAuthHeader = [FIRMessagingTokenOperation HTTPAuthHeaderFromCheckin:checkin];
  132. XCTestExpectation *authHeaderMatchesCheckinExpectation =
  133. [self expectationWithDescription:@"Auth header string in request matches checkin info"];
  134. FIRMessagingTokenFetchOperation *operation = [[FIRMessagingTokenFetchOperation alloc]
  135. initWithAuthorizedEntity:kAuthorizedEntity
  136. scope:kScope
  137. options:nil
  138. checkinPreferences:checkin
  139. instanceID:self.instanceID
  140. heartbeatLogger:[[FIRHeartbeatLoggerFake alloc] init]];
  141. NSURL *expectedRequestURL = [NSURL URLWithString:FIRMessagingTokenRegisterServer()];
  142. NSHTTPURLResponse *expectedResponse = [[NSHTTPURLResponse alloc] initWithURL:expectedRequestURL
  143. statusCode:200
  144. HTTPVersion:@"HTTP/1.1"
  145. headerFields:nil];
  146. [FIRURLSessionOCMockStub
  147. stubURLSessionDataTaskWithResponse:expectedResponse
  148. body:[self dataForResponseWithValidToken:YES]
  149. error:nil
  150. URLSessionMock:self.URLSessionMock
  151. requestValidationBlock:^BOOL(NSURLRequest *_Nonnull sentRequest) {
  152. NSDictionary<NSString *, NSString *> *headers = sentRequest.allHTTPHeaderFields;
  153. NSString *authHeader = headers[@"Authorization"];
  154. if ([authHeader isEqualToString:expectedAuthHeader]) {
  155. [authHeaderMatchesCheckinExpectation fulfill];
  156. }
  157. return YES;
  158. }];
  159. [operation start];
  160. [self waitForExpectationsWithTimeout:0.25
  161. handler:^(NSError *_Nullable error) {
  162. XCTAssertNil(error.localizedDescription);
  163. }];
  164. }
  165. - (void)testThatTokenOperationWithoutCheckInFails {
  166. // If asserts are enabled, test for the assert to be thrown, otherwise check for the resulting
  167. // error in the completion handler.
  168. XCTestExpectation *failedExpectation =
  169. [self expectationWithDescription:@"Operation failed without checkin info"];
  170. // This will return hasCheckinInfo == NO
  171. FIRMessagingCheckinPreferences *emptyCheckinPreferences =
  172. [[FIRMessagingCheckinPreferences alloc] initWithDeviceID:@"" secretToken:@""];
  173. FIRMessagingTokenOperation *operation =
  174. [[FIRMessagingTokenOperation alloc] initWithAction:FIRMessagingTokenActionFetch
  175. forAuthorizedEntity:kAuthorizedEntity
  176. scope:kScope
  177. options:nil
  178. checkinPreferences:emptyCheckinPreferences
  179. instanceID:self.instanceID
  180. heartbeatLogger:[[FIRHeartbeatLoggerFake alloc] init]];
  181. [operation addCompletionHandler:^(FIRMessagingTokenOperationResult result,
  182. NSString *_Nullable token, NSError *_Nullable error) {
  183. [failedExpectation fulfill];
  184. }];
  185. @try {
  186. [operation start];
  187. } @catch (NSException *exception) {
  188. if (exception.name == NSInternalInconsistencyException) {
  189. [failedExpectation fulfill];
  190. }
  191. } @finally {
  192. }
  193. [self waitForExpectationsWithTimeout:0.25
  194. handler:^(NSError *_Nullable error) {
  195. XCTAssertNil(error.localizedDescription);
  196. }];
  197. }
  198. - (void)testThatAnAlreadyCancelledOperationFinishesWithoutStarting {
  199. XCTestExpectation *cancelledExpectation =
  200. [self expectationWithDescription:@"Operation finished as cancelled"];
  201. XCTestExpectation *didNotCallPerform =
  202. [self expectationWithDescription:@"Did not call performTokenOperation"];
  203. int64_t tenHoursAgo = FIRMessagingCurrentTimestampInMilliseconds() - 10 * 60 * 60 * 1000;
  204. FIRMessagingCheckinPreferences *checkinPreferences =
  205. [self setCheckinPreferencesWithLastCheckinTime:tenHoursAgo];
  206. FIRMessagingTokenOperationFake *operation =
  207. [[FIRMessagingTokenOperationFake alloc] initWithAction:FIRMessagingTokenActionFetch
  208. forAuthorizedEntity:kAuthorizedEntity
  209. scope:kScope
  210. options:nil
  211. checkinPreferences:checkinPreferences
  212. instanceID:self.instanceID
  213. heartbeatLogger:[[FIRHeartbeatLoggerFake alloc] init]];
  214. operation.performWasCalled = NO;
  215. __weak FIRMessagingTokenOperationFake *weakOperation = operation;
  216. [operation addCompletionHandler:^(FIRMessagingTokenOperationResult result,
  217. NSString *_Nullable token, NSError *_Nullable error) {
  218. if (result == FIRMessagingTokenOperationCancelled) {
  219. [cancelledExpectation fulfill];
  220. }
  221. if (!weakOperation.performWasCalled) {
  222. [didNotCallPerform fulfill];
  223. }
  224. }];
  225. [operation cancel];
  226. [operation start];
  227. [self waitForExpectationsWithTimeout:0.25
  228. handler:^(NSError *_Nullable error) {
  229. XCTAssertNil(error.localizedDescription);
  230. }];
  231. }
  232. - (void)testThatOptionsDictionaryIsIncludedWithFetchRequest {
  233. XCTestExpectation *optionsIncludedExpectation =
  234. [self expectationWithDescription:@"Options keys were included in token URL request"];
  235. int64_t tenHoursAgo = FIRMessagingCurrentTimestampInMilliseconds() - 10 * 60 * 60 * 1000;
  236. FIRMessagingCheckinPreferences *checkinPreferences =
  237. [self setCheckinPreferencesWithLastCheckinTime:tenHoursAgo];
  238. NSData *fakeDeviceToken = [@"fakeAPNSToken" dataUsingEncoding:NSUTF8StringEncoding];
  239. BOOL isSandbox = NO;
  240. NSString *apnsTupleString =
  241. FIRMessagingAPNSTupleStringForTokenAndServerType(fakeDeviceToken, isSandbox);
  242. NSDictionary *options = @{
  243. kFIRMessagingTokenOptionsFirebaseAppIDKey : @"fakeGMPAppID",
  244. kFIRMessagingTokenOptionsAPNSKey : fakeDeviceToken,
  245. kFIRMessagingTokenOptionsAPNSIsSandboxKey : @(isSandbox),
  246. };
  247. FIRMessagingTokenFetchOperation *operation = [[FIRMessagingTokenFetchOperation alloc]
  248. initWithAuthorizedEntity:kAuthorizedEntity
  249. scope:kScope
  250. options:options
  251. checkinPreferences:checkinPreferences
  252. instanceID:self.instanceID
  253. heartbeatLogger:[[FIRHeartbeatLoggerFake alloc] init]];
  254. NSURL *expectedRequestURL = [NSURL URLWithString:FIRMessagingTokenRegisterServer()];
  255. NSHTTPURLResponse *expectedResponse = [[NSHTTPURLResponse alloc] initWithURL:expectedRequestURL
  256. statusCode:200
  257. HTTPVersion:@"HTTP/1.1"
  258. headerFields:nil];
  259. [FIRURLSessionOCMockStub
  260. stubURLSessionDataTaskWithResponse:expectedResponse
  261. body:[self dataForResponseWithValidToken:YES]
  262. error:nil
  263. URLSessionMock:self.URLSessionMock
  264. requestValidationBlock:^BOOL(NSURLRequest *_Nonnull sentRequest) {
  265. NSString *query = [[NSString alloc] initWithData:sentRequest.HTTPBody
  266. encoding:NSUTF8StringEncoding];
  267. NSString *gmpAppIDQueryTuple = [NSString
  268. stringWithFormat:@"%@=%@", kFIRMessagingTokenOptionsFirebaseAppIDKey,
  269. options[kFIRMessagingTokenOptionsFirebaseAppIDKey]];
  270. NSRange gmpAppIDRange = [query rangeOfString:gmpAppIDQueryTuple];
  271. NSString *apnsQueryTuple =
  272. [NSString stringWithFormat:@"%@=%@", kFIRMessagingTokenOptionsAPNSKey,
  273. apnsTupleString];
  274. NSRange apnsRange = [query rangeOfString:apnsQueryTuple];
  275. if (gmpAppIDRange.location != NSNotFound && apnsRange.location != NSNotFound) {
  276. [optionsIncludedExpectation fulfill];
  277. }
  278. return YES;
  279. }];
  280. [operation start];
  281. [self waitForExpectationsWithTimeout:0.25
  282. handler:^(NSError *_Nullable error) {
  283. XCTAssertNil(error.localizedDescription);
  284. }];
  285. }
  286. - (void)testServerResetCommand {
  287. XCTestExpectation *shouldResetIdentityExpectation =
  288. [self expectationWithDescription:
  289. @"When server sends down RST error, clients should return reset identity error."];
  290. int64_t tenHoursAgo = FIRMessagingCurrentTimestampInMilliseconds() - 10 * 60 * 60 * 1000;
  291. FIRMessagingCheckinPreferences *checkinPreferences =
  292. [self setCheckinPreferencesWithLastCheckinTime:tenHoursAgo];
  293. FIRMessagingTokenFetchOperation *operation = [[FIRMessagingTokenFetchOperation alloc]
  294. initWithAuthorizedEntity:kAuthorizedEntity
  295. scope:kScope
  296. options:nil
  297. checkinPreferences:checkinPreferences
  298. instanceID:self.instanceID
  299. heartbeatLogger:[[FIRHeartbeatLoggerFake alloc] init]];
  300. NSURL *expectedRequestURL = [NSURL URLWithString:FIRMessagingTokenRegisterServer()];
  301. NSHTTPURLResponse *expectedResponse = [[NSHTTPURLResponse alloc] initWithURL:expectedRequestURL
  302. statusCode:200
  303. HTTPVersion:@"HTTP/1.1"
  304. headerFields:nil];
  305. [FIRURLSessionOCMockStub
  306. stubURLSessionDataTaskWithResponse:expectedResponse
  307. body:[self dataForResponseWithValidToken:NO]
  308. error:nil
  309. URLSessionMock:self.URLSessionMock
  310. requestValidationBlock:^BOOL(NSURLRequest *_Nonnull sentRequest) {
  311. return YES;
  312. }];
  313. [operation addCompletionHandler:^(FIRMessagingTokenOperationResult result,
  314. NSString *_Nullable token, NSError *_Nullable error) {
  315. XCTAssertEqual(result, FIRMessagingTokenOperationError);
  316. XCTAssertNotNil(error);
  317. XCTAssertEqual(error.code, kFIRMessagingErrorCodeInvalidIdentity);
  318. [shouldResetIdentityExpectation fulfill];
  319. }];
  320. [operation start];
  321. [self waitForExpectationsWithTimeout:0.25
  322. handler:^(NSError *_Nullable error) {
  323. XCTAssertNil(error.localizedDescription);
  324. }];
  325. }
  326. - (void)testHTTPAuthHeaderGenerationFromCheckin {
  327. FIRMessagingCheckinPreferences *checkinPreferences =
  328. [[FIRMessagingCheckinPreferences alloc] initWithDeviceID:kDeviceID secretToken:kSecretToken];
  329. NSString *expectedHeader =
  330. [NSString stringWithFormat:@"AidLogin %@:%@", checkinPreferences.deviceID,
  331. checkinPreferences.secretToken];
  332. NSString *generatedHeader =
  333. [FIRMessagingTokenOperation HTTPAuthHeaderFromCheckin:checkinPreferences];
  334. XCTAssertEqualObjects(generatedHeader, expectedHeader);
  335. }
  336. - (void)testTokenFetchOperationFirebaseUserAgentAndHeartbeatHeader_WhenHeartbeatNeedsSending {
  337. [self assertTokenFetchOperationRequestContainsFirebaseUserAgentAndHeartbeatInfoCode:
  338. FIRDailyHeartbeatCodeSome];
  339. }
  340. - (void)testTokenFetchOperationFirebaseUserAgentAndHeartbeatHeader_WhenNoHeartbeatNeedsSending {
  341. [self assertTokenFetchOperationRequestContainsFirebaseUserAgentAndHeartbeatInfoCode:
  342. FIRDailyHeartbeatCodeNone];
  343. }
  344. #pragma mark - Internal Helpers
  345. - (void)assertTokenFetchOperationRequestContainsFirebaseUserAgentAndHeartbeatInfoCode:
  346. (FIRDailyHeartbeatCode)heartbeatInfoCode {
  347. XCTestExpectation *completionExpectation =
  348. [self expectationWithDescription:@"completionExpectation"];
  349. FIRHeartbeatLoggerFake *heartbeatLoggerFake = [[FIRHeartbeatLoggerFake alloc] init];
  350. XCTestExpectation *heartbeatExpectation =
  351. [self expectationWithDescription:@"heartbeatExpectation"];
  352. heartbeatLoggerFake.onHeartbeatCodeForTodayHandler = ^FIRDailyHeartbeatCode {
  353. [heartbeatExpectation fulfill];
  354. return heartbeatInfoCode;
  355. };
  356. FIRMessagingCheckinPreferences *checkinPreferences =
  357. [self setCheckinPreferencesWithLastCheckinTime:0];
  358. FIRMessagingTokenFetchOperation *operation =
  359. [[FIRMessagingTokenFetchOperation alloc] initWithAuthorizedEntity:kAuthorizedEntity
  360. scope:kScope
  361. options:nil
  362. checkinPreferences:checkinPreferences
  363. instanceID:self.instanceID
  364. heartbeatLogger:heartbeatLoggerFake];
  365. NSURL *expectedRequestURL = [NSURL URLWithString:FIRMessagingTokenRegisterServer()];
  366. NSHTTPURLResponse *expectedResponse = [[NSHTTPURLResponse alloc] initWithURL:expectedRequestURL
  367. statusCode:200
  368. HTTPVersion:@"HTTP/1.1"
  369. headerFields:nil];
  370. [FIRURLSessionOCMockStub
  371. stubURLSessionDataTaskWithResponse:expectedResponse
  372. body:[self dataForResponseWithValidToken:NO]
  373. error:nil
  374. URLSessionMock:self.URLSessionMock
  375. requestValidationBlock:^BOOL(NSURLRequest *_Nonnull sentRequest) {
  376. NSString *userAgentValue =
  377. sentRequest.allHTTPHeaderFields[kFIRMessagingFirebaseUserAgentKey];
  378. XCTAssertEqualObjects(userAgentValue, [FIRApp firebaseUserAgent]);
  379. NSString *heartBeatCode =
  380. sentRequest.allHTTPHeaderFields[kFIRMessagingFirebaseHeartbeatKey];
  381. // It is expected that the global heartbeat matches passed in
  382. // `heartbeatInfoCode`.
  383. XCTAssertEqual(heartBeatCode.integerValue, heartbeatInfoCode);
  384. [completionExpectation fulfill];
  385. return YES;
  386. }];
  387. [operation start];
  388. [self waitForExpectationsWithTimeout:0.25
  389. handler:^(NSError *_Nullable error) {
  390. XCTAssertNil(error.localizedDescription);
  391. }];
  392. }
  393. - (NSData *)dataForResponseWithValidToken:(BOOL)validToken {
  394. NSString *response;
  395. if (validToken) {
  396. response = [NSString stringWithFormat:@"token=%@", kRegistrationToken];
  397. } else {
  398. response = @"Error=RST";
  399. }
  400. return [response dataUsingEncoding:NSUTF8StringEncoding];
  401. }
  402. - (FIRMessagingCheckinPreferences *)setCheckinPreferencesWithLastCheckinTime:(int64_t)time {
  403. FIRMessagingCheckinPreferences *checkinPreferences =
  404. [[FIRMessagingCheckinPreferences alloc] initWithDeviceID:kDeviceID secretToken:kSecretToken];
  405. NSDictionary *checkinPlistContents = @{
  406. kFIRMessagingDigestStringKey : kDigestString,
  407. kFIRMessagingVersionInfoStringKey : kVersionInfoString,
  408. kFIRMessagingLastCheckinTimeKey : @(time)
  409. };
  410. [checkinPreferences updateWithCheckinPlistContents:checkinPlistContents];
  411. // manually initialize the checkin preferences
  412. self.checkinPreferences = checkinPreferences;
  413. return checkinPreferences;
  414. }
  415. - (void)stubInstallations {
  416. _mockInstallations = OCMClassMock([FIRInstallations class]);
  417. OCMStub([_mockInstallations installations]).andReturn(_mockInstallations);
  418. FIRInstallationsAuthTokenResult *authToken =
  419. [[FIRInstallationsAuthTokenResult alloc] initWithToken:@"fis-auth-token"
  420. expirationDate:[NSDate distantFuture]];
  421. id authTokenWithCompletionArg = [OCMArg invokeBlockWithArgs:authToken, [NSNull null], nil];
  422. OCMStub([_mockInstallations authTokenWithCompletion:authTokenWithCompletionArg]);
  423. }
  424. @end