voluptasmollitia
Version:
Monorepo for the Firebase JavaScript SDK
153 lines (135 loc) • 5.67 kB
text/typescript
/**
* @license
* Copyright 2020 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 { expect, use } from 'chai';
import * as chaiAsPromised from 'chai-as-promised';
import * as sinon from 'sinon';
import * as sinonChai from 'sinon-chai';
import { testAuth, testUser } from '../../../test/helpers/mock_auth';
import { UserInternal } from '../../model/user';
import { AuthErrorCode } from '../errors';
import { _createError } from '../util/assert';
import { Duration, ProactiveRefresh } from './proactive_refresh';
use(chaiAsPromised);
use(sinonChai);
describe('core/user/proactive_refresh', () => {
let user: UserInternal;
let proactiveRefresh: ProactiveRefresh;
let getTokenStub: sinon.SinonStub;
let clock: sinon.SinonFakeTimers;
// Sets the expiration time in accordance with the offset in proactive refresh
// This translates to the interval between updates
function setExpirationTime(offset: number): void {
user.stsTokenManager.expirationTime = Duration.OFFSET + offset;
}
// clock.nextAsync() returns the number of milliseconds since the unix epoch.
// We have set "now" to be the epoch. This function is like clock.nextAsync
// except that it returns the amount of time *for that one timeout* rather
// than the time since the epoch
async function nextAsync(): Promise<number> {
const now = Date.now();
const timeoutTime = (await clock.nextAsync()) - now;
return timeoutTime;
}
beforeEach(async () => {
const auth = await testAuth();
user = testUser(auth, 'uid');
proactiveRefresh = new ProactiveRefresh(user);
getTokenStub = sinon
.stub(user, 'getIdToken')
.returns(Promise.resolve('foo'));
clock = sinon.useFakeTimers({
now: 0,
shouldAdvanceTime: false
});
});
afterEach(() => {
sinon.restore();
});
it('calls getToken at regular intervals', async () => {
setExpirationTime(1000);
proactiveRefresh._start();
expect(await clock.nextAsync()).to.eq(1000);
expect(await clock.nextAsync()).to.eq(1000);
expect(await clock.nextAsync()).to.eq(1000);
expect(getTokenStub.getCalls().length).to.eq(3);
});
it('stops getting token when _stop is called', async () => {
setExpirationTime(1000);
proactiveRefresh._start();
await clock.nextAsync();
proactiveRefresh._stop();
await clock.nextAsync();
await clock.nextAsync();
await clock.nextAsync();
expect(getTokenStub.getCalls().length).to.eq(1);
});
it('stops getting token when a non-network error occurs', async () => {
setExpirationTime(1000);
getTokenStub.callsFake(() => Promise.reject(new Error('no')));
proactiveRefresh._start();
await clock.nextAsync();
await clock.nextAsync();
await clock.nextAsync();
expect(getTokenStub.getCalls().length).to.eq(1);
});
context('error backoff', () => {
const error = _createError(AuthErrorCode.NETWORK_REQUEST_FAILED, {
appName: 'app'
});
beforeEach(() => {
getTokenStub.callsFake(() => Promise.reject(error));
});
it('schedules a backoff when a network error occurs', async () => {
setExpirationTime(1000);
proactiveRefresh._start();
expect(await nextAsync()).to.eq(1000);
expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN);
});
it('backoff continues to increase until the max', async () => {
setExpirationTime(1000);
proactiveRefresh._start();
expect(await nextAsync()).to.eq(1000);
expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN);
expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN * 2);
expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN * 4);
expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN * 8);
expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN * 16);
expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN * 32);
expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN * 32);
expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN * 32);
});
it('backoff resets after one success', async () => {
setExpirationTime(1000);
proactiveRefresh._start();
expect(await nextAsync()).to.eq(1000);
expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN);
expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN * 2);
expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN * 4);
expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN * 8);
expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN * 16);
setExpirationTime(1000 + Date.now() + Duration.RETRY_BACKOFF_MIN * 32);
getTokenStub.callsFake(() => Promise.resolve());
expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN * 32);
expect(await nextAsync()).to.eq(1000);
getTokenStub.callsFake(() => Promise.reject(error));
await nextAsync();
expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN);
expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN * 2);
expect(await nextAsync()).to.eq(Duration.RETRY_BACKOFF_MIN * 4);
});
});
});