FIRAppCheckTokenRefresherTests.m 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  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 "FirebaseAppCheck/Sources/Core/FIRAppCheckSettings.h"
  19. #import "FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefresher.h"
  20. #import "FirebaseAppCheck/Tests/Unit/Utils/FIRFakeTimer.h"
  21. @interface FIRAppCheckTokenRefresherTests : XCTestCase
  22. @property(nonatomic) FIRFakeTimer *fakeTimer;
  23. @property(nonatomic) OCMockObject<FIRAppCheckSettingsProtocol> *mockSettings;
  24. @property(nonatomic) NSDate *initialTokenExpirationDate;
  25. @property(nonatomic) NSTimeInterval tokenExpirationThreshold;
  26. @end
  27. @implementation FIRAppCheckTokenRefresherTests
  28. - (void)setUp {
  29. self.mockSettings = OCMProtocolMock(@protocol(FIRAppCheckSettingsProtocol));
  30. self.fakeTimer = [[FIRFakeTimer alloc] init];
  31. self.initialTokenExpirationDate = [NSDate dateWithTimeIntervalSinceNow:1000];
  32. self.tokenExpirationThreshold = 1 * 60;
  33. }
  34. - (void)tearDown {
  35. self.fakeTimer = nil;
  36. [self.mockSettings stopMocking];
  37. self.mockSettings = nil;
  38. }
  39. #pragma mark - Auto refresh is allowed
  40. - (void)testInitialRefreshWhenAutoRefreshAllowed {
  41. FIRAppCheckTokenRefresher *refresher = [self createRefresher];
  42. // 1. Expect checking if auto-refresh allowed before scheduling initial refresh.
  43. [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled];
  44. // 2. Expect timer to be scheduled.
  45. NSDate *expectedTimerFireDate =
  46. [self.initialTokenExpirationDate dateByAddingTimeInterval:-self.tokenExpirationThreshold];
  47. XCTestExpectation *timerCreateExpectation = [self expectationWithDescription:@"create timer"];
  48. __auto_type weakSelf = self;
  49. self.fakeTimer.createHandler = ^(NSDate *_Nonnull fireDate) {
  50. weakSelf.fakeTimer.createHandler = nil;
  51. XCTAssertEqualObjects(fireDate, expectedTimerFireDate);
  52. [timerCreateExpectation fulfill];
  53. };
  54. // 3. Expect refresh handler to be called.
  55. NSDate *refreshedTokenExpirationDate = [expectedTimerFireDate dateByAddingTimeInterval:60 * 60];
  56. XCTestExpectation *initialRefreshExpectation =
  57. [self expectationWithDescription:@"initial refresh"];
  58. XCTestExpectation *noEarlyRefreshExpectation =
  59. [self expectationWithDescription:@"no early refresh"];
  60. noEarlyRefreshExpectation.inverted = YES;
  61. refresher.tokenRefreshHandler = ^(FIRAppCheckTokenRefreshCompletion _Nonnull completion) {
  62. [initialRefreshExpectation fulfill];
  63. [noEarlyRefreshExpectation fulfill];
  64. // Call completion.
  65. completion(YES, refreshedTokenExpirationDate);
  66. };
  67. // 4. Check if the handler is not fired before the timer.
  68. [self waitForExpectations:@[ timerCreateExpectation, noEarlyRefreshExpectation ] timeout:1];
  69. // 5. Expect checking if auto-refresh allowed before refreshing.
  70. [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled];
  71. // 6. Fire the timer and wait for completion.
  72. [self fireTimer];
  73. [self waitForExpectations:@[ initialRefreshExpectation ] timeout:0.5];
  74. OCMVerifyAll(self.mockSettings);
  75. }
  76. - (void)testNoTimeScheduledUntilHandlerSet {
  77. // 1. Don't expect timer to be scheduled.
  78. XCTestExpectation *timerCreateExpectation1 = [self expectationWithDescription:@"create timer 1"];
  79. timerCreateExpectation1.inverted = YES;
  80. self.fakeTimer.createHandler = ^(NSDate *_Nonnull fireDate) {
  81. [timerCreateExpectation1 fulfill];
  82. };
  83. // 2. Create a publisher.
  84. FIRAppCheckTokenRefresher *refresher = [self createRefresher];
  85. XCTAssertNotNil(refresher);
  86. [self waitForExpectations:@[ timerCreateExpectation1 ] timeout:0.5];
  87. // 3. Expect timer to be created after the handler has been set.
  88. // 3.1. Expect checking if auto-refresh allowed one more time when timer fires.
  89. [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled];
  90. // 3.2. Expect timer to fire.
  91. XCTestExpectation *timerCreateExpectation2 = [self expectationWithDescription:@"create timer 2"];
  92. self.fakeTimer.createHandler = ^(NSDate *_Nonnull fireDate) {
  93. [timerCreateExpectation2 fulfill];
  94. };
  95. // 3.3. Set handler.
  96. refresher.tokenRefreshHandler = ^(FIRAppCheckTokenRefreshCompletion _Nonnull completion) {
  97. };
  98. [self waitForExpectations:@[ timerCreateExpectation2 ] timeout:0.5];
  99. OCMVerifyAll(self.mockSettings);
  100. }
  101. - (void)testNextRefreshOnRefreshSuccess {
  102. FIRAppCheckTokenRefresher *refresher = [self createRefresher];
  103. NSDate *refreshedTokenExpirationDate =
  104. [self.initialTokenExpirationDate dateByAddingTimeInterval:60 * 60];
  105. // 1. Expect checking if auto-refresh allowed before scheduling initial refresh.
  106. [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled];
  107. // 2. Expect refresh handler.
  108. XCTestExpectation *initialRefreshExpectation =
  109. [self expectationWithDescription:@"initial refresh"];
  110. refresher.tokenRefreshHandler = ^(FIRAppCheckTokenRefreshCompletion _Nonnull completion) {
  111. [initialRefreshExpectation fulfill];
  112. // Call completion in a while.
  113. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)),
  114. dispatch_get_main_queue(), ^{
  115. completion(YES, refreshedTokenExpirationDate);
  116. });
  117. };
  118. // 3. Expect for new timer to be created.
  119. NSDate *expectedFireDate =
  120. [refreshedTokenExpirationDate dateByAddingTimeInterval:-self.tokenExpirationThreshold];
  121. XCTestExpectation *createTimerExpectation = [self expectationWithDescription:@"create timer"];
  122. self.fakeTimer.createHandler = ^(NSDate *_Nonnull fireDate) {
  123. [createTimerExpectation fulfill];
  124. XCTAssertEqualObjects(fireDate, expectedFireDate);
  125. };
  126. // 4. Expect checking if auto-refresh allowed before refreshing.
  127. [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled];
  128. // 5. Fire initial timer and wait for expectations.
  129. [self fireTimer];
  130. [self waitForExpectations:@[ initialRefreshExpectation, createTimerExpectation ]
  131. timeout:1
  132. enforceOrder:YES];
  133. OCMVerifyAll(self.mockSettings);
  134. }
  135. - (void)testBackoff {
  136. FIRAppCheckTokenRefresher *refresher = [self createRefresher];
  137. // Initial backoff interval.
  138. NSTimeInterval expectedBackoffTime = 0;
  139. NSTimeInterval maximumBackoffTime = 16 * 60; // 16 min.
  140. // 1. Expect checking if auto-refresh allowed before scheduling initial refresh.
  141. [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled];
  142. for (NSInteger i = 0; i < 10; i++) {
  143. // 2. Expect refresh handler.
  144. XCTestExpectation *initialRefreshExpectation =
  145. [self expectationWithDescription:@"initial refresh"];
  146. refresher.tokenRefreshHandler = ^(FIRAppCheckTokenRefreshCompletion _Nonnull completion) {
  147. [initialRefreshExpectation fulfill];
  148. // Call completion in a while.
  149. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)),
  150. dispatch_get_main_queue(), ^{
  151. completion(NO, nil);
  152. });
  153. };
  154. // 3. Expect for new timer to be created.
  155. // No backoff initially, 1st backoff 30sec, double backoff on each next attempt until 16min.
  156. expectedBackoffTime = expectedBackoffTime == 0 ? 30 : expectedBackoffTime * 2;
  157. expectedBackoffTime = MIN(expectedBackoffTime, maximumBackoffTime);
  158. NSDate *expectedFireDate = [[NSDate date] dateByAddingTimeInterval:expectedBackoffTime];
  159. XCTestExpectation *createTimerExpectation = [self expectationWithDescription:@"create timer"];
  160. self.fakeTimer.createHandler = ^(NSDate *_Nonnull fireDate) {
  161. [createTimerExpectation fulfill];
  162. // Check expected and actual fire date are not too different (account for the random part
  163. // and request attempt delay).
  164. XCTAssertLessThan(ABS([expectedFireDate timeIntervalSinceDate:fireDate]), 2);
  165. };
  166. // 4. Expect checking if auto-refresh allowed before refreshing.
  167. [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled];
  168. // 5. Fire initial timer and wait for expectations.
  169. [self fireTimer];
  170. [self waitForExpectations:@[ initialRefreshExpectation, createTimerExpectation ]
  171. timeout:1
  172. enforceOrder:YES];
  173. }
  174. OCMVerifyAll(self.mockSettings);
  175. }
  176. #pragma mark - Auto refresh is not allowed
  177. - (void)testNoInitialRefreshWhenAutoRefreshIsNotAllowed {
  178. FIRAppCheckTokenRefresher *refresher = [self createRefresher];
  179. // 1. Expect checking if auto-refresh allowed before scheduling initial refresh.
  180. [[[self.mockSettings expect] andReturnValue:@(NO)] isTokenAutoRefreshEnabled];
  181. // 2. Don't expect timer to be scheduled.
  182. NSDate *expectedTimerFireDate =
  183. [self.initialTokenExpirationDate dateByAddingTimeInterval:-self.tokenExpirationThreshold];
  184. XCTestExpectation *timerCreateExpectation = [self expectationWithDescription:@"create timer"];
  185. timerCreateExpectation.inverted = YES;
  186. __auto_type weakSelf = self;
  187. self.fakeTimer.createHandler = ^(NSDate *_Nonnull fireDate) {
  188. weakSelf.fakeTimer.createHandler = nil;
  189. XCTAssertEqualObjects(fireDate, expectedTimerFireDate);
  190. [timerCreateExpectation fulfill];
  191. };
  192. // 3. Don't expect refresh handler to be called.
  193. NSDate *refreshedTokenExpirationDate = [expectedTimerFireDate dateByAddingTimeInterval:60 * 60];
  194. XCTestExpectation *refreshExpectation = [self expectationWithDescription:@"refresh"];
  195. refreshExpectation.inverted = YES;
  196. refresher.tokenRefreshHandler = ^(FIRAppCheckTokenRefreshCompletion _Nonnull completion) {
  197. [refreshExpectation fulfill];
  198. // Call completion.
  199. completion(YES, refreshedTokenExpirationDate);
  200. };
  201. // 4. Check if the handler is not fired before the timer.
  202. [self waitForExpectations:@[ timerCreateExpectation, refreshExpectation ] timeout:1];
  203. OCMVerifyAll(self.mockSettings);
  204. }
  205. - (void)testNoRefreshWhenAutoRefreshWasDisabledAfterInit {
  206. FIRAppCheckTokenRefresher *refresher = [self createRefresher];
  207. // 1. Expect checking if auto-refresh allowed before scheduling initial refresh.
  208. [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled];
  209. // 2. Expect timer to be scheduled.
  210. NSDate *expectedTimerFireDate =
  211. [self.initialTokenExpirationDate dateByAddingTimeInterval:-self.tokenExpirationThreshold];
  212. XCTestExpectation *timerCreateExpectation = [self expectationWithDescription:@"create timer"];
  213. __auto_type weakSelf = self;
  214. self.fakeTimer.createHandler = ^(NSDate *_Nonnull fireDate) {
  215. weakSelf.fakeTimer.createHandler = nil;
  216. XCTAssertEqualObjects(fireDate, expectedTimerFireDate);
  217. [timerCreateExpectation fulfill];
  218. };
  219. // 3. Expect refresh handler to be called.
  220. NSDate *refreshedTokenExpirationDate = [expectedTimerFireDate dateByAddingTimeInterval:60 * 60];
  221. XCTestExpectation *noRefreshExpectation = [self expectationWithDescription:@"initial refresh"];
  222. noRefreshExpectation.inverted = YES;
  223. refresher.tokenRefreshHandler = ^(FIRAppCheckTokenRefreshCompletion _Nonnull completion) {
  224. [noRefreshExpectation fulfill];
  225. // Call completion.
  226. completion(YES, refreshedTokenExpirationDate);
  227. };
  228. // 4. Check if the handler is not fired before the timer.
  229. [self waitForExpectations:@[ timerCreateExpectation ] timeout:1];
  230. // 5. Expect checking if auto-refresh allowed before refreshing.
  231. [[[self.mockSettings expect] andReturnValue:@(NO)] isTokenAutoRefreshEnabled];
  232. // 6. Fire the timer and wait for completion.
  233. [self fireTimer];
  234. [self waitForExpectations:@[ noRefreshExpectation ] timeout:1];
  235. OCMVerifyAll(self.mockSettings);
  236. }
  237. #pragma mark - Update token expiration
  238. - (void)testUpdateTokenExpirationDateWhenAutoRefreshIsAllowed {
  239. FIRAppCheckTokenRefresher *refresher = [self createRefresher];
  240. NSDate *newExpirationDate = [self.initialTokenExpirationDate dateByAddingTimeInterval:10 * 60];
  241. // 1. Expect checking if auto-refresh allowed before scheduling initial refresh.
  242. [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled];
  243. // 2. Expect timer to be scheduled.
  244. NSDate *expectedTimerFireDate =
  245. [newExpirationDate dateByAddingTimeInterval:-self.tokenExpirationThreshold];
  246. XCTestExpectation *timerCreateExpectation = [self expectationWithDescription:@"create timer"];
  247. __auto_type weakSelf = self;
  248. self.fakeTimer.createHandler = ^(NSDate *_Nonnull fireDate) {
  249. weakSelf.fakeTimer.createHandler = nil;
  250. XCTAssertEqualObjects(fireDate, expectedTimerFireDate);
  251. [timerCreateExpectation fulfill];
  252. };
  253. // 3. Update token expiration date.
  254. [refresher updateTokenExpirationDate:newExpirationDate];
  255. // 4. Wait for timer to be created.
  256. [self waitForExpectations:@[ timerCreateExpectation ] timeout:1];
  257. OCMVerifyAll(self.mockSettings);
  258. }
  259. - (void)testUpdateTokenExpirationDateWhenAutoRefreshIsNotAllowed {
  260. FIRAppCheckTokenRefresher *refresher = [self createRefresher];
  261. NSDate *newExpirationDate = [self.initialTokenExpirationDate dateByAddingTimeInterval:10 * 60];
  262. // 1. Expect checking if auto-refresh allowed before scheduling initial refresh.
  263. [[[self.mockSettings expect] andReturnValue:@(NO)] isTokenAutoRefreshEnabled];
  264. // 2. Don't expect timer to be scheduled.
  265. NSDate *expectedTimerFireDate =
  266. [newExpirationDate dateByAddingTimeInterval:-self.tokenExpirationThreshold];
  267. XCTestExpectation *timerCreateExpectation = [self expectationWithDescription:@"create timer"];
  268. timerCreateExpectation.inverted = YES;
  269. __auto_type weakSelf = self;
  270. self.fakeTimer.createHandler = ^(NSDate *_Nonnull fireDate) {
  271. weakSelf.fakeTimer.createHandler = nil;
  272. XCTAssertEqualObjects(fireDate, expectedTimerFireDate);
  273. [timerCreateExpectation fulfill];
  274. };
  275. // 3. Update token expiration date.
  276. [refresher updateTokenExpirationDate:newExpirationDate];
  277. // 4. Wait for timer to be created.
  278. [self waitForExpectations:@[ timerCreateExpectation ] timeout:1];
  279. OCMVerifyAll(self.mockSettings);
  280. }
  281. #pragma mark - Helpers
  282. - (void)fireTimer {
  283. if (self.fakeTimer.handler) {
  284. self.fakeTimer.handler();
  285. } else {
  286. XCTFail(@"handler must not be nil!");
  287. }
  288. }
  289. - (FIRAppCheckTokenRefresher *)createRefresher {
  290. return [[FIRAppCheckTokenRefresher alloc]
  291. initWithTokenExpirationDate:self.initialTokenExpirationDate
  292. tokenExpirationThreshold:self.tokenExpirationThreshold
  293. timerProvider:[self.fakeTimer fakeTimerProvider]
  294. settings:self.mockSettings];
  295. }
  296. @end