FIRAppCheckTests.m 28 KB

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