FIRAppCheckTests.m 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  1. /*
  2. * Copyright 2020 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 "FBLPromise+Testing.h"
  19. #import <FirebaseAppCheck/FirebaseAppCheck.h>
  20. #import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheck.h"
  21. #import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckProvider.h"
  22. #import "FirebaseAppCheck/Sources/Interop/FIRAppCheckInterop.h"
  23. #import "FirebaseAppCheck/Sources/Interop/FIRAppCheckTokenResultInterop.h"
  24. #import "FirebaseAppCheck/Sources/Core/FIRAppCheckSettings.h"
  25. #import "FirebaseAppCheck/Sources/Core/FIRAppCheckTokenResult.h"
  26. #import "FirebaseAppCheck/Sources/Core/Storage/FIRAppCheckStorage.h"
  27. #import "FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefresher.h"
  28. #import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h"
  29. // The FAC token value returned when an error occurs.
  30. static NSString *const kDummyToken = @"eyJlcnJvciI6IlVOS05PV05fRVJST1IifQ==";
  31. @interface FIRAppCheck (Tests) <FIRAppCheckInterop>
  32. - (instancetype)initWithAppName:(NSString *)appName
  33. appCheckProvider:(id<FIRAppCheckProvider>)appCheckProvider
  34. storage:(id<FIRAppCheckStorageProtocol>)storage
  35. tokenRefresher:(id<FIRAppCheckTokenRefresherProtocol>)tokenRefresher
  36. notificationCenter:(NSNotificationCenter *)notificationCenter
  37. settings:(id<FIRAppCheckSettingsProtocol>)settings;
  38. - (nullable instancetype)initWithApp:(FIRApp *)app;
  39. @end
  40. @interface FIRAppCheckTests : XCTestCase
  41. @property(nonatomic) NSString *appName;
  42. @property(nonatomic) OCMockObject<FIRAppCheckStorageProtocol> *mockStorage;
  43. @property(nonatomic) OCMockObject<FIRAppCheckProvider> *mockAppCheckProvider;
  44. @property(nonatomic) OCMockObject<FIRAppCheckTokenRefresherProtocol> *mockTokenRefresher;
  45. @property(nonatomic) OCMockObject<FIRAppCheckSettingsProtocol> *mockSettings;
  46. @property(nonatomic) NSNotificationCenter *notificationCenter;
  47. @property(nonatomic) FIRAppCheck<FIRAppCheckInterop> *appCheck;
  48. @property(nonatomic, copy, nullable) FIRAppCheckTokenRefreshBlock tokenRefreshHandler;
  49. @end
  50. @implementation FIRAppCheckTests
  51. - (void)setUp {
  52. [super setUp];
  53. self.appName = @"FIRAppCheckTests";
  54. self.mockStorage = OCMProtocolMock(@protocol(FIRAppCheckStorageProtocol));
  55. self.mockAppCheckProvider = OCMProtocolMock(@protocol(FIRAppCheckProvider));
  56. self.mockTokenRefresher = OCMProtocolMock(@protocol(FIRAppCheckTokenRefresherProtocol));
  57. self.mockSettings = OCMProtocolMock(@protocol(FIRAppCheckSettingsProtocol));
  58. self.notificationCenter = [[NSNotificationCenter alloc] init];
  59. [self stubSetTokenRefreshHandler];
  60. self.appCheck = [[FIRAppCheck alloc] initWithAppName:self.appName
  61. appCheckProvider:self.mockAppCheckProvider
  62. storage:self.mockStorage
  63. tokenRefresher:self.mockTokenRefresher
  64. notificationCenter:self.notificationCenter
  65. settings:self.mockSettings];
  66. }
  67. - (void)tearDown {
  68. self.appCheck = nil;
  69. [self.mockAppCheckProvider stopMocking];
  70. self.mockAppCheckProvider = nil;
  71. [self.mockStorage stopMocking];
  72. self.mockStorage = nil;
  73. [self.mockTokenRefresher stopMocking];
  74. self.mockTokenRefresher = nil;
  75. [super tearDown];
  76. }
  77. - (void)testInitWithApp {
  78. NSString *googleAppID = @"testInitWithApp_googleAppID";
  79. NSString *appName = @"testInitWithApp_appName";
  80. NSString *appGroupID = @"testInitWithApp_appGroupID";
  81. // 1. Stub FIRApp and validate usage.
  82. id mockApp = OCMStrictClassMock([FIRApp class]);
  83. id mockAppOptions = OCMStrictClassMock([FIROptions class]);
  84. OCMStub([mockApp name]).andReturn(appName);
  85. OCMStub([(FIRApp *)mockApp options]).andReturn(mockAppOptions);
  86. OCMExpect([mockAppOptions googleAppID]).andReturn(googleAppID);
  87. OCMExpect([mockAppOptions appGroupID]).andReturn(appGroupID);
  88. // 2. Stub FIRAppCheckTokenRefresher and validate usage.
  89. id mockTokenRefresher = OCMClassMock([FIRAppCheckTokenRefresher class]);
  90. OCMExpect([mockTokenRefresher alloc]).andReturn(mockTokenRefresher);
  91. id refresherDateValidator = [OCMArg checkWithBlock:^BOOL(NSDate *tokenExpirationDate) {
  92. NSTimeInterval accuracy = 1;
  93. XCTAssertLessThanOrEqual(ABS([tokenExpirationDate timeIntervalSinceNow]), accuracy);
  94. return YES;
  95. }];
  96. id settingsValidator = [OCMArg checkWithBlock:^BOOL(id obj) {
  97. XCTAssert([obj isKindOfClass:[FIRAppCheckSettings class]]);
  98. return YES;
  99. }];
  100. OCMExpect([mockTokenRefresher initWithTokenExpirationDate:refresherDateValidator
  101. tokenExpirationThreshold:5 * 60
  102. settings:settingsValidator])
  103. .andReturn(mockTokenRefresher);
  104. OCMExpect([mockTokenRefresher setTokenRefreshHandler:[OCMArg any]]);
  105. // 3. Stub FIRAppCheckStorage and validate usage.
  106. id mockStorage = OCMClassMock([FIRAppCheckStorage class]);
  107. OCMExpect([mockStorage alloc]).andReturn(mockStorage);
  108. OCMExpect([mockStorage initWithAppName:appName appID:googleAppID accessGroup:appGroupID])
  109. .andReturn(mockStorage);
  110. // 4. Stub attestation provider.
  111. OCMockObject<FIRAppCheckProviderFactory> *mockProviderFactory =
  112. OCMProtocolMock(@protocol(FIRAppCheckProviderFactory));
  113. OCMockObject<FIRAppCheckProvider> *mockProvider = OCMProtocolMock(@protocol(FIRAppCheckProvider));
  114. OCMExpect([mockProviderFactory createProviderWithApp:mockApp]).andReturn(mockProvider);
  115. [FIRAppCheck setAppCheckProviderFactory:mockProviderFactory];
  116. // 5. Call init.
  117. FIRAppCheck *appCheck = [[FIRAppCheck alloc] initWithApp:mockApp];
  118. XCTAssert([appCheck isKindOfClass:[FIRAppCheck class]]);
  119. // 6. Verify mocks.
  120. OCMVerifyAll(mockApp);
  121. OCMVerifyAll(mockAppOptions);
  122. OCMVerifyAll(mockTokenRefresher);
  123. OCMVerifyAll(mockStorage);
  124. OCMVerifyAll(mockProviderFactory);
  125. OCMVerifyAll(mockProvider);
  126. // 7. Stop mocking real class mocks.
  127. [mockApp stopMocking];
  128. mockApp = nil;
  129. [mockAppOptions stopMocking];
  130. mockAppOptions = nil;
  131. [mockTokenRefresher stopMocking];
  132. mockTokenRefresher = nil;
  133. [mockStorage stopMocking];
  134. mockStorage = nil;
  135. }
  136. - (void)testAppCheckDefaultInstance {
  137. // Should throw an exception when the default app is not configured.
  138. XCTAssertThrows([FIRAppCheck appCheck]);
  139. // Configure default FIRApp.
  140. FIROptions *options =
  141. [[FIROptions alloc] initWithGoogleAppID:@"1:100000000000:ios:aaaaaaaaaaaaaaaaaaaaaaaa"
  142. GCMSenderID:@"sender_id"];
  143. options.APIKey = @"api_key";
  144. options.projectID = @"project_id";
  145. [FIRApp configureWithOptions:options];
  146. // Check.
  147. XCTAssertNotNil([FIRAppCheck appCheck]);
  148. [FIRApp resetApps];
  149. }
  150. - (void)testAppCheckInstanceForApp {
  151. FIROptions *options =
  152. [[FIROptions alloc] initWithGoogleAppID:@"1:100000000000:ios:aaaaaaaaaaaaaaaaaaaaaaaa"
  153. GCMSenderID:@"sender_id"];
  154. options.APIKey = @"api_key";
  155. options.projectID = @"project_id";
  156. [FIRApp configureWithName:@"testAppCheckInstanceForApp" options:options];
  157. FIRApp *app = [FIRApp appNamed:@"testAppCheckInstanceForApp"];
  158. XCTAssertNotNil(app);
  159. XCTAssertNotNil([FIRAppCheck appCheckWithApp:app]);
  160. [FIRApp resetApps];
  161. }
  162. - (void)testGetToken_WhenNoCache_Success {
  163. // 1. Expect token to be requested from storage.
  164. OCMExpect([self.mockStorage getToken]).andReturn([FBLPromise resolvedWith:nil]);
  165. // 2. Expect token requested from app check provider.
  166. FIRAppCheckToken *tokenToReturn = [[FIRAppCheckToken alloc] initWithToken:@"valid"
  167. expirationDate:[NSDate distantFuture]];
  168. id completionArg = [OCMArg invokeBlockWithArgs:tokenToReturn, [NSNull null], nil];
  169. OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]);
  170. // 3. Expect new token to be stored.
  171. OCMExpect([self.mockStorage setToken:tokenToReturn])
  172. .andReturn([FBLPromise resolvedWith:tokenToReturn]);
  173. // 4. Expect token update notification to be sent.
  174. XCTestExpectation *notificationExpectation =
  175. [self tokenUpdateNotificationWithExpectedToken:tokenToReturn.token];
  176. // 5. Request token.
  177. XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"];
  178. [self.appCheck getTokenForcingRefresh:NO
  179. completion:^(id<FIRAppCheckTokenResultInterop> tokenResult) {
  180. [getTokenExpectation fulfill];
  181. XCTAssertNotNil(tokenResult);
  182. XCTAssertEqualObjects(tokenResult.token, tokenToReturn.token);
  183. XCTAssertNil(tokenResult.error);
  184. }];
  185. // 6. Wait for expectations and validate mocks.
  186. [self waitForExpectations:@[ notificationExpectation, getTokenExpectation ] timeout:0.5];
  187. OCMVerifyAll(self.mockStorage);
  188. OCMVerifyAll(self.mockAppCheckProvider);
  189. }
  190. - (void)testGetToken_WhenCachedTokenIsValid_Success {
  191. [self assertGetToken_WhenCachedTokenIsValid_Success];
  192. }
  193. - (void)testGetTokenForcingRefresh_WhenCachedTokenIsValid_Success {
  194. // 1. Don't expect token to be requested from storage.
  195. OCMReject([self.mockStorage getToken]);
  196. // 2. Expect token requested from app check provider.
  197. FIRAppCheckToken *tokenToReturn = [[FIRAppCheckToken alloc] initWithToken:@"valid"
  198. expirationDate:[NSDate distantFuture]];
  199. id completionArg = [OCMArg invokeBlockWithArgs:tokenToReturn, [NSNull null], nil];
  200. OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]);
  201. // 3. Expect new token to be stored.
  202. OCMExpect([self.mockStorage setToken:tokenToReturn])
  203. .andReturn([FBLPromise resolvedWith:tokenToReturn]);
  204. // 4. Expect token update notification to be sent.
  205. XCTestExpectation *notificationExpectation =
  206. [self tokenUpdateNotificationWithExpectedToken:tokenToReturn.token];
  207. // 5. Request token.
  208. XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"];
  209. [self.appCheck getTokenForcingRefresh:YES
  210. completion:^(id<FIRAppCheckTokenResultInterop> tokenResult) {
  211. [getTokenExpectation fulfill];
  212. XCTAssertNotNil(tokenResult);
  213. XCTAssertEqualObjects(tokenResult.token, tokenToReturn.token);
  214. XCTAssertNil(tokenResult.error);
  215. }];
  216. // 6. Wait for expectations and validate mocks.
  217. [self waitForExpectations:@[ notificationExpectation, getTokenExpectation ] timeout:0.5];
  218. OCMVerifyAll(self.mockStorage);
  219. OCMVerifyAll(self.mockAppCheckProvider);
  220. }
  221. - (void)testGetToken_WhenCachedTokenExpired_Success {
  222. FIRAppCheckToken *cachedToken = [[FIRAppCheckToken alloc] initWithToken:@"valid"
  223. expirationDate:[NSDate date]];
  224. // 1. Expect token to be requested from storage.
  225. OCMExpect([self.mockStorage getToken]).andReturn([FBLPromise resolvedWith:cachedToken]);
  226. // 2. Expect token requested from app check provider.
  227. FIRAppCheckToken *tokenToReturn = [[FIRAppCheckToken alloc] initWithToken:@"valid"
  228. expirationDate:[NSDate distantFuture]];
  229. id completionArg = [OCMArg invokeBlockWithArgs:tokenToReturn, [NSNull null], nil];
  230. OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]);
  231. // 3. Expect new token to be stored.
  232. OCMExpect([self.mockStorage setToken:tokenToReturn])
  233. .andReturn([FBLPromise resolvedWith:tokenToReturn]);
  234. // 4. Expect token update notification to be sent.
  235. XCTestExpectation *notificationExpectation =
  236. [self tokenUpdateNotificationWithExpectedToken:tokenToReturn.token];
  237. // 5. Request token.
  238. XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"];
  239. [self.appCheck getTokenForcingRefresh:NO
  240. completion:^(id<FIRAppCheckTokenResultInterop> tokenResult) {
  241. [getTokenExpectation fulfill];
  242. XCTAssertNotNil(tokenResult);
  243. XCTAssertEqualObjects(tokenResult.token, tokenToReturn.token);
  244. XCTAssertNil(tokenResult.error);
  245. }];
  246. // 6. Wait for expectations and validate mocks.
  247. [self waitForExpectations:@[ notificationExpectation, getTokenExpectation ] timeout:0.5];
  248. OCMVerifyAll(self.mockStorage);
  249. OCMVerifyAll(self.mockAppCheckProvider);
  250. }
  251. - (void)testGetToken_AppCheckProviderError {
  252. NSDate *soonExpiringTokenDate = [NSDate dateWithTimeIntervalSinceNow:4.5 * 60];
  253. FIRAppCheckToken *cachedToken = [[FIRAppCheckToken alloc] initWithToken:@"valid"
  254. expirationDate:soonExpiringTokenDate];
  255. // 1. Expect token to be requested from storage.
  256. OCMExpect([self.mockStorage getToken]).andReturn([FBLPromise resolvedWith:cachedToken]);
  257. // 2. Expect token requested from app check provider.
  258. NSError *providerError = [NSError errorWithDomain:@"FIRAppCheckTests" code:-1 userInfo:nil];
  259. id completionArg = [OCMArg invokeBlockWithArgs:[NSNull null], providerError, nil];
  260. OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]);
  261. // 3. Don't expect token requested from app check provider.
  262. OCMReject([self.mockAppCheckProvider getTokenWithCompletion:[OCMArg any]]);
  263. // 4. Don't expect token update notification to be sent.
  264. XCTestExpectation *notificationExpectation = [self tokenUpdateNotificationWithExpectedToken:@""];
  265. notificationExpectation.inverted = YES;
  266. // 5. Request token.
  267. XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"];
  268. [self.appCheck getTokenForcingRefresh:NO
  269. completion:^(id<FIRAppCheckTokenResultInterop> result) {
  270. [getTokenExpectation fulfill];
  271. XCTAssertNotNil(result);
  272. XCTAssertEqualObjects(result.token, kDummyToken);
  273. // TODO: When method is added to public API: expect a public domain
  274. // error to be returned - not the internal one.
  275. XCTAssertEqualObjects(result.error, providerError);
  276. }];
  277. // 6. Wait for expectations and validate mocks.
  278. [self waitForExpectations:@[ notificationExpectation, getTokenExpectation ] timeout:0.5];
  279. OCMVerifyAll(self.mockStorage);
  280. OCMVerifyAll(self.mockAppCheckProvider);
  281. }
  282. #pragma mark - Token refresher
  283. - (void)testTokenRefreshTriggeredAndRefreshSuccess {
  284. // 1. Expect token to be requested from storage.
  285. OCMExpect([self.mockStorage getToken]).andReturn([FBLPromise resolvedWith:nil]);
  286. // 2. Expect token requested from app check provider.
  287. NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:10000];
  288. FIRAppCheckToken *tokenToReturn = [[FIRAppCheckToken alloc] initWithToken:@"valid"
  289. expirationDate:expirationDate];
  290. id completionArg = [OCMArg invokeBlockWithArgs:tokenToReturn, [NSNull null], nil];
  291. OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]);
  292. // 3. Expect new token to be stored.
  293. OCMExpect([self.mockStorage setToken:tokenToReturn])
  294. .andReturn([FBLPromise resolvedWith:tokenToReturn]);
  295. // 4. Expect token update notification to be sent.
  296. XCTestExpectation *notificationExpectation =
  297. [self tokenUpdateNotificationWithExpectedToken:tokenToReturn.token];
  298. // 5. Trigger refresh and expect the result.
  299. if (self.tokenRefreshHandler == nil) {
  300. XCTFail(@"`tokenRefreshHandler` must be not `nil`.");
  301. return;
  302. }
  303. XCTestExpectation *completionExpectation = [self expectationWithDescription:@"completion"];
  304. self.tokenRefreshHandler(^(BOOL success, NSDate *_Nullable tokenExpirationDate) {
  305. [completionExpectation fulfill];
  306. XCTAssertEqual(tokenExpirationDate, expirationDate);
  307. XCTAssertTrue(success);
  308. });
  309. [self waitForExpectations:@[ notificationExpectation, completionExpectation ] timeout:0.5];
  310. OCMVerifyAll(self.mockStorage);
  311. OCMVerifyAll(self.mockAppCheckProvider);
  312. }
  313. - (void)testTokenRefreshTriggeredAndRefreshError {
  314. // 1. Expect token to be requested from storage.
  315. OCMExpect([self.mockStorage getToken]).andReturn([FBLPromise resolvedWith:nil]);
  316. // 2. Expect token requested from app check provider.
  317. NSError *providerError = [NSError errorWithDomain:@"FIRAppCheckTests" code:-1 userInfo:nil];
  318. id completionArg = [OCMArg invokeBlockWithArgs:[NSNull null], providerError, nil];
  319. OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]);
  320. // 3. Don't expect token requested from app check provider.
  321. OCMReject([self.mockAppCheckProvider getTokenWithCompletion:[OCMArg any]]);
  322. // 4. Don't expect token update notification to be sent.
  323. XCTestExpectation *notificationExpectation = [self tokenUpdateNotificationWithExpectedToken:@""];
  324. notificationExpectation.inverted = YES;
  325. // 5. Trigger refresh and expect the result.
  326. if (self.tokenRefreshHandler == nil) {
  327. XCTFail(@"`tokenRefreshHandler` must be not `nil`.");
  328. return;
  329. }
  330. XCTestExpectation *completionExpectation = [self expectationWithDescription:@"completion"];
  331. self.tokenRefreshHandler(^(BOOL success, NSDate *_Nullable tokenExpirationDate) {
  332. [completionExpectation fulfill];
  333. XCTAssertNil(tokenExpirationDate);
  334. XCTAssertFalse(success);
  335. });
  336. [self waitForExpectations:@[ notificationExpectation, completionExpectation ] timeout:0.5];
  337. OCMVerifyAll(self.mockStorage);
  338. OCMVerifyAll(self.mockAppCheckProvider);
  339. }
  340. #pragma mark - Token update notifications
  341. - (void)testTokenUpdateNotificationKeys {
  342. XCTAssertEqualObjects([self.appCheck tokenDidChangeNotificationName],
  343. @"FIRAppCheckAppCheckTokenDidChangeNotification");
  344. XCTAssertEqualObjects([self.appCheck notificationAppNameKey],
  345. @"FIRAppCheckAppNameNotificationKey");
  346. XCTAssertEqualObjects([self.appCheck notificationTokenKey], @"FIRAppCheckTokenNotificationKey");
  347. }
  348. #pragma mark - Auto-refresh enabled
  349. - (void)testIsTokenAutoRefreshEnabled {
  350. // Expect value from settings to be used.
  351. [[[self.mockSettings expect] andReturnValue:@(NO)] isTokenAutoRefreshEnabled];
  352. XCTAssertFalse(self.appCheck.isTokenAutoRefreshEnabled);
  353. [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled];
  354. XCTAssertTrue(self.appCheck.isTokenAutoRefreshEnabled);
  355. OCMVerifyAll(self.mockSettings);
  356. }
  357. - (void)testSetIsTokenAutoRefreshEnabled {
  358. OCMExpect([self.mockSettings setIsTokenAutoRefreshEnabled:YES]);
  359. self.appCheck.isTokenAutoRefreshEnabled = YES;
  360. OCMExpect([self.mockSettings setIsTokenAutoRefreshEnabled:NO]);
  361. self.appCheck.isTokenAutoRefreshEnabled = NO;
  362. OCMVerifyAll(self.mockSettings);
  363. }
  364. #pragma mark - Merging multiple get token requests
  365. - (void)testGetToken_WhenCalledSeveralTimesSuccess_ThenThereIsOnlyOneOperation {
  366. // 1. Expect token to be requested from storage.
  367. OCMExpect([self.mockStorage getToken]).andReturn([FBLPromise resolvedWith:nil]);
  368. // 2. Expect token requested from app check provider.
  369. FIRAppCheckToken *tokenToReturn = [[FIRAppCheckToken alloc] initWithToken:[NSUUID UUID].UUIDString
  370. expirationDate:[NSDate distantFuture]];
  371. id completionArg = [OCMArg invokeBlockWithArgs:tokenToReturn, [NSNull null], nil];
  372. OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]);
  373. // 3. Expect new token to be stored.
  374. // 3.1. Create a pending promise to resolve later.
  375. FBLPromise<FIRAppCheckToken *> *storeTokenPromise = [FBLPromise pendingPromise];
  376. // 3.2. Stub storage set token method.
  377. OCMExpect([self.mockStorage setToken:tokenToReturn]).andReturn(storeTokenPromise);
  378. // 4. Expect token update notification to be sent.
  379. XCTestExpectation *notificationExpectation =
  380. [self tokenUpdateNotificationWithExpectedToken:tokenToReturn.token];
  381. // 5. Request token several times.
  382. NSInteger getTokenCallsCount = 10;
  383. NSMutableArray *getTokenCompletionExpectations =
  384. [NSMutableArray arrayWithCapacity:getTokenCallsCount];
  385. for (NSInteger i = 0; i < getTokenCallsCount; i++) {
  386. // 5.1. Expect a completion to be called for each method call.
  387. XCTestExpectation *getTokenExpectation =
  388. [self expectationWithDescription:[NSString stringWithFormat:@"getToken%@", @(i)]];
  389. [getTokenCompletionExpectations addObject:getTokenExpectation];
  390. // 5.2. Call get token.
  391. [self.appCheck getTokenForcingRefresh:NO
  392. completion:^(id<FIRAppCheckTokenResultInterop> tokenResult) {
  393. [getTokenExpectation fulfill];
  394. XCTAssertNotNil(tokenResult);
  395. XCTAssertEqualObjects(tokenResult.token, tokenToReturn.token);
  396. XCTAssertNil(tokenResult.error);
  397. }];
  398. }
  399. // 5.3. Fulfill the pending promise to finish the get token operation.
  400. [storeTokenPromise fulfill:tokenToReturn];
  401. // 6. Wait for expectations and validate mocks.
  402. [self waitForExpectations:[getTokenCompletionExpectations
  403. arrayByAddingObject:notificationExpectation]
  404. timeout:0.5];
  405. OCMVerifyAll(self.mockStorage);
  406. OCMVerifyAll(self.mockAppCheckProvider);
  407. // 7. Check a get token call after.
  408. [self assertGetToken_WhenCachedTokenIsValid_Success];
  409. }
  410. - (void)testGetToken_WhenCalledSeveralTimesError_ThenThereIsOnlyOneOperation {
  411. // 1. Expect token to be requested from storage.
  412. OCMExpect([self.mockStorage getToken]).andReturn([FBLPromise resolvedWith:nil]);
  413. // 2. Expect token requested from app check provider.
  414. FIRAppCheckToken *tokenToReturn = [[FIRAppCheckToken alloc] initWithToken:[NSUUID UUID].UUIDString
  415. expirationDate:[NSDate distantFuture]];
  416. id completionArg = [OCMArg invokeBlockWithArgs:tokenToReturn, [NSNull null], nil];
  417. OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]);
  418. // 3. Expect new token to be stored.
  419. // 3.1. Create a pending promise to resolve later.
  420. FBLPromise<FIRAppCheckToken *> *storeTokenPromise = [FBLPromise pendingPromise];
  421. // 3.2. Stub storage set token method.
  422. OCMExpect([self.mockStorage setToken:tokenToReturn]).andReturn(storeTokenPromise);
  423. // 3.3. Create an expected error to be rejected with later.
  424. NSError *storageError = [NSError errorWithDomain:self.name code:0 userInfo:nil];
  425. // 4. Don't expect token update notification to be sent.
  426. XCTestExpectation *notificationExpectation =
  427. [self tokenUpdateNotificationWithExpectedToken:tokenToReturn.token];
  428. notificationExpectation.inverted = YES;
  429. // 5. Request token several times.
  430. NSInteger getTokenCallsCount = 10;
  431. NSMutableArray *getTokenCompletionExpectations =
  432. [NSMutableArray arrayWithCapacity:getTokenCallsCount];
  433. for (NSInteger i = 0; i < getTokenCallsCount; i++) {
  434. // 5.1. Expect a completion to be called for each method call.
  435. XCTestExpectation *getTokenExpectation =
  436. [self expectationWithDescription:[NSString stringWithFormat:@"getToken%@", @(i)]];
  437. [getTokenCompletionExpectations addObject:getTokenExpectation];
  438. // 5.2. Call get token.
  439. [self.appCheck getTokenForcingRefresh:NO
  440. completion:^(id<FIRAppCheckTokenResultInterop> tokenResult) {
  441. [getTokenExpectation fulfill];
  442. XCTAssertNotNil(tokenResult);
  443. XCTAssertEqualObjects(tokenResult.error, storageError);
  444. XCTAssertEqualObjects(tokenResult.token, kDummyToken);
  445. }];
  446. }
  447. // 5.3. Reject the pending promise to finish the get token operation.
  448. [storeTokenPromise reject:storageError];
  449. // 6. Wait for expectations and validate mocks.
  450. [self waitForExpectations:[getTokenCompletionExpectations
  451. arrayByAddingObject:notificationExpectation]
  452. timeout:0.5];
  453. OCMVerifyAll(self.mockStorage);
  454. OCMVerifyAll(self.mockAppCheckProvider);
  455. // 7. Check a get token call after.
  456. [self assertGetToken_WhenCachedTokenIsValid_Success];
  457. }
  458. #pragma mark - Helpers
  459. - (void)stubSetTokenRefreshHandler {
  460. id arg = [OCMArg checkWithBlock:^BOOL(id handler) {
  461. self.tokenRefreshHandler = handler;
  462. return YES;
  463. }];
  464. OCMExpect([self.mockTokenRefresher setTokenRefreshHandler:arg]);
  465. }
  466. - (XCTestExpectation *)tokenUpdateNotificationWithExpectedToken:(NSString *)expectedToken {
  467. XCTestExpectation *expectation =
  468. [self expectationForNotification:[self.appCheck tokenDidChangeNotificationName]
  469. object:nil
  470. notificationCenter:self.notificationCenter
  471. handler:^BOOL(NSNotification *_Nonnull notification) {
  472. XCTAssertEqualObjects(
  473. notification.userInfo[[self.appCheck notificationAppNameKey]],
  474. self.appName);
  475. XCTAssertEqualObjects(
  476. notification.userInfo[[self.appCheck notificationTokenKey]],
  477. expectedToken);
  478. XCTAssertEqualObjects(notification.object, self.appCheck);
  479. return YES;
  480. }];
  481. return expectation;
  482. }
  483. - (void)assertGetToken_WhenCachedTokenIsValid_Success {
  484. FIRAppCheckToken *cachedToken = [[FIRAppCheckToken alloc] initWithToken:[NSUUID UUID].UUIDString
  485. expirationDate:[NSDate distantFuture]];
  486. // 1. Expect token to be requested from storage.
  487. OCMExpect([self.mockStorage getToken]).andReturn([FBLPromise resolvedWith:cachedToken]);
  488. // 2. Don't expect token requested from app check provider.
  489. OCMReject([self.mockAppCheckProvider getTokenWithCompletion:[OCMArg any]]);
  490. // 3. Don't expect token update notification to be sent.
  491. XCTestExpectation *notificationExpectation = [self tokenUpdateNotificationWithExpectedToken:@""];
  492. notificationExpectation.inverted = YES;
  493. // 4. Request token.
  494. XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"];
  495. [self.appCheck getTokenForcingRefresh:NO
  496. completion:^(id<FIRAppCheckTokenResultInterop> tokenResult) {
  497. [getTokenExpectation fulfill];
  498. XCTAssertNotNil(tokenResult);
  499. XCTAssertEqualObjects(tokenResult.token, cachedToken.token);
  500. XCTAssertNil(tokenResult.error);
  501. }];
  502. // 5. Wait for expectations and validate mocks.
  503. [self waitForExpectations:@[ notificationExpectation, getTokenExpectation ] timeout:0.5];
  504. OCMVerifyAll(self.mockStorage);
  505. OCMVerifyAll(self.mockAppCheckProvider);
  506. }
  507. @end