| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502 |
- /*
- * Copyright 2021 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- #import <XCTest/XCTest.h>
- #import <OCMock/OCMock.h>
- #import "FirebaseAppCheck/Sources/Core/FIRAppCheckSettings.h"
- #import "FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefreshResult.h"
- #import "FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefresher.h"
- #import "FirebaseAppCheck/Tests/Unit/Utils/FIRFakeTimer.h"
- #import "SharedTestUtilities/Date/FIRDateTestUtils.h"
- @interface FIRAppCheckTokenRefresherTests : XCTestCase
- @property(nonatomic) FIRFakeTimer *fakeTimer;
- @property(nonatomic) OCMockObject<FIRAppCheckSettingsProtocol> *mockSettings;
- @property(nonatomic) FIRAppCheckTokenRefreshResult *initialTokenRefreshResult;
- @end
- @implementation FIRAppCheckTokenRefresherTests
- - (void)setUp {
- self.mockSettings = OCMProtocolMock(@protocol(FIRAppCheckSettingsProtocol));
- self.fakeTimer = [[FIRFakeTimer alloc] init];
- NSDate *receivedAtDate = [NSDate date];
- self.initialTokenRefreshResult = [[FIRAppCheckTokenRefreshResult alloc]
- initWithStatusSuccessAndExpirationDate:[receivedAtDate dateByAddingTimeInterval:1000]
- receivedAtDate:receivedAtDate];
- }
- - (void)tearDown {
- self.fakeTimer = nil;
- [self.mockSettings stopMocking];
- self.mockSettings = nil;
- }
- #pragma mark - Auto refresh is allowed
- - (void)testInitialRefreshWhenAutoRefreshAllowed {
- __auto_type weakSelf = self;
- self.initialTokenRefreshResult = [[FIRAppCheckTokenRefreshResult alloc] initWithStatusNever];
- FIRAppCheckTokenRefresher *refresher = [self createRefresher];
- // 1. Expect checking if auto-refresh allowed before scheduling the initial refresh.
- [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled];
- // 2. Don't expect the timer to be scheduled for the first refresh as the refresh should be
- // triggered straight away.
- XCTestExpectation *initialTimerCreatedExpectation =
- [self expectationWithDescription:@"initial refresh timer created"];
- initialTimerCreatedExpectation.inverted = YES;
- self.fakeTimer.createHandler = ^(NSDate *_Nonnull fireDate) {
- weakSelf.fakeTimer.createHandler = nil;
- [initialTimerCreatedExpectation fulfill];
- };
- // 3. Expect checking if auto-refresh allowed before triggering the initial refresh.
- [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled];
- // 4. Expect initial refresh handler to be called.
- __block FIRAppCheckTokenRefreshCompletion initialRefreshCompletion;
- XCTestExpectation *initialRefreshExpectation =
- [self expectationWithDescription:@"initial refresh"];
- refresher.tokenRefreshHandler = ^(FIRAppCheckTokenRefreshCompletion _Nonnull completion) {
- // Save completion to be called later.
- initialRefreshCompletion = completion;
- [initialRefreshExpectation fulfill];
- };
- NSDate *initialTokenExpirationDate = [NSDate dateWithTimeIntervalSinceNow:60 * 60];
- NSDate *initialTokenReceivedDate = [NSDate date];
- __auto_type initialRefreshResult = [[FIRAppCheckTokenRefreshResult alloc]
- initWithStatusSuccessAndExpirationDate:initialTokenExpirationDate
- receivedAtDate:initialTokenReceivedDate];
- [self waitForExpectations:@[ initialTimerCreatedExpectation, initialRefreshExpectation ]
- timeout:1];
- // 5. Expect checking if auto-refresh allowed before scheduling next refresh.
- [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled];
- // 6. Expect a next refresh timer to be scheduled on initial refresh completion.
- NSDate *expectedRefreshDate =
- [self expectedRefreshDateWithReceivedDate:initialTokenReceivedDate
- expirationDate:initialTokenExpirationDate];
- XCTestExpectation *nextTimerCreateExpectation =
- [self expectationWithDescription:@"next refresh create timer"];
- self.fakeTimer.createHandler = ^(NSDate *_Nonnull fireDate) {
- weakSelf.fakeTimer.createHandler = nil;
- XCTAssertEqualObjects(fireDate, expectedRefreshDate);
- [nextTimerCreateExpectation fulfill];
- };
- // 7. Call initial refresh completion and wait for next refresh timer to be scheduled.
- initialRefreshCompletion(initialRefreshResult);
- [self waitForExpectations:@[ nextTimerCreateExpectation ] timeout:0.5];
- // 8. Expect checking if auto-refresh allowed before triggering the next refresh.
- [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled];
- // 9. Expect refresh handler to be called for the next refresh.
- __auto_type nextRefreshResult = [[FIRAppCheckTokenRefreshResult alloc]
- initWithStatusSuccessAndExpirationDate:[expectedRefreshDate dateByAddingTimeInterval:60 * 60]
- receivedAtDate:expectedRefreshDate];
- XCTestExpectation *nextRefreshExpectation = [self expectationWithDescription:@"next refresh"];
- refresher.tokenRefreshHandler = ^(FIRAppCheckTokenRefreshCompletion _Nonnull completion) {
- [nextRefreshExpectation fulfill];
- // Call completion.
- completion(nextRefreshResult);
- };
- // 10. Fire the timer.
- [self fireTimer];
- // 11. Wait for the next refresh handler to be called.
- [self waitForExpectations:@[ nextRefreshExpectation ] timeout:1];
- OCMVerifyAll(self.mockSettings);
- }
- - (void)testNoTimeScheduledUntilHandlerSet {
- // 1. Don't expect timer to be scheduled.
- XCTestExpectation *timerCreateExpectation1 = [self expectationWithDescription:@"create timer 1"];
- timerCreateExpectation1.inverted = YES;
- self.fakeTimer.createHandler = ^(NSDate *_Nonnull fireDate) {
- [timerCreateExpectation1 fulfill];
- };
- // 2. Create a publisher.
- FIRAppCheckTokenRefresher *refresher = [self createRefresher];
- XCTAssertNotNil(refresher);
- [self waitForExpectations:@[ timerCreateExpectation1 ] timeout:0.5];
- // 3. Expect timer to be created after the handler has been set.
- // 3.1. Expect checking if auto-refresh allowed one more time when timer fires.
- [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled];
- // 3.2. Expect timer to fire.
- XCTestExpectation *timerCreateExpectation2 = [self expectationWithDescription:@"create timer 2"];
- self.fakeTimer.createHandler = ^(NSDate *_Nonnull fireDate) {
- [timerCreateExpectation2 fulfill];
- };
- // 3.3. Set handler.
- refresher.tokenRefreshHandler = ^(FIRAppCheckTokenRefreshCompletion _Nonnull completion) {
- };
- [self waitForExpectations:@[ timerCreateExpectation2 ] timeout:0.5];
- OCMVerifyAll(self.mockSettings);
- }
- - (void)testNextRefreshOnRefreshSuccess {
- FIRAppCheckTokenRefresher *refresher = [self createRefresher];
- NSDate *refreshedTokenExpirationDate =
- [self.initialTokenRefreshResult.tokenExpirationDate dateByAddingTimeInterval:60 * 60];
- __auto_type refreshResult = [[FIRAppCheckTokenRefreshResult alloc]
- initWithStatusSuccessAndExpirationDate:refreshedTokenExpirationDate
- receivedAtDate:self.initialTokenRefreshResult.tokenExpirationDate];
- // 1. Expect checking if auto-refresh allowed before scheduling initial refresh.
- [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled];
- // 2. Expect checking if auto-refresh allowed before calling the refresh handler.
- [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled];
- // 3. Expect refresh handler.
- XCTestExpectation *initialRefreshExpectation =
- [self expectationWithDescription:@"initial refresh"];
- refresher.tokenRefreshHandler = ^(FIRAppCheckTokenRefreshCompletion _Nonnull completion) {
- [initialRefreshExpectation fulfill];
- // Call completion in a while.
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)),
- dispatch_get_main_queue(), ^{
- completion(refreshResult);
- });
- };
- // 4. Expect for new timer to be created.
- NSDate *expectedFireDate =
- [self expectedRefreshDateWithReceivedDate:refreshResult.tokenReceivedAtDate
- expirationDate:refreshResult.tokenExpirationDate];
- XCTestExpectation *createTimerExpectation = [self expectationWithDescription:@"create timer"];
- self.fakeTimer.createHandler = ^(NSDate *_Nonnull fireDate) {
- [createTimerExpectation fulfill];
- XCTAssertEqualObjects(fireDate, expectedFireDate);
- };
- // 5. Expect checking if auto-refresh allowed before refreshing.
- [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled];
- // 6. Fire initial timer and wait for expectations.
- [self fireTimer];
- [self waitForExpectations:@[ initialRefreshExpectation, createTimerExpectation ]
- timeout:1
- enforceOrder:YES];
- OCMVerifyAll(self.mockSettings);
- }
- - (void)testBackoff {
- FIRAppCheckTokenRefresher *refresher = [self createRefresher];
- // Initial backoff interval.
- NSTimeInterval expectedBackoffTime = 0;
- NSTimeInterval maximumBackoffTime = 16 * 60; // 16 min.
- // 1. Expect checking if auto-refresh allowed before scheduling initial refresh.
- [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled];
- for (NSInteger i = 0; i < 10; i++) {
- // 2. Expect checking if auto-refresh allowed before calling the refresh handler.
- [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled];
- // 3. Expect refresh handler.
- XCTestExpectation *initialRefreshExpectation =
- [self expectationWithDescription:@"initial refresh"];
- refresher.tokenRefreshHandler = ^(FIRAppCheckTokenRefreshCompletion _Nonnull completion) {
- [initialRefreshExpectation fulfill];
- // Call completion in a while.
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)),
- dispatch_get_main_queue(), ^{
- __auto_type refreshFailure =
- [[FIRAppCheckTokenRefreshResult alloc] initWithStatusFailure];
- completion(refreshFailure);
- });
- };
- // 4. Expect for new timer to be created.
- // No backoff initially, 1st backoff 30sec, double backoff on each next attempt until 16min.
- expectedBackoffTime = expectedBackoffTime == 0 ? 30 : expectedBackoffTime * 2;
- expectedBackoffTime = MIN(expectedBackoffTime, maximumBackoffTime);
- NSDate *expectedFireDate = [[NSDate date] dateByAddingTimeInterval:expectedBackoffTime];
- XCTestExpectation *createTimerExpectation = [self expectationWithDescription:@"create timer"];
- self.fakeTimer.createHandler = ^(NSDate *_Nonnull fireDate) {
- [createTimerExpectation fulfill];
- // Check expected and actual fire date are not too different (account for the random part
- // and request attempt delay).
- XCTAssertLessThan(ABS([expectedFireDate timeIntervalSinceDate:fireDate]), 2);
- };
- // 5. Expect checking if auto-refresh allowed before refreshing.
- [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled];
- // 6. Fire initial timer and wait for expectations.
- [self fireTimer];
- [self waitForExpectations:@[ initialRefreshExpectation, createTimerExpectation ]
- timeout:1
- enforceOrder:YES];
- }
- OCMVerifyAll(self.mockSettings);
- }
- #pragma mark - Auto refresh is not allowed
- - (void)testNoInitialRefreshWhenAutoRefreshIsNotAllowed {
- FIRAppCheckTokenRefresher *refresher = [self createRefresher];
- // 1. Expect checking if auto-refresh allowed before scheduling initial refresh.
- [[[self.mockSettings expect] andReturnValue:@(NO)] isTokenAutoRefreshEnabled];
- // 2. Don't expect timer to be scheduled.
- XCTestExpectation *timerCreateExpectation = [self expectationWithDescription:@"create timer"];
- timerCreateExpectation.inverted = YES;
- __auto_type weakSelf = self;
- self.fakeTimer.createHandler = ^(NSDate *_Nonnull fireDate) {
- weakSelf.fakeTimer.createHandler = nil;
- [timerCreateExpectation fulfill];
- };
- // 3. Don't expect refresh handler to be called.
- __auto_type refreshResult = [[FIRAppCheckTokenRefreshResult alloc]
- initWithStatusSuccessAndExpirationDate:[NSDate dateWithTimeIntervalSinceNow:60 * 60]
- receivedAtDate:[NSDate date]];
- XCTestExpectation *refreshExpectation = [self expectationWithDescription:@"refresh"];
- refreshExpectation.inverted = YES;
- refresher.tokenRefreshHandler = ^(FIRAppCheckTokenRefreshCompletion _Nonnull completion) {
- [refreshExpectation fulfill];
- // Call completion.
- completion(refreshResult);
- };
- // 4. Check if the handler is not fired before the timer.
- [self waitForExpectations:@[ timerCreateExpectation, refreshExpectation ] timeout:1];
- OCMVerifyAll(self.mockSettings);
- }
- - (void)testNoRefreshWhenAutoRefreshWasDisabledAfterInit {
- FIRAppCheckTokenRefresher *refresher = [self createRefresher];
- // 1. Expect checking if auto-refresh allowed before scheduling initial refresh.
- [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled];
- // 2. Expect timer to be scheduled.
- NSDate *expectedTimerFireDate =
- [self expectedRefreshDateWithReceivedDate:self.initialTokenRefreshResult.tokenReceivedAtDate
- expirationDate:self.initialTokenRefreshResult.tokenExpirationDate];
- XCTestExpectation *timerCreateExpectation = [self expectationWithDescription:@"create timer"];
- __auto_type weakSelf = self;
- self.fakeTimer.createHandler = ^(NSDate *_Nonnull fireDate) {
- weakSelf.fakeTimer.createHandler = nil;
- XCTAssertEqualObjects(fireDate, expectedTimerFireDate);
- [timerCreateExpectation fulfill];
- };
- // 3. Expect refresh handler to be called.
- __auto_type refreshResult = [[FIRAppCheckTokenRefreshResult alloc]
- initWithStatusSuccessAndExpirationDate:[expectedTimerFireDate
- dateByAddingTimeInterval:60 * 60]
- receivedAtDate:expectedTimerFireDate];
- XCTestExpectation *noRefreshExpectation = [self expectationWithDescription:@"initial refresh"];
- noRefreshExpectation.inverted = YES;
- refresher.tokenRefreshHandler = ^(FIRAppCheckTokenRefreshCompletion _Nonnull completion) {
- [noRefreshExpectation fulfill];
- // Call completion.
- completion(refreshResult);
- };
- // 4. Check if the handler is not fired before the timer.
- [self waitForExpectations:@[ timerCreateExpectation ] timeout:1];
- // 5. Expect checking if auto-refresh allowed before refreshing.
- [[[self.mockSettings expect] andReturnValue:@(NO)] isTokenAutoRefreshEnabled];
- // 6. Fire the timer and wait for completion.
- [self fireTimer];
- [self waitForExpectations:@[ noRefreshExpectation ] timeout:1];
- OCMVerifyAll(self.mockSettings);
- }
- #pragma mark - Update token expiration
- - (void)testUpdateWithRefreshResultWhenAutoRefreshIsAllowed {
- FIRAppCheckTokenRefresher *refresher = [self createRefresher];
- NSDate *newExpirationDate =
- [self.initialTokenRefreshResult.tokenExpirationDate dateByAddingTimeInterval:10 * 60];
- __auto_type newRefreshResult = [[FIRAppCheckTokenRefreshResult alloc]
- initWithStatusSuccessAndExpirationDate:newExpirationDate
- receivedAtDate:self.initialTokenRefreshResult.tokenExpirationDate];
- // 1. Expect checking if auto-refresh allowed before scheduling refresh.
- [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled];
- // 2. Expect timer to be scheduled.
- NSDate *expectedTimerFireDate =
- [self expectedRefreshDateWithReceivedDate:newRefreshResult.tokenReceivedAtDate
- expirationDate:newRefreshResult.tokenExpirationDate];
- XCTestExpectation *timerCreateExpectation = [self expectationWithDescription:@"create timer"];
- __auto_type weakSelf = self;
- self.fakeTimer.createHandler = ^(NSDate *_Nonnull fireDate) {
- weakSelf.fakeTimer.createHandler = nil;
- XCTAssertEqualObjects(fireDate, expectedTimerFireDate);
- [timerCreateExpectation fulfill];
- };
- // 3. Update token expiration date.
- [refresher updateWithRefreshResult:newRefreshResult];
- // 4. Wait for timer to be created.
- [self waitForExpectations:@[ timerCreateExpectation ] timeout:1];
- OCMVerifyAll(self.mockSettings);
- }
- - (void)testUpdateWithRefreshResultWhenAutoRefreshIsNotAllowed {
- FIRAppCheckTokenRefresher *refresher = [self createRefresher];
- __auto_type newRefreshResult = [[FIRAppCheckTokenRefreshResult alloc]
- initWithStatusSuccessAndExpirationDate:[NSDate dateWithTimeIntervalSinceNow:60 * 60]
- receivedAtDate:self.initialTokenRefreshResult.tokenExpirationDate];
- // 1. Expect checking if auto-refresh allowed before scheduling initial refresh.
- [[[self.mockSettings expect] andReturnValue:@(NO)] isTokenAutoRefreshEnabled];
- // 2. Don't expect timer to be scheduled.
- XCTestExpectation *timerCreateExpectation = [self expectationWithDescription:@"create timer"];
- timerCreateExpectation.inverted = YES;
- __auto_type weakSelf = self;
- self.fakeTimer.createHandler = ^(NSDate *_Nonnull fireDate) {
- weakSelf.fakeTimer.createHandler = nil;
- [timerCreateExpectation fulfill];
- };
- // 3. Update token expiration date.
- [refresher updateWithRefreshResult:newRefreshResult];
- // 4. Wait for timer to be created.
- [self waitForExpectations:@[ timerCreateExpectation ] timeout:1];
- OCMVerifyAll(self.mockSettings);
- }
- - (void)testUpdateWithRefreshResult_WhenTokenExpiresLessThanIn1Minute {
- FIRAppCheckTokenRefresher *refresher = [self createRefresher];
- NSDate *newExpirationDate = [NSDate dateWithTimeIntervalSinceNow:0.5 * 60];
- __auto_type newRefreshResult = [[FIRAppCheckTokenRefreshResult alloc]
- initWithStatusSuccessAndExpirationDate:newExpirationDate
- receivedAtDate:[NSDate date]];
- // 1. Expect checking if auto-refresh allowed before scheduling refresh.
- [[[self.mockSettings expect] andReturnValue:@(YES)] isTokenAutoRefreshEnabled];
- // 2. Expect timer to be scheduled in at least 1 minute.
- XCTestExpectation *timerCreateExpectation = [self expectationWithDescription:@"create timer"];
- __auto_type weakSelf = self;
- self.fakeTimer.createHandler = ^(NSDate *_Nonnull fireDate) {
- weakSelf.fakeTimer.createHandler = nil;
- // 1 minute is the minimal interval between successful refreshes.
- XCTAssert([FIRDateTestUtils isDate:fireDate
- approximatelyEqualCurrentPlusTimeInterval:60
- precision:1]);
- [timerCreateExpectation fulfill];
- };
- // 3. Update token expiration date.
- [refresher updateWithRefreshResult:newRefreshResult];
- // 4. Wait for timer to be created.
- [self waitForExpectations:@[ timerCreateExpectation ] timeout:1];
- OCMVerifyAll(self.mockSettings);
- }
- #pragma mark - Helpers
- - (void)fireTimer {
- if (self.fakeTimer.handler) {
- self.fakeTimer.handler();
- } else {
- XCTFail(@"handler must not be nil!");
- }
- }
- - (FIRAppCheckTokenRefresher *)createRefresher {
- return [[FIRAppCheckTokenRefresher alloc] initWithRefreshResult:self.initialTokenRefreshResult
- timerProvider:[self.fakeTimer fakeTimerProvider]
- settings:self.mockSettings];
- }
- - (NSDate *)expectedRefreshDateWithReceivedDate:(NSDate *)receivedDate
- expirationDate:(NSDate *)expirationDate {
- NSTimeInterval timeToLive = [expirationDate timeIntervalSinceDate:receivedDate];
- XCTAssertGreaterThanOrEqual(timeToLive, 0);
- NSTimeInterval timeToRefresh = timeToLive / 2 + 5 * 60; // 50% of TTL + 5 min
- NSTimeInterval minimalAutoRefreshInterval = 60; // 1 min
- timeToRefresh = MAX(timeToRefresh, minimalAutoRefreshInterval);
- NSDate *refreshDate = [receivedDate dateByAddingTimeInterval:timeToRefresh];
- NSDate *now = [NSDate date];
- return [refreshDate laterDate:now];
- }
- @end
|