GACAppCheckTests.m 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695
  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 "AppCheckCore/Sources/Public/AppCheckCore/GACAppCheck.h"
  20. #import "AppCheckCore/Sources/Public/AppCheckCore/GACAppCheckErrors.h"
  21. #import "AppCheckCore/Sources/Public/AppCheckCore/GACAppCheckProvider.h"
  22. #import "AppCheckCore/Sources/Public/AppCheckCore/GACAppCheckSettings.h"
  23. #import "AppCheckCore/Sources/Core/Errors/GACAppCheckErrorUtil.h"
  24. #import "AppCheckCore/Sources/Core/Storage/GACAppCheckStorage.h"
  25. #import "AppCheckCore/Sources/Core/TokenRefresh/GACAppCheckTokenRefreshResult.h"
  26. #import "AppCheckCore/Sources/Core/TokenRefresh/GACAppCheckTokenRefresher.h"
  27. #import "AppCheckCore/Sources/Public/AppCheckCore/GACAppCheckToken.h"
  28. #import "AppCheckCore/Sources/Public/AppCheckCore/GACAppCheckTokenDelegate.h"
  29. #import "FirebaseCore/Extension/FirebaseCoreInternal.h"
  30. // The FAC token value returned when an error occurs.
  31. static NSString *const kDummyToken = @"eyJlcnJvciI6IlVOS05PV05fRVJST1IifQ==";
  32. static NSString *const kResourceName = @"projects/test_project_id/apps/test_app_id";
  33. static NSString *const kAppName = @"GACAppCheckTests";
  34. static NSString *const kAppGroupID = @"app_group_id";
  35. @interface GACAppCheck (Tests)
  36. - (instancetype)initWithServiceName:(NSString *)instanceName
  37. appCheckProvider:(id<GACAppCheckProvider>)appCheckProvider
  38. storage:(id<GACAppCheckStorageProtocol>)storage
  39. tokenRefresher:(id<GACAppCheckTokenRefresherProtocol>)tokenRefresher
  40. settings:(id<GACAppCheckSettingsProtocol>)settings
  41. tokenDelegate:(nullable id<GACAppCheckTokenDelegate>)tokenDelegate;
  42. @end
  43. @interface GACAppCheckTests : XCTestCase
  44. @property(nonatomic) OCMockObject<GACAppCheckStorageProtocol> *mockStorage;
  45. @property(nonatomic) OCMockObject<GACAppCheckProvider> *mockAppCheckProvider;
  46. @property(nonatomic) OCMockObject<GACAppCheckTokenRefresherProtocol> *mockTokenRefresher;
  47. @property(nonatomic) OCMockObject<GACAppCheckSettingsProtocol> *mockSettings;
  48. @property(nonatomic) OCMockObject<GACAppCheckTokenDelegate> *mockTokenDelegate;
  49. @property(nonatomic) GACAppCheck *appCheck;
  50. @property(nonatomic, copy, nullable) GACAppCheckTokenRefreshBlock tokenRefreshHandler;
  51. @end
  52. @implementation GACAppCheckTests
  53. - (void)setUp {
  54. [super setUp];
  55. self.mockStorage = OCMStrictProtocolMock(@protocol(GACAppCheckStorageProtocol));
  56. self.mockAppCheckProvider = OCMStrictProtocolMock(@protocol(GACAppCheckProvider));
  57. self.mockTokenRefresher = OCMStrictProtocolMock(@protocol(GACAppCheckTokenRefresherProtocol));
  58. self.mockSettings = OCMStrictProtocolMock(@protocol(GACAppCheckSettingsProtocol));
  59. self.mockTokenDelegate = OCMStrictProtocolMock(@protocol(GACAppCheckTokenDelegate));
  60. [self stubSetTokenRefreshHandler];
  61. self.appCheck = [[GACAppCheck alloc] initWithServiceName:kAppName
  62. appCheckProvider:self.mockAppCheckProvider
  63. storage:self.mockStorage
  64. tokenRefresher:self.mockTokenRefresher
  65. settings:self.mockSettings
  66. tokenDelegate:self.mockTokenDelegate];
  67. }
  68. - (void)tearDown {
  69. self.appCheck = nil;
  70. [self.mockAppCheckProvider stopMocking];
  71. self.mockAppCheckProvider = nil;
  72. [self.mockStorage stopMocking];
  73. self.mockStorage = nil;
  74. [self.mockTokenRefresher stopMocking];
  75. self.mockTokenRefresher = nil;
  76. [super tearDown];
  77. }
  78. #pragma mark - Public Init
  79. - (void)testAppCheckInit {
  80. NSString *tokenKey =
  81. [NSString stringWithFormat:@"app_check_token.%@.%@", kAppName, kResourceName];
  82. // 1. Stub GACAppCheckTokenRefresher and validate usage.
  83. id mockTokenRefresher = OCMClassMock([GACAppCheckTokenRefresher class]);
  84. OCMExpect([mockTokenRefresher alloc]).andReturn(mockTokenRefresher);
  85. id refresherDateValidator =
  86. [OCMArg checkWithBlock:^BOOL(GACAppCheckTokenRefreshResult *refreshResult) {
  87. XCTAssertEqual(refreshResult.status, GACAppCheckTokenRefreshStatusNever);
  88. XCTAssertEqual(refreshResult.tokenExpirationDate, nil);
  89. XCTAssertEqual(refreshResult.tokenReceivedAtDate, nil);
  90. return YES;
  91. }];
  92. id settingsValidator = [OCMArg checkWithBlock:^BOOL(id obj) {
  93. XCTAssert([obj conformsToProtocol:@protocol(GACAppCheckSettingsProtocol)]);
  94. return YES;
  95. }];
  96. OCMExpect([mockTokenRefresher initWithRefreshResult:refresherDateValidator
  97. settings:settingsValidator])
  98. .andReturn(mockTokenRefresher);
  99. OCMExpect([mockTokenRefresher setTokenRefreshHandler:[OCMArg any]]);
  100. // 2. Stub GACAppCheckStorage and validate usage.
  101. id mockStorage = OCMStrictClassMock([GACAppCheckStorage class]);
  102. OCMExpect([mockStorage alloc]).andReturn(mockStorage);
  103. OCMExpect([mockStorage initWithTokenKey:tokenKey accessGroup:kAppGroupID]).andReturn(mockStorage);
  104. // 3. Stub attestation provider.
  105. OCMockObject<GACAppCheckProvider> *mockProvider =
  106. OCMStrictProtocolMock(@protocol(GACAppCheckProvider));
  107. // 4. Stub GACAppCheckSettingsProtocol.
  108. OCMockObject<GACAppCheckSettingsProtocol> *mockSettings =
  109. OCMStrictProtocolMock(@protocol(GACAppCheckSettingsProtocol));
  110. // 5. Stub GACAppCheckTokenDelegate.
  111. OCMockObject<GACAppCheckTokenDelegate> *mockTokenDelegate =
  112. OCMStrictProtocolMock(@protocol(GACAppCheckTokenDelegate));
  113. // 6. Call init.
  114. GACAppCheck *appCheck = [[GACAppCheck alloc] initWithServiceName:kAppName
  115. resourceName:kResourceName
  116. appCheckProvider:mockProvider
  117. settings:mockSettings
  118. tokenDelegate:mockTokenDelegate
  119. keychainAccessGroup:kAppGroupID];
  120. XCTAssert([appCheck isKindOfClass:[GACAppCheck class]]);
  121. // 7. Verify mocks.
  122. OCMVerifyAll(mockTokenRefresher);
  123. OCMVerifyAll(mockStorage);
  124. OCMVerifyAll(mockProvider);
  125. OCMVerifyAll(mockSettings);
  126. OCMVerifyAll(mockTokenDelegate);
  127. // 8. Stop mocking real class mocks.
  128. [mockTokenRefresher stopMocking];
  129. mockTokenRefresher = nil;
  130. [mockStorage stopMocking];
  131. mockStorage = nil;
  132. }
  133. #pragma mark - Public Get Token
  134. - (void)testGetToken_WhenCachedTokenIsValid_Success {
  135. [self assertGetToken_WhenCachedTokenIsValid_Success];
  136. }
  137. #pragma mark - GACAppCheckInterop Get Token
  138. - (void)testInteropGetToken_WhenNoCache_Success {
  139. // 1. Create expected token and configure expectations.
  140. GACAppCheckToken *expectedToken = [self validToken];
  141. XCTestExpectation *expectation =
  142. [self configuredExpectations_GetTokenWhenNoCache_withExpectedToken:expectedToken];
  143. // 2. Request token and verify result.
  144. [self.appCheck
  145. getTokenForcingRefresh:NO
  146. completion:^(id<GACAppCheckTokenProtocol> _Nullable token, NSError *error) {
  147. [expectation fulfill];
  148. XCTAssertNotNil(token);
  149. XCTAssertEqualObjects(token.token, expectedToken.token);
  150. XCTAssertNil(error);
  151. }];
  152. // 3. Wait for expectations and validate mocks.
  153. [self waitForExpectations:@[ expectation ] timeout:0.5];
  154. [self verifyAllMocks];
  155. }
  156. - (void)testInteropGetToken_WhenCachedTokenIsValid_Success {
  157. [self assertInteropGetToken_WhenCachedTokenIsValid_Success];
  158. }
  159. - (void)testInteropGetTokenForcingRefresh_WhenCachedTokenIsValid_Success {
  160. // 1. Create expected token and configure expectations.
  161. GACAppCheckToken *expectedToken = [self validToken];
  162. XCTestExpectation *expectation =
  163. [self configuredExpectations_GetTokenForcingRefreshWhenCacheIsValid_withExpectedToken:
  164. expectedToken];
  165. // 2. Request token and verify result.
  166. [self.appCheck
  167. getTokenForcingRefresh:YES
  168. completion:^(id<GACAppCheckTokenProtocol> _Nullable token, NSError *error) {
  169. [expectation fulfill];
  170. XCTAssertNotNil(token);
  171. XCTAssertEqualObjects(token.token, expectedToken.token);
  172. XCTAssertNil(error);
  173. }];
  174. // 3. Wait for expectations and validate mocks.
  175. [self waitForExpectations:@[ expectation ] timeout:0.5];
  176. [self verifyAllMocks];
  177. }
  178. - (void)testInteropGetToken_WhenCachedTokenExpired_Success {
  179. // 1. Create expected token and configure expectations.
  180. GACAppCheckToken *expectedToken = [self validToken];
  181. XCTestExpectation *expectation =
  182. [self configuredExpectations_GetTokenWhenCachedTokenExpired_withExpectedToken:expectedToken];
  183. // 2. Request token and verify result.
  184. [self.appCheck
  185. getTokenForcingRefresh:NO
  186. completion:^(id<GACAppCheckTokenProtocol> _Nullable token, NSError *error) {
  187. [expectation fulfill];
  188. XCTAssertNotNil(token);
  189. XCTAssertEqualObjects(token.token, expectedToken.token);
  190. XCTAssertNil(error);
  191. }];
  192. // 3. Wait for expectations and validate mocks.
  193. [self waitForExpectations:@[ expectation ] timeout:0.5];
  194. [self verifyAllMocks];
  195. }
  196. - (void)testInteropGetToken_AppCheckProviderError {
  197. // 1. Create expected tokens and errors and configure expectations.
  198. GACAppCheckToken *cachedToken = [self soonExpiringToken];
  199. NSError *providerError = [NSError errorWithDomain:@"GACAppCheckTests" code:-1 userInfo:nil];
  200. XCTestExpectation *expectation =
  201. [self configuredExpectations_GetTokenWhenError_withError:providerError andToken:cachedToken];
  202. // 2. Request token and verify result.
  203. [self.appCheck
  204. getTokenForcingRefresh:NO
  205. completion:^(id<GACAppCheckTokenProtocol> _Nullable token, NSError *error) {
  206. [expectation fulfill];
  207. XCTAssertNil(token);
  208. XCTAssertEqualObjects(error, providerError);
  209. // Interop API does not wrap errors in public domain.
  210. XCTAssertNotEqualObjects(error.domain, GACAppCheckErrorDomain);
  211. }];
  212. // 3. Wait for expectations and validate mocks.
  213. [self waitForExpectations:@[ expectation ] timeout:0.5];
  214. [self verifyAllMocks];
  215. }
  216. #pragma mark - Token refresher
  217. - (void)testTokenRefreshTriggeredAndRefreshSuccess {
  218. // 1. Expect token to be requested from storage.
  219. OCMExpect([self.mockStorage getToken]).andReturn([FBLPromise resolvedWith:nil]);
  220. // 2. Expect token requested from app check provider.
  221. NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:10000];
  222. GACAppCheckToken *tokenToReturn = [[GACAppCheckToken alloc] initWithToken:@"valid"
  223. expirationDate:expirationDate];
  224. id completionArg = [OCMArg invokeBlockWithArgs:tokenToReturn, [NSNull null], nil];
  225. OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]);
  226. // 3. Expect new token to be stored.
  227. OCMExpect([self.mockStorage setToken:tokenToReturn])
  228. .andReturn([FBLPromise resolvedWith:tokenToReturn]);
  229. OCMExpect([self.mockTokenRefresher updateWithRefreshResult:[OCMArg any]]);
  230. // 4. Expect token update notification to be sent.
  231. OCMExpect([self.mockTokenDelegate tokenDidUpdate:tokenToReturn serviceName:kAppName]);
  232. // 5. Trigger refresh and expect the result.
  233. if (self.tokenRefreshHandler == nil) {
  234. XCTFail(@"`tokenRefreshHandler` must be not `nil`.");
  235. return;
  236. }
  237. XCTestExpectation *completionExpectation = [self expectationWithDescription:@"completion"];
  238. self.tokenRefreshHandler(^(GACAppCheckTokenRefreshResult *refreshResult) {
  239. [completionExpectation fulfill];
  240. XCTAssertEqualObjects(refreshResult.tokenExpirationDate, expirationDate);
  241. XCTAssertEqual(refreshResult.status, GACAppCheckTokenRefreshStatusSuccess);
  242. });
  243. [self waitForExpectations:@[ completionExpectation ] timeout:0.5];
  244. [self verifyAllMocks];
  245. }
  246. - (void)testTokenRefreshTriggeredAndRefreshError {
  247. // 1. Expect token to be requested from storage.
  248. OCMExpect([self.mockStorage getToken]).andReturn([FBLPromise resolvedWith:nil]);
  249. // 2. Expect token requested from app check provider.
  250. NSError *providerError = [self internalError];
  251. id completionArg = [OCMArg invokeBlockWithArgs:[NSNull null], providerError, nil];
  252. OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]);
  253. // 3. Don't expect token requested from app check provider.
  254. OCMReject([self.mockAppCheckProvider getTokenWithCompletion:[OCMArg any]]);
  255. // 4. Don't expect token update notification to be sent.
  256. OCMReject([self.mockTokenDelegate tokenDidUpdate:OCMOCK_ANY serviceName:OCMOCK_ANY]);
  257. // 5. Trigger refresh and expect the result.
  258. if (self.tokenRefreshHandler == nil) {
  259. XCTFail(@"`tokenRefreshHandler` must be not `nil`.");
  260. return;
  261. }
  262. XCTestExpectation *completionExpectation = [self expectationWithDescription:@"completion"];
  263. self.tokenRefreshHandler(^(GACAppCheckTokenRefreshResult *refreshResult) {
  264. [completionExpectation fulfill];
  265. XCTAssertEqual(refreshResult.status, GACAppCheckTokenRefreshStatusFailure);
  266. XCTAssertNil(refreshResult.tokenExpirationDate);
  267. XCTAssertNil(refreshResult.tokenReceivedAtDate);
  268. });
  269. [self waitForExpectations:@[ completionExpectation ] timeout:0.5];
  270. [self verifyAllMocks];
  271. }
  272. - (void)testLimitedUseTokenWithSuccess {
  273. // 1. Don't expect token to be requested from storage.
  274. OCMReject([self.mockStorage getToken]);
  275. // 2. Expect token requested from app check provider.
  276. GACAppCheckToken *expectedToken = [self validToken];
  277. id completionArg = [OCMArg invokeBlockWithArgs:expectedToken, [NSNull null], nil];
  278. OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]);
  279. // 3. Don't expect token requested from storage.
  280. OCMReject([self.mockStorage setToken:expectedToken]);
  281. // 4. Don't expect token update notification to be sent.
  282. OCMReject([self.mockTokenDelegate tokenDidUpdate:OCMOCK_ANY serviceName:OCMOCK_ANY]);
  283. // 5. Expect token request to be completed.
  284. XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"];
  285. [self.appCheck getLimitedUseTokenWithCompletion:^(id<GACAppCheckTokenProtocol> _Nullable token,
  286. NSError *_Nullable error) {
  287. [getTokenExpectation fulfill];
  288. XCTAssertNotNil(token);
  289. XCTAssertEqualObjects(token.token, expectedToken.token);
  290. XCTAssertNil(error);
  291. }];
  292. [self waitForExpectations:@[ getTokenExpectation ] timeout:0.5];
  293. [self verifyAllMocks];
  294. }
  295. - (void)testLimitedUseToken_WhenTokenGenerationErrors {
  296. // 1. Don't expect token to be requested from storage.
  297. OCMReject([self.mockStorage getToken]);
  298. // 2. Expect error when requesting token from app check provider.
  299. NSError *providerError = [GACAppCheckErrorUtil keychainErrorWithError:[self internalError]];
  300. id completionArg = [OCMArg invokeBlockWithArgs:[NSNull null], providerError, nil];
  301. OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]);
  302. // 3. Don't expect token requested from app check provider.
  303. OCMReject([self.mockAppCheckProvider getTokenWithCompletion:[OCMArg any]]);
  304. // 4. Don't expect token update notification to be sent.
  305. OCMReject([self.mockTokenDelegate tokenDidUpdate:OCMOCK_ANY serviceName:OCMOCK_ANY]);
  306. // 5. Expect token request to be completed.
  307. XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"];
  308. [self.appCheck getLimitedUseTokenWithCompletion:^(id<GACAppCheckTokenProtocol> _Nullable token,
  309. NSError *_Nullable error) {
  310. [getTokenExpectation fulfill];
  311. XCTAssertNotNil(error);
  312. XCTAssertNil(token.token);
  313. XCTAssertEqualObjects(error, providerError);
  314. XCTAssertEqualObjects(error.domain, GACAppCheckErrorDomain);
  315. }];
  316. [self waitForExpectations:@[ getTokenExpectation ] timeout:0.5];
  317. [self verifyAllMocks];
  318. }
  319. #pragma mark - Merging multiple get token requests
  320. - (void)testInteropGetToken_WhenCalledSeveralTimesSuccess_ThenThereIsOnlyOneOperation {
  321. // 1. Expect a token to be requested and stored.
  322. NSArray * /*[expectedToken, storeTokenPromise]*/ expectedTokenAndPromise =
  323. [self expectTokenRequestFromAppCheckProvider];
  324. GACAppCheckToken *expectedToken = expectedTokenAndPromise.firstObject;
  325. FBLPromise *storeTokenPromise = expectedTokenAndPromise.lastObject;
  326. OCMExpect([self.mockTokenRefresher updateWithRefreshResult:[OCMArg any]]);
  327. // 2. Expect token update notification to be sent.
  328. OCMExpect([self.mockTokenDelegate tokenDidUpdate:expectedToken serviceName:kAppName]);
  329. // 3. Request token several times.
  330. NSInteger getTokenCallsCount = 10;
  331. NSMutableArray *getTokenCompletionExpectations =
  332. [NSMutableArray arrayWithCapacity:getTokenCallsCount];
  333. for (NSInteger i = 0; i < getTokenCallsCount; i++) {
  334. // 3.1. Expect a completion to be called for each method call.
  335. XCTestExpectation *getTokenExpectation =
  336. [self expectationWithDescription:[NSString stringWithFormat:@"getToken%@", @(i)]];
  337. [getTokenCompletionExpectations addObject:getTokenExpectation];
  338. // 3.2. Request token and verify result.
  339. [self.appCheck
  340. getTokenForcingRefresh:NO
  341. completion:^(id<GACAppCheckTokenProtocol> _Nullable token, NSError *error) {
  342. [getTokenExpectation fulfill];
  343. XCTAssertNotNil(token);
  344. XCTAssertEqualObjects(token.token, expectedToken.token);
  345. XCTAssertNil(error);
  346. }];
  347. }
  348. // 3.3. Fulfill the pending promise to finish the get token operation.
  349. [storeTokenPromise fulfill:expectedToken];
  350. // 4. Wait for expectations and validate mocks.
  351. [self waitForExpectations:getTokenCompletionExpectations timeout:0.5];
  352. [self verifyAllMocks];
  353. // 5. Check a get token call after.
  354. [self assertInteropGetToken_WhenCachedTokenIsValid_Success];
  355. }
  356. - (void)testInteropGetToken_WhenCalledSeveralTimesError_ThenThereIsOnlyOneOperation {
  357. // 1. Expect a token to be requested and stored.
  358. NSArray * /*[expectedToken, storeTokenPromise]*/ expectedTokenAndPromise =
  359. [self expectTokenRequestFromAppCheckProvider];
  360. FBLPromise *storeTokenPromise = expectedTokenAndPromise.lastObject;
  361. // 1.1. Create an expected error to be reject the store token promise with later.
  362. NSError *storageError = [NSError errorWithDomain:self.name code:0 userInfo:nil];
  363. // 2. Don't expect token update notification to be sent.
  364. OCMReject([self.mockTokenDelegate tokenDidUpdate:OCMOCK_ANY serviceName:OCMOCK_ANY]);
  365. // 3. Request token several times.
  366. NSInteger getTokenCallsCount = 10;
  367. NSMutableArray *getTokenCompletionExpectations =
  368. [NSMutableArray arrayWithCapacity:getTokenCallsCount];
  369. for (NSInteger i = 0; i < getTokenCallsCount; i++) {
  370. // 3.1. Expect a completion to be called for each method call.
  371. XCTestExpectation *getTokenExpectation =
  372. [self expectationWithDescription:[NSString stringWithFormat:@"getToken%@", @(i)]];
  373. [getTokenCompletionExpectations addObject:getTokenExpectation];
  374. // 3.2. Request token and verify result.
  375. [self.appCheck
  376. getTokenForcingRefresh:NO
  377. completion:^(id<GACAppCheckTokenProtocol> _Nullable token, NSError *error) {
  378. [getTokenExpectation fulfill];
  379. XCTAssertNil(token);
  380. XCTAssertEqualObjects(error, storageError);
  381. }];
  382. }
  383. // 3.3. Reject the pending promise to finish the get token operation.
  384. [storeTokenPromise reject:storageError];
  385. // 4. Wait for expectations and validate mocks.
  386. [self waitForExpectations:getTokenCompletionExpectations timeout:0.5];
  387. [self verifyAllMocks];
  388. // 5. Check a get token call after.
  389. [self assertInteropGetToken_WhenCachedTokenIsValid_Success];
  390. }
  391. #pragma mark - Helpers
  392. - (NSError *)internalError {
  393. return [NSError errorWithDomain:@"com.internal.error" code:-1 userInfo:nil];
  394. }
  395. - (GACAppCheckToken *)validToken {
  396. return [[GACAppCheckToken alloc] initWithToken:[NSUUID UUID].UUIDString
  397. expirationDate:[NSDate distantFuture]];
  398. }
  399. - (GACAppCheckToken *)soonExpiringToken {
  400. NSDate *soonExpiringTokenDate = [NSDate dateWithTimeIntervalSinceNow:4.5 * 60];
  401. return [[GACAppCheckToken alloc] initWithToken:@"valid" expirationDate:soonExpiringTokenDate];
  402. }
  403. - (void)stubSetTokenRefreshHandler {
  404. id arg = [OCMArg checkWithBlock:^BOOL(id handler) {
  405. self.tokenRefreshHandler = handler;
  406. return YES;
  407. }];
  408. OCMExpect([self.mockTokenRefresher setTokenRefreshHandler:arg]);
  409. }
  410. - (void)assertGetToken_WhenCachedTokenIsValid_Success {
  411. // 1. Create expected token and configure expectations.
  412. GACAppCheckToken *cachedToken = [self validToken];
  413. XCTestExpectation *expectation =
  414. [self configuredExpectation_GetTokenWhenCacheTokenIsValid_withExpectedToken:cachedToken];
  415. // 2. Request token and verify result.
  416. [self.appCheck getTokenForcingRefresh:NO
  417. completion:^(id<GACAppCheckTokenProtocol> _Nullable token,
  418. NSError *_Nullable error) {
  419. [expectation fulfill];
  420. XCTAssertNotNil(token);
  421. XCTAssertEqualObjects(token.token, cachedToken.token);
  422. XCTAssertNil(error);
  423. }];
  424. // 3. Wait for expectations and validate mocks.
  425. [self waitForExpectations:@[ expectation ] timeout:0.5];
  426. [self verifyAllMocks];
  427. }
  428. - (void)assertInteropGetToken_WhenCachedTokenIsValid_Success {
  429. // 1. Create expected token and configure expectations.
  430. GACAppCheckToken *cachedToken = [self validToken];
  431. XCTestExpectation *expectation =
  432. [self configuredExpectation_GetTokenWhenCacheTokenIsValid_withExpectedToken:cachedToken];
  433. // 2. Request token and verify result.
  434. [self.appCheck
  435. getTokenForcingRefresh:NO
  436. completion:^(id<GACAppCheckTokenProtocol> _Nullable token, NSError *error) {
  437. [expectation fulfill];
  438. XCTAssertNotNil(token);
  439. XCTAssertEqualObjects(token.token, cachedToken.token);
  440. XCTAssertNil(error);
  441. }];
  442. // 3. Wait for expectations and validate mocks.
  443. [self waitForExpectations:@[ expectation ] timeout:0.5];
  444. [self verifyAllMocks];
  445. }
  446. - (XCTestExpectation *)configuredExpectations_GetTokenWhenNoCache_withExpectedToken:
  447. (GACAppCheckToken *)expectedToken {
  448. // 1. Expect token to be requested from storage.
  449. OCMExpect([self.mockStorage getToken]).andReturn([FBLPromise resolvedWith:nil]);
  450. // 2. Expect token requested from app check provider.
  451. id completionArg = [OCMArg invokeBlockWithArgs:expectedToken, [NSNull null], nil];
  452. OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]);
  453. // 3. Expect new token to be stored.
  454. OCMExpect([self.mockStorage setToken:expectedToken])
  455. .andReturn([FBLPromise resolvedWith:expectedToken]);
  456. OCMExpect([self.mockTokenRefresher updateWithRefreshResult:[OCMArg any]]);
  457. // 4. Expect token update notification to be sent.
  458. OCMExpect([self.mockTokenDelegate tokenDidUpdate:expectedToken serviceName:kAppName]);
  459. // 5. Expect token request to be completed.
  460. XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"];
  461. return getTokenExpectation;
  462. }
  463. - (XCTestExpectation *)configuredExpectation_GetTokenWhenCacheTokenIsValid_withExpectedToken:
  464. (GACAppCheckToken *)expectedToken {
  465. // 1. Expect token to be requested from storage.
  466. OCMExpect([self.mockStorage getToken]).andReturn([FBLPromise resolvedWith:expectedToken]);
  467. // 2. Don't expect token requested from app check provider.
  468. OCMReject([self.mockAppCheckProvider getTokenWithCompletion:[OCMArg any]]);
  469. // 3. Don't expect token update notification to be sent.
  470. OCMReject([self.mockTokenDelegate tokenDidUpdate:OCMOCK_ANY serviceName:OCMOCK_ANY]);
  471. // 4. Expect token request to be completed.
  472. return [self expectationWithDescription:@"getToken"];
  473. }
  474. - (XCTestExpectation *)
  475. configuredExpectations_GetTokenForcingRefreshWhenCacheIsValid_withExpectedToken:
  476. (GACAppCheckToken *)expectedToken {
  477. // 1. Don't expect token to be requested from storage.
  478. OCMReject([self.mockStorage getToken]);
  479. // 2. Expect token requested from app check provider.
  480. id completionArg = [OCMArg invokeBlockWithArgs:expectedToken, [NSNull null], nil];
  481. OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]);
  482. // 3. Expect new token to be stored.
  483. OCMExpect([self.mockStorage setToken:expectedToken])
  484. .andReturn([FBLPromise resolvedWith:expectedToken]);
  485. OCMExpect([self.mockTokenRefresher updateWithRefreshResult:[OCMArg any]]);
  486. // 4. Expect token update notification to be sent.
  487. OCMExpect([self.mockTokenDelegate tokenDidUpdate:expectedToken serviceName:kAppName]);
  488. // 5. Expect token request to be completed.
  489. XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"];
  490. return getTokenExpectation;
  491. }
  492. - (XCTestExpectation *)configuredExpectations_GetTokenWhenCachedTokenExpired_withExpectedToken:
  493. (GACAppCheckToken *)expectedToken {
  494. // 1. Expect token to be requested from storage.
  495. GACAppCheckToken *cachedToken = [[GACAppCheckToken alloc] initWithToken:@"expired"
  496. expirationDate:[NSDate date]];
  497. OCMExpect([self.mockStorage getToken]).andReturn([FBLPromise resolvedWith:cachedToken]);
  498. // 2. Expect token requested from app check provider.
  499. id completionArg = [OCMArg invokeBlockWithArgs:expectedToken, [NSNull null], nil];
  500. OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]);
  501. // 3. Expect new token to be stored.
  502. OCMExpect([self.mockStorage setToken:expectedToken])
  503. .andReturn([FBLPromise resolvedWith:expectedToken]);
  504. OCMExpect([self.mockTokenRefresher updateWithRefreshResult:[OCMArg any]]);
  505. // 4. Expect token update notification to be sent.
  506. OCMExpect([self.mockTokenDelegate tokenDidUpdate:expectedToken serviceName:kAppName]);
  507. // 5. Expect token request to be completed.
  508. XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"];
  509. return getTokenExpectation;
  510. }
  511. - (XCTestExpectation *)
  512. configuredExpectations_GetTokenWhenError_withError:(NSError *_Nonnull)error
  513. andToken:(GACAppCheckToken *_Nullable)token {
  514. // 1. Expect token to be requested from storage.
  515. OCMExpect([self.mockStorage getToken]).andReturn([FBLPromise resolvedWith:token]);
  516. // 2. Expect token requested from app check provider.
  517. id completionArg = [OCMArg invokeBlockWithArgs:[NSNull null], error, nil];
  518. OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]);
  519. // 3. Don't expect token requested from app check provider.
  520. OCMReject([self.mockAppCheckProvider getTokenWithCompletion:[OCMArg any]]);
  521. // 4. Expect token update notification to be sent.
  522. OCMReject([self.mockTokenDelegate tokenDidUpdate:OCMOCK_ANY serviceName:OCMOCK_ANY]);
  523. // 5. Expect token request to be completed.
  524. XCTestExpectation *getTokenExpectation = [self expectationWithDescription:@"getToken"];
  525. return getTokenExpectation;
  526. }
  527. - (NSArray *)expectTokenRequestFromAppCheckProvider {
  528. // 1. Expect token to be requested from storage.
  529. OCMExpect([self.mockStorage getToken]).andReturn([FBLPromise resolvedWith:nil]);
  530. // 2. Expect token requested from app check provider.
  531. GACAppCheckToken *expectedToken = [self validToken];
  532. id completionArg = [OCMArg invokeBlockWithArgs:expectedToken, [NSNull null], nil];
  533. OCMExpect([self.mockAppCheckProvider getTokenWithCompletion:completionArg]);
  534. // 3. Expect new token to be stored.
  535. // 3.1. Create a pending promise to resolve later.
  536. FBLPromise<GACAppCheckToken *> *storeTokenPromise = [FBLPromise pendingPromise];
  537. // 3.2. Stub storage set token method.
  538. OCMExpect([self.mockStorage setToken:expectedToken]).andReturn(storeTokenPromise);
  539. return @[ expectedToken, storeTokenPromise ];
  540. }
  541. - (void)verifyAllMocks {
  542. OCMVerifyAll(self.mockAppCheckProvider);
  543. OCMVerifyAll(self.mockStorage);
  544. OCMVerifyAll(self.mockSettings);
  545. OCMVerifyAll(self.mockTokenDelegate);
  546. OCMVerifyAll(self.mockTokenRefresher);
  547. }
  548. @end