mirror of
https://github.com/hanzoai/firebase-js-sdk.git
synced 2026-09-24 16:59:10 +00:00
647 lines
20 KiB
TypeScript
647 lines
20 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2019 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 { FirebaseApp } from '@firebase/app';
|
|
import {
|
|
FetchResponse,
|
|
RemoteConfig as RemoteConfigType,
|
|
LogLevel as RemoteConfigLogLevel
|
|
} from '../src/public_types';
|
|
import { expect } from 'chai';
|
|
import * as sinon from 'sinon';
|
|
import { StorageCache } from '../src/storage/storage_cache';
|
|
import { Storage } from '../src/storage/storage';
|
|
import { RemoteConfig } from '../src/remote_config';
|
|
import { RemoteConfigFetchClient } from '../src/client/remote_config_fetch_client';
|
|
import { Value } from '../src/value';
|
|
import './setup';
|
|
import { ERROR_FACTORY, ErrorCode } from '../src/errors';
|
|
import { Logger, LogLevel as FirebaseLogLevel } from '@firebase/logger';
|
|
import {
|
|
activate,
|
|
ensureInitialized,
|
|
getAll,
|
|
getBoolean,
|
|
getNumber,
|
|
getString,
|
|
getValue,
|
|
setLogLevel,
|
|
fetchConfig,
|
|
setCustomSignals
|
|
} from '../src/api';
|
|
import * as api from '../src/api';
|
|
import { fetchAndActivate } from '../src';
|
|
import { restore } from 'sinon';
|
|
import { Experiment } from '../src/abt/experiment';
|
|
import { Provider } from '@firebase/component';
|
|
import { FirebaseAnalyticsInternalName } from '@firebase/analytics-interop-types';
|
|
import { RealtimeHandler } from '../src/client/realtime_handler';
|
|
|
|
describe('RemoteConfig', () => {
|
|
const ACTIVE_CONFIG = {
|
|
key1: 'active_config_value_1',
|
|
key2: 'active_config_value_2',
|
|
key3: 'true',
|
|
key4: '123'
|
|
};
|
|
const DEFAULT_CONFIG = {
|
|
key1: 'default_config_value_1',
|
|
key2: 'default_config_value_2',
|
|
key3: 'false',
|
|
key4: '345',
|
|
test: 'test'
|
|
};
|
|
|
|
let app: FirebaseApp;
|
|
let client: RemoteConfigFetchClient;
|
|
let storageCache: StorageCache;
|
|
let storage: Storage;
|
|
let logger: Logger;
|
|
let realtimeHandler: RealtimeHandler;
|
|
let rc: RemoteConfigType;
|
|
let analyticsProvider: Provider<FirebaseAnalyticsInternalName>;
|
|
|
|
let getActiveConfigStub: sinon.SinonStub;
|
|
let loggerDebugSpy: sinon.SinonSpy;
|
|
let loggerLogLevelSpy: any;
|
|
|
|
beforeEach(() => {
|
|
// Clears stubbed behavior between each test.
|
|
app = {} as FirebaseApp;
|
|
client = {} as RemoteConfigFetchClient;
|
|
storageCache = {} as StorageCache;
|
|
storage = {} as Storage;
|
|
analyticsProvider = {} as Provider<FirebaseAnalyticsInternalName>;
|
|
realtimeHandler = {} as RealtimeHandler;
|
|
logger = new Logger('package-name');
|
|
getActiveConfigStub = sinon.stub().returns(undefined);
|
|
storageCache.getActiveConfig = getActiveConfigStub;
|
|
loggerDebugSpy = sinon.spy(logger, 'debug');
|
|
loggerLogLevelSpy = sinon.spy(logger, 'logLevel', ['set']);
|
|
rc = new RemoteConfig(
|
|
app,
|
|
client,
|
|
storageCache,
|
|
storage,
|
|
logger,
|
|
realtimeHandler,
|
|
analyticsProvider
|
|
);
|
|
});
|
|
|
|
afterEach(() => {
|
|
loggerDebugSpy.restore();
|
|
loggerLogLevelSpy.restore();
|
|
});
|
|
|
|
describe('setCustomSignals', () => {
|
|
beforeEach(() => {
|
|
storageCache.setCustomSignals = sinon.stub();
|
|
storage.setCustomSignals = sinon.stub();
|
|
logger.error = sinon.stub();
|
|
});
|
|
|
|
it('call storage API to store signals', async () => {
|
|
await setCustomSignals(rc, { key: 'value' });
|
|
|
|
expect(storageCache.setCustomSignals).to.have.been.calledWith({
|
|
key: 'value'
|
|
});
|
|
});
|
|
|
|
it('logs an error when supplied with a custom signal key greater than 250 characters', async () => {
|
|
const longKey = 'a'.repeat(251);
|
|
const customSignals = { [longKey]: 'value' };
|
|
|
|
await setCustomSignals(rc, customSignals);
|
|
|
|
expect(storageCache.setCustomSignals).to.not.have.been.called;
|
|
expect(logger.error).to.have.been.called;
|
|
});
|
|
|
|
it('logs an error when supplied with a custom signal value greater than 500 characters', async () => {
|
|
const longValue = 'a'.repeat(501);
|
|
const customSignals = { 'key': longValue };
|
|
|
|
await setCustomSignals(rc, customSignals);
|
|
|
|
expect(storageCache.setCustomSignals).to.not.have.been.called;
|
|
expect(logger.error).to.have.been.called;
|
|
});
|
|
|
|
it('empty custom signals map does nothing', async () => {
|
|
await setCustomSignals(rc, {});
|
|
|
|
expect(storageCache.setCustomSignals).to.not.have.been.called;
|
|
});
|
|
});
|
|
|
|
// Adapts getUserLanguage tests from packages/auth/test/utils_test.js for TypeScript.
|
|
describe('setLogLevel', () => {
|
|
it('proxies to the FirebaseLogger instance', () => {
|
|
setLogLevel(rc, 'debug');
|
|
|
|
// Casts spy to any because property setters aren't defined on the SinonSpy type.
|
|
expect(loggerLogLevelSpy.set).to.have.been.calledWith(
|
|
FirebaseLogLevel.DEBUG
|
|
);
|
|
});
|
|
|
|
it('normalizes levels other than DEBUG and SILENT to ERROR', () => {
|
|
for (const logLevel of ['info', 'verbose', 'error', 'severe']) {
|
|
setLogLevel(rc, logLevel as RemoteConfigLogLevel);
|
|
|
|
// Casts spy to any because property setters aren't defined on the SinonSpy type.
|
|
expect(loggerLogLevelSpy.set).to.have.been.calledWith(
|
|
FirebaseLogLevel.ERROR
|
|
);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('ensureInitialized', () => {
|
|
it('warms cache', async () => {
|
|
storageCache.loadFromStorage = sinon.stub().returns(Promise.resolve());
|
|
|
|
await ensureInitialized(rc);
|
|
|
|
expect(storageCache.loadFromStorage).to.have.been.calledOnce;
|
|
});
|
|
|
|
it('de-duplicates repeated calls', async () => {
|
|
storageCache.loadFromStorage = sinon.stub().returns(Promise.resolve());
|
|
|
|
await ensureInitialized(rc);
|
|
await ensureInitialized(rc);
|
|
|
|
expect(storageCache.loadFromStorage).to.have.been.calledOnce;
|
|
});
|
|
});
|
|
|
|
describe('fetchTimeMillis', () => {
|
|
it('normalizes undefined values', async () => {
|
|
storageCache.getLastSuccessfulFetchTimestampMillis = sinon
|
|
.stub()
|
|
.returns(undefined);
|
|
|
|
expect(rc.fetchTimeMillis).to.eq(-1);
|
|
});
|
|
|
|
it('reads from cache', async () => {
|
|
const lastFetchTimeMillis = 123;
|
|
|
|
storageCache.getLastSuccessfulFetchTimestampMillis = sinon
|
|
.stub()
|
|
.returns(lastFetchTimeMillis);
|
|
|
|
expect(rc.fetchTimeMillis).to.eq(lastFetchTimeMillis);
|
|
});
|
|
});
|
|
|
|
describe('lastFetchStatus', () => {
|
|
it('normalizes undefined values', async () => {
|
|
storageCache.getLastFetchStatus = sinon.stub().returns(undefined);
|
|
|
|
expect(rc.lastFetchStatus).to.eq('no-fetch-yet');
|
|
});
|
|
|
|
it('reads from cache', async () => {
|
|
const lastFetchStatus = 'success';
|
|
|
|
storageCache.getLastFetchStatus = sinon.stub().returns(lastFetchStatus);
|
|
|
|
expect(rc.lastFetchStatus).to.eq(lastFetchStatus);
|
|
});
|
|
});
|
|
|
|
describe('getValue', () => {
|
|
it('returns the active value if available', () => {
|
|
getActiveConfigStub.returns(ACTIVE_CONFIG);
|
|
rc.defaultConfig = DEFAULT_CONFIG;
|
|
|
|
expect(getValue(rc, 'key1')).to.deep.eq(
|
|
new Value('remote', ACTIVE_CONFIG.key1)
|
|
);
|
|
});
|
|
|
|
it('returns the default value if active is not available', () => {
|
|
rc.defaultConfig = DEFAULT_CONFIG;
|
|
|
|
expect(getValue(rc, 'key1')).to.deep.eq(
|
|
new Value('default', DEFAULT_CONFIG.key1)
|
|
);
|
|
});
|
|
|
|
it('returns the stringified default boolean values if active is not available', () => {
|
|
const DEFAULTS = { trueVal: true, falseVal: false };
|
|
rc.defaultConfig = DEFAULTS;
|
|
|
|
expect(getValue(rc, 'trueVal')).to.deep.eq(
|
|
new Value('default', String(DEFAULTS.trueVal))
|
|
);
|
|
expect(getValue(rc, 'falseVal')).to.deep.eq(
|
|
new Value('default', String(DEFAULTS.falseVal))
|
|
);
|
|
});
|
|
|
|
it('returns the stringified default numeric values if active is not available', () => {
|
|
const DEFAULTS = { negative: -1, zero: 0, positive: 11 };
|
|
rc.defaultConfig = DEFAULTS;
|
|
|
|
expect(getValue(rc, 'negative')).to.deep.eq(
|
|
new Value('default', String(DEFAULTS.negative))
|
|
);
|
|
expect(getValue(rc, 'zero')).to.deep.eq(
|
|
new Value('default', String(DEFAULTS.zero))
|
|
);
|
|
expect(getValue(rc, 'positive')).to.deep.eq(
|
|
new Value('default', String(DEFAULTS.positive))
|
|
);
|
|
});
|
|
|
|
it('returns the static value if active and default are not available', () => {
|
|
expect(getValue(rc, 'key1')).to.deep.eq(new Value('static'));
|
|
|
|
// Asserts debug message logged if static value is returned, per EAP feedback.
|
|
expect(logger.debug).to.have.been.called;
|
|
});
|
|
|
|
it('logs if initialization is incomplete', async () => {
|
|
// Defines default value to isolate initialization logging from static value logging.
|
|
rc.defaultConfig = { key1: 'val' };
|
|
|
|
// Gets value before initialization.
|
|
getValue(rc, 'key1');
|
|
|
|
// Asserts getValue logs.
|
|
expect(logger.debug).to.have.been.called;
|
|
|
|
// Enables initialization to complete.
|
|
storageCache.loadFromStorage = sinon.stub().returns(Promise.resolve());
|
|
|
|
// Ensures initialization completes.
|
|
await ensureInitialized(rc);
|
|
|
|
// Gets value after initialization.
|
|
getValue(rc, 'key1');
|
|
|
|
// Asserts getValue doesn't log after initialization is complete.
|
|
expect(logger.debug).to.have.been.calledOnce;
|
|
});
|
|
});
|
|
|
|
describe('getBoolean', () => {
|
|
it('returns the active value if available', () => {
|
|
getActiveConfigStub.returns(ACTIVE_CONFIG);
|
|
rc.defaultConfig = DEFAULT_CONFIG;
|
|
|
|
expect(getBoolean(rc, 'key3')).to.be.true;
|
|
});
|
|
|
|
it('returns the default value if active is not available', () => {
|
|
rc.defaultConfig = DEFAULT_CONFIG;
|
|
|
|
expect(getBoolean(rc, 'key3')).to.be.false;
|
|
});
|
|
|
|
it('returns the static value if active and default are not available', () => {
|
|
expect(getBoolean(rc, 'key3')).to.be.false;
|
|
});
|
|
});
|
|
|
|
describe('getString', () => {
|
|
it('returns the active value if available', () => {
|
|
getActiveConfigStub.returns(ACTIVE_CONFIG);
|
|
rc.defaultConfig = DEFAULT_CONFIG;
|
|
|
|
expect(getString(rc, 'key1')).to.eq(ACTIVE_CONFIG.key1);
|
|
});
|
|
|
|
it('returns the default value if active is not available', () => {
|
|
rc.defaultConfig = DEFAULT_CONFIG;
|
|
|
|
expect(getString(rc, 'key2')).to.eq(DEFAULT_CONFIG.key2);
|
|
});
|
|
|
|
it('returns the static value if active and default are not available', () => {
|
|
expect(getString(rc, 'key1')).to.eq('');
|
|
});
|
|
});
|
|
|
|
describe('getNumber', () => {
|
|
it('returns the active value if available', () => {
|
|
getActiveConfigStub.returns(ACTIVE_CONFIG);
|
|
rc.defaultConfig = DEFAULT_CONFIG;
|
|
|
|
expect(getNumber(rc, 'key4')).to.eq(Number(ACTIVE_CONFIG.key4));
|
|
});
|
|
|
|
it('returns the default value if active is not available', () => {
|
|
rc.defaultConfig = DEFAULT_CONFIG;
|
|
|
|
expect(getNumber(rc, 'key4')).to.eq(Number(DEFAULT_CONFIG.key4));
|
|
});
|
|
|
|
it('returns the static value if active and default are not available', () => {
|
|
expect(getNumber(rc, 'key1')).to.eq(0);
|
|
});
|
|
});
|
|
|
|
describe('getAll', () => {
|
|
it('returns values for all keys included in active and default configs', () => {
|
|
getActiveConfigStub.returns(ACTIVE_CONFIG);
|
|
rc.defaultConfig = DEFAULT_CONFIG;
|
|
|
|
expect(getAll(rc)).to.deep.eq({
|
|
key1: new Value('remote', ACTIVE_CONFIG.key1),
|
|
key2: new Value('remote', ACTIVE_CONFIG.key2),
|
|
key3: new Value('remote', ACTIVE_CONFIG.key3),
|
|
key4: new Value('remote', ACTIVE_CONFIG.key4),
|
|
test: new Value('default', DEFAULT_CONFIG.test)
|
|
});
|
|
});
|
|
|
|
it('returns values in default if active is not available', () => {
|
|
rc.defaultConfig = DEFAULT_CONFIG;
|
|
|
|
expect(getAll(rc)).to.deep.eq({
|
|
key1: new Value('default', DEFAULT_CONFIG.key1),
|
|
key2: new Value('default', DEFAULT_CONFIG.key2),
|
|
key3: new Value('default', DEFAULT_CONFIG.key3),
|
|
key4: new Value('default', DEFAULT_CONFIG.key4),
|
|
test: new Value('default', DEFAULT_CONFIG.test)
|
|
});
|
|
});
|
|
|
|
it('returns empty object if both active and default configs are not defined', () => {
|
|
expect(getAll(rc)).to.deep.eq({});
|
|
});
|
|
});
|
|
|
|
describe('activate', () => {
|
|
const ETAG = 'etag';
|
|
const CONFIG = { key: 'val' };
|
|
const NEW_ETAG = 'new_etag';
|
|
const TEMPLATE_VERSION = 1;
|
|
const EXPERIMENTS = [
|
|
{
|
|
'experimentId': '_exp_1',
|
|
'variantId': '1',
|
|
'experimentStartTime': '2025-04-06T14:13:57.597Z',
|
|
'triggerTimeoutMillis': '15552000000',
|
|
'timeToLiveMillis': '15552000000'
|
|
}
|
|
];
|
|
|
|
let sandbox: sinon.SinonSandbox;
|
|
let updateActiveExperimentsStub: sinon.SinonStub;
|
|
let getLastSuccessfulFetchResponseStub: sinon.SinonStub;
|
|
let getActiveConfigEtagStub: sinon.SinonStub;
|
|
let getActiveConfigTemplateVersionStub: sinon.SinonStub;
|
|
let setActiveConfigEtagStub: sinon.SinonStub;
|
|
let setActiveConfigStub: sinon.SinonStub;
|
|
let setActiveConfigTemplateVersionStub: sinon.SinonStub;
|
|
|
|
beforeEach(() => {
|
|
sandbox = sinon.createSandbox();
|
|
updateActiveExperimentsStub = sandbox.stub(
|
|
Experiment.prototype,
|
|
'updateActiveExperiments'
|
|
);
|
|
getLastSuccessfulFetchResponseStub = sinon.stub();
|
|
getActiveConfigEtagStub = sinon.stub();
|
|
getActiveConfigTemplateVersionStub = sinon.stub();
|
|
setActiveConfigEtagStub = sinon.stub();
|
|
setActiveConfigStub = sinon.stub();
|
|
setActiveConfigTemplateVersionStub = sinon.stub();
|
|
|
|
storage.getLastSuccessfulFetchResponse =
|
|
getLastSuccessfulFetchResponseStub;
|
|
storage.getActiveConfigEtag = getActiveConfigEtagStub;
|
|
storage.getActiveConfigTemplateVersion =
|
|
getActiveConfigTemplateVersionStub;
|
|
storage.setActiveConfigEtag = setActiveConfigEtagStub;
|
|
storageCache.setActiveConfig = setActiveConfigStub;
|
|
storage.setActiveConfigTemplateVersion =
|
|
setActiveConfigTemplateVersionStub;
|
|
});
|
|
|
|
afterEach(() => {
|
|
sandbox.restore();
|
|
});
|
|
|
|
afterEach(() => {
|
|
sandbox.restore();
|
|
});
|
|
|
|
it('does not activate if last successful fetch response is undefined', async () => {
|
|
getLastSuccessfulFetchResponseStub.returns(Promise.resolve());
|
|
getActiveConfigEtagStub.returns(Promise.resolve(ETAG));
|
|
getActiveConfigTemplateVersionStub.returns(
|
|
Promise.resolve(TEMPLATE_VERSION)
|
|
);
|
|
|
|
const activateResponse = await activate(rc);
|
|
|
|
expect(activateResponse).to.be.false;
|
|
expect(storage.setActiveConfigEtag).to.not.have.been.called;
|
|
expect(storageCache.setActiveConfig).to.not.have.been.called;
|
|
expect(storage.setActiveConfigTemplateVersion).to.not.have.been.called;
|
|
expect(updateActiveExperimentsStub).to.not.have.been.called;
|
|
});
|
|
|
|
it('does not activate if fetched and active etags are the same', async () => {
|
|
getLastSuccessfulFetchResponseStub.returns(
|
|
Promise.resolve({
|
|
config: {},
|
|
eTag: ETAG,
|
|
templateVersion: TEMPLATE_VERSION
|
|
})
|
|
);
|
|
getActiveConfigEtagStub.returns(Promise.resolve(ETAG));
|
|
|
|
const activateResponse = await activate(rc);
|
|
|
|
expect(activateResponse).to.be.false;
|
|
expect(storage.setActiveConfigEtag).to.not.have.been.called;
|
|
expect(storageCache.setActiveConfig).to.not.have.been.called;
|
|
expect(storage.setActiveConfigTemplateVersion).to.not.have.been.called;
|
|
expect(updateActiveExperimentsStub).to.not.have.been.called;
|
|
});
|
|
|
|
it('activates if fetched and active etags are different', async () => {
|
|
getLastSuccessfulFetchResponseStub.returns(
|
|
Promise.resolve({
|
|
config: CONFIG,
|
|
eTag: NEW_ETAG,
|
|
templateVersion: TEMPLATE_VERSION,
|
|
experiments: EXPERIMENTS
|
|
})
|
|
);
|
|
getActiveConfigEtagStub.returns(Promise.resolve(ETAG));
|
|
|
|
const activateResponse = await activate(rc);
|
|
|
|
expect(activateResponse).to.be.true;
|
|
expect(storage.setActiveConfigEtag).to.have.been.calledWith(NEW_ETAG);
|
|
expect(storageCache.setActiveConfig).to.have.been.calledWith(CONFIG);
|
|
expect(storage.setActiveConfigTemplateVersion).to.have.been.calledWith(
|
|
TEMPLATE_VERSION
|
|
);
|
|
});
|
|
|
|
it('activates if fetched is defined but active config is not', async () => {
|
|
getLastSuccessfulFetchResponseStub.returns(
|
|
Promise.resolve({
|
|
config: CONFIG,
|
|
eTag: NEW_ETAG,
|
|
templateVersion: TEMPLATE_VERSION,
|
|
experiments: EXPERIMENTS
|
|
})
|
|
);
|
|
getActiveConfigEtagStub.returns(Promise.resolve());
|
|
|
|
const activateResponse = await activate(rc);
|
|
|
|
expect(activateResponse).to.be.true;
|
|
expect(storage.setActiveConfigEtag).to.have.been.calledWith(NEW_ETAG);
|
|
expect(storageCache.setActiveConfig).to.have.been.calledWith(CONFIG);
|
|
expect(storage.setActiveConfigTemplateVersion).to.have.been.calledWith(
|
|
TEMPLATE_VERSION
|
|
);
|
|
expect(updateActiveExperimentsStub).to.have.been.calledWith(EXPERIMENTS);
|
|
});
|
|
});
|
|
|
|
describe('fetchAndActivate', () => {
|
|
let rcActivateStub: sinon.SinonStub<[RemoteConfigType], Promise<boolean>>;
|
|
|
|
beforeEach(() => {
|
|
sinon.stub(api, 'fetchConfig').returns(Promise.resolve());
|
|
rcActivateStub = sinon.stub(api, 'activate');
|
|
});
|
|
|
|
afterEach(() => restore());
|
|
|
|
it('calls fetch and activate and returns activation boolean if true', async () => {
|
|
rcActivateStub.returns(Promise.resolve(true));
|
|
|
|
const response = await fetchAndActivate(rc);
|
|
|
|
expect(response).to.be.true;
|
|
expect(api.fetchConfig).to.have.been.calledWith(rc);
|
|
expect(api.activate).to.have.been.calledWith(rc);
|
|
});
|
|
|
|
it('calls fetch and activate and returns activation boolean if false', async () => {
|
|
rcActivateStub.returns(Promise.resolve(false));
|
|
|
|
const response = await fetchAndActivate(rc);
|
|
|
|
expect(response).to.be.false;
|
|
expect(api.fetchConfig).to.have.been.calledWith(rc);
|
|
expect(api.activate).to.have.been.calledWith(rc);
|
|
});
|
|
});
|
|
|
|
describe('fetch', () => {
|
|
let timeoutStub: sinon.SinonStub<
|
|
[callback: (args: void) => void, ms?: number | undefined]
|
|
>;
|
|
beforeEach(() => {
|
|
client.fetch = sinon
|
|
.stub()
|
|
.returns(Promise.resolve({ status: 200 } as FetchResponse));
|
|
storageCache.setLastFetchStatus = sinon.stub();
|
|
storageCache.getCustomSignals = sinon.stub();
|
|
timeoutStub = sinon.stub(window, 'setTimeout');
|
|
});
|
|
|
|
afterEach(() => {
|
|
timeoutStub.restore();
|
|
});
|
|
|
|
it('defines a default timeout', async () => {
|
|
await fetchConfig(rc);
|
|
|
|
expect(timeoutStub).to.have.been.calledWith(sinon.match.any, 60000);
|
|
});
|
|
|
|
it('honors a custom timeout', async () => {
|
|
rc.settings.fetchTimeoutMillis = 1000;
|
|
|
|
await fetchConfig(rc);
|
|
|
|
expect(timeoutStub).to.have.been.calledWith(sinon.match.any, 1000);
|
|
});
|
|
|
|
it('sets success status', async () => {
|
|
for (const status of [200, 304]) {
|
|
client.fetch = sinon
|
|
.stub()
|
|
.returns(Promise.resolve({ status } as FetchResponse));
|
|
|
|
await fetchConfig(rc);
|
|
|
|
expect(storageCache.setLastFetchStatus).to.have.been.calledWith(
|
|
'success'
|
|
);
|
|
}
|
|
});
|
|
|
|
it('sets throttle status', async () => {
|
|
storage.getThrottleMetadata = sinon.stub().returns(Promise.resolve({}));
|
|
|
|
const error = ERROR_FACTORY.create(ErrorCode.FETCH_THROTTLE, {
|
|
throttleEndTimeMillis: 123
|
|
});
|
|
|
|
client.fetch = sinon.stub().returns(Promise.reject(error));
|
|
|
|
const fetchPromise = fetchConfig(rc);
|
|
|
|
await expect(fetchPromise).to.eventually.be.rejectedWith(error);
|
|
expect(storageCache.setLastFetchStatus).to.have.been.calledWith(
|
|
'throttle'
|
|
);
|
|
});
|
|
|
|
it('defaults to failure status', async () => {
|
|
storage.getThrottleMetadata = sinon.stub().returns(Promise.resolve());
|
|
|
|
const error = ERROR_FACTORY.create(ErrorCode.FETCH_STATUS, {
|
|
httpStatus: 400
|
|
});
|
|
|
|
client.fetch = sinon.stub().returns(Promise.reject(error));
|
|
|
|
const fetchPromise = fetchConfig(rc);
|
|
|
|
await expect(fetchPromise).to.eventually.be.rejectedWith(error);
|
|
expect(storageCache.setLastFetchStatus).to.have.been.calledWith(
|
|
'failure'
|
|
);
|
|
});
|
|
|
|
it('sends custom signals', async () => {
|
|
await fetchConfig(rc);
|
|
|
|
expect(storageCache.getCustomSignals).to.have.been.called;
|
|
});
|
|
});
|
|
});
|