You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
746 lines
22 KiB
746 lines
22 KiB
4 years ago
|
import assert from 'assert'
|
||
|
import sinon from 'sinon'
|
||
|
|
||
|
import { ethers } from 'ethers'
|
||
|
import BigNumber from 'bignumber.js'
|
||
|
import ObservableStore from 'obs-store'
|
||
|
import { createTestProviderTools } from '../../../stub/provider'
|
||
|
import { DEFAULT_ERC20_APPROVE_GAS } from '../../../../ui/app/helpers/constants/swaps'
|
||
|
import SwapsController from '../../../../app/scripts/controllers/swaps'
|
||
|
|
||
|
const MOCK_FETCH_PARAMS = {
|
||
|
slippage: 3,
|
||
|
sourceToken: '0x6b175474e89094c44da98b954eedeac495271d0f',
|
||
|
sourceDecimals: 18,
|
||
|
destinationToken: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
|
||
|
value: '1000000000000000000',
|
||
|
fromAddress: '0x7F18BB4Dd92CF2404C54CBa1A9BE4A1153bdb078',
|
||
|
exchangeList: 'zeroExV1',
|
||
|
}
|
||
|
|
||
|
const TEST_AGG_ID = 'zeroExV1'
|
||
|
const MOCK_QUOTES = {
|
||
|
[TEST_AGG_ID]: {
|
||
|
trade: {
|
||
|
data: '0x00',
|
||
|
from: '0x7F18BB4Dd92CF2404C54CBa1A9BE4A1153bdb078',
|
||
|
value: '0x17647444f166000',
|
||
|
gas: '0xe09c0',
|
||
|
gasPrice: undefined,
|
||
|
to: '0x016B4bf68d421147c06f1b8680602c5bf0Df91A8',
|
||
|
},
|
||
|
sourceAmount: '1000000000000000000000000000000000000',
|
||
|
destinationAmount: '396493201125465',
|
||
|
error: null,
|
||
|
sourceToken: '0x6b175474e89094c44da98b954eedeac495271d0f',
|
||
|
destinationToken: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
|
||
|
approvalNeeded: null,
|
||
|
maxGas: 920000,
|
||
|
averageGas: 312510,
|
||
|
estimatedRefund: 343090,
|
||
|
fetchTime: 559,
|
||
|
aggregator: TEST_AGG_ID,
|
||
|
aggType: 'AGG',
|
||
|
slippage: 3,
|
||
|
},
|
||
|
}
|
||
|
const MOCK_FETCH_METADATA = {
|
||
|
destinationTokenInfo: {
|
||
|
symbol: 'FOO',
|
||
|
decimals: 18,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
const MOCK_TOKEN_RATES_STORE = new ObservableStore({
|
||
|
contractExchangeRates: { '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48': 2 },
|
||
|
})
|
||
|
|
||
|
const MOCK_GET_PROVIDER_CONFIG = () => ({ type: 'FAKE_NETWORK' })
|
||
|
|
||
|
const MOCK_GET_BUFFERED_GAS_LIMIT = async () => ({
|
||
|
gasLimit: 2000000,
|
||
|
simulationFails: undefined,
|
||
|
})
|
||
|
|
||
|
const EMPTY_INIT_STATE = {
|
||
|
swapsState: {
|
||
|
quotes: {},
|
||
|
fetchParams: null,
|
||
|
tokens: null,
|
||
|
tradeTxId: null,
|
||
|
approveTxId: null,
|
||
|
maxMode: false,
|
||
|
quotesLastFetched: null,
|
||
|
customMaxGas: '',
|
||
|
customGasPrice: null,
|
||
|
selectedAggId: null,
|
||
|
customApproveTxData: '',
|
||
|
errorKey: '',
|
||
|
topAggId: null,
|
||
|
routeState: '',
|
||
|
swapsFeatureIsLive: false,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
const sandbox = sinon.createSandbox()
|
||
|
const fetchTradesInfoStub = sandbox.stub()
|
||
|
const fetchSwapsFeatureLivenessStub = sandbox.stub()
|
||
|
|
||
|
describe('SwapsController', function () {
|
||
|
let provider
|
||
|
|
||
|
const getSwapsController = () => {
|
||
|
return new SwapsController({
|
||
|
getBufferedGasLimit: MOCK_GET_BUFFERED_GAS_LIMIT,
|
||
|
provider,
|
||
|
getProviderConfig: MOCK_GET_PROVIDER_CONFIG,
|
||
|
tokenRatesStore: MOCK_TOKEN_RATES_STORE,
|
||
|
fetchTradesInfo: fetchTradesInfoStub,
|
||
|
fetchSwapsFeatureLiveness: fetchSwapsFeatureLivenessStub,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
before(function () {
|
||
|
const providerResultStub = {
|
||
|
// 1 gwei
|
||
|
eth_gasPrice: '0x0de0b6b3a7640000',
|
||
|
// by default, all accounts are external accounts (not contracts)
|
||
|
eth_getCode: '0x',
|
||
|
}
|
||
|
provider = createTestProviderTools({ scaffold: providerResultStub })
|
||
|
.provider
|
||
|
})
|
||
|
|
||
|
afterEach(function () {
|
||
|
sandbox.restore()
|
||
|
})
|
||
|
|
||
|
describe('constructor', function () {
|
||
|
it('should setup correctly', function () {
|
||
|
const swapsController = getSwapsController()
|
||
|
assert.deepStrictEqual(swapsController.store.getState(), EMPTY_INIT_STATE)
|
||
|
assert.deepStrictEqual(
|
||
|
swapsController.getBufferedGasLimit,
|
||
|
MOCK_GET_BUFFERED_GAS_LIMIT,
|
||
|
)
|
||
|
assert.strictEqual(swapsController.pollCount, 0)
|
||
|
assert.deepStrictEqual(
|
||
|
swapsController.getProviderConfig,
|
||
|
MOCK_GET_PROVIDER_CONFIG,
|
||
|
)
|
||
|
})
|
||
|
})
|
||
|
|
||
|
describe('API', function () {
|
||
|
let swapsController
|
||
|
beforeEach(function () {
|
||
|
swapsController = getSwapsController()
|
||
|
})
|
||
|
|
||
|
describe('setters', function () {
|
||
|
it('should set selected quote agg id', function () {
|
||
|
const selectedAggId = 'test'
|
||
|
swapsController.setSelectedQuoteAggId(selectedAggId)
|
||
|
assert.deepStrictEqual(
|
||
|
swapsController.store.getState().swapsState.selectedAggId,
|
||
|
selectedAggId,
|
||
|
)
|
||
|
})
|
||
|
|
||
|
it('should set swaps tokens', function () {
|
||
|
const tokens = []
|
||
|
swapsController.setSwapsTokens(tokens)
|
||
|
assert.deepStrictEqual(
|
||
|
swapsController.store.getState().swapsState.tokens,
|
||
|
tokens,
|
||
|
)
|
||
|
})
|
||
|
|
||
|
it('should set trade tx id', function () {
|
||
|
const tradeTxId = 'test'
|
||
|
swapsController.setTradeTxId(tradeTxId)
|
||
|
assert.strictEqual(
|
||
|
swapsController.store.getState().swapsState.tradeTxId,
|
||
|
tradeTxId,
|
||
|
)
|
||
|
})
|
||
|
|
||
|
it('should set max mode', function () {
|
||
|
const maxMode = true
|
||
|
swapsController.setMaxMode(maxMode)
|
||
|
assert.strictEqual(
|
||
|
swapsController.store.getState().swapsState.maxMode,
|
||
|
maxMode,
|
||
|
)
|
||
|
})
|
||
|
|
||
|
it('should set swaps tx gas price', function () {
|
||
|
const gasPrice = 1
|
||
|
swapsController.setSwapsTxGasPrice(gasPrice)
|
||
|
assert.deepStrictEqual(
|
||
|
swapsController.store.getState().swapsState.customGasPrice,
|
||
|
gasPrice,
|
||
|
)
|
||
|
})
|
||
|
|
||
|
it('should set swaps tx gas limit', function () {
|
||
|
const gasLimit = '1'
|
||
|
swapsController.setSwapsTxGasLimit(gasLimit)
|
||
|
assert.deepStrictEqual(
|
||
|
swapsController.store.getState().swapsState.customMaxGas,
|
||
|
gasLimit,
|
||
|
)
|
||
|
})
|
||
|
|
||
|
it('should set background swap route state', function () {
|
||
|
const routeState = 'test'
|
||
|
swapsController.setBackgroundSwapRouteState(routeState)
|
||
|
assert.deepStrictEqual(
|
||
|
swapsController.store.getState().swapsState.routeState,
|
||
|
routeState,
|
||
|
)
|
||
|
})
|
||
|
|
||
|
it('should set swaps error key', function () {
|
||
|
const errorKey = 'test'
|
||
|
swapsController.setSwapsErrorKey(errorKey)
|
||
|
assert.deepStrictEqual(
|
||
|
swapsController.store.getState().swapsState.errorKey,
|
||
|
errorKey,
|
||
|
)
|
||
|
})
|
||
|
|
||
|
it('should set initial gas estimate', async function () {
|
||
|
const initialAggId = TEST_AGG_ID
|
||
|
const baseGasEstimate = 10
|
||
|
const { maxGas, estimatedRefund } = MOCK_QUOTES[TEST_AGG_ID]
|
||
|
|
||
|
const { swapsState } = swapsController.store.getState()
|
||
|
// Set mock quotes in order to have data for the test agg
|
||
|
swapsController.store.updateState({
|
||
|
swapsState: { ...swapsState, quotes: MOCK_QUOTES },
|
||
|
})
|
||
|
|
||
|
await swapsController.setInitialGasEstimate(
|
||
|
initialAggId,
|
||
|
baseGasEstimate,
|
||
|
)
|
||
|
|
||
|
const {
|
||
|
gasLimit: bufferedGasLimit,
|
||
|
} = await swapsController.getBufferedGasLimit()
|
||
|
const {
|
||
|
gasEstimate,
|
||
|
gasEstimateWithRefund,
|
||
|
} = swapsController.store.getState().swapsState.quotes[initialAggId]
|
||
|
assert.strictEqual(gasEstimate, bufferedGasLimit)
|
||
|
assert.strictEqual(
|
||
|
gasEstimateWithRefund,
|
||
|
new BigNumber(maxGas, 10).minus(estimatedRefund, 10).toString(16),
|
||
|
)
|
||
|
})
|
||
|
|
||
|
it('should set custom approve tx data', function () {
|
||
|
const data = 'test'
|
||
|
swapsController.setCustomApproveTxData(data)
|
||
|
assert.deepStrictEqual(
|
||
|
swapsController.store.getState().swapsState.customApproveTxData,
|
||
|
data,
|
||
|
)
|
||
|
})
|
||
|
})
|
||
|
|
||
|
describe('fetchAndSetQuotes', function () {
|
||
|
it('returns null if fetchParams is not provided', async function () {
|
||
|
const quotes = await swapsController.fetchAndSetQuotes(undefined)
|
||
|
assert.strictEqual(quotes, null)
|
||
|
})
|
||
|
|
||
|
it('calls fetchTradesInfo with the given fetchParams and returns the correct quotes', async function () {
|
||
|
fetchTradesInfoStub.resolves(MOCK_QUOTES)
|
||
|
|
||
|
// Make it so approval is not required
|
||
|
sandbox
|
||
|
.stub(swapsController, '_getERC20Allowance')
|
||
|
.resolves(ethers.BigNumber.from(1))
|
||
|
|
||
|
const [newQuotes] = await swapsController.fetchAndSetQuotes(
|
||
|
MOCK_FETCH_PARAMS,
|
||
|
MOCK_FETCH_METADATA,
|
||
|
)
|
||
|
|
||
|
assert.deepStrictEqual(newQuotes[TEST_AGG_ID], {
|
||
|
...MOCK_QUOTES[TEST_AGG_ID],
|
||
|
sourceTokenInfo: undefined,
|
||
|
destinationTokenInfo: {
|
||
|
symbol: 'FOO',
|
||
|
decimals: 18,
|
||
|
},
|
||
|
isBestQuote: true,
|
||
|
// TODO: find a way to calculate these values dynamically
|
||
|
gasEstimate: 2000000,
|
||
|
gasEstimateWithRefund: '8cd8e',
|
||
|
})
|
||
|
|
||
|
assert.strictEqual(
|
||
|
fetchTradesInfoStub.calledOnceWithExactly(MOCK_FETCH_PARAMS),
|
||
|
true,
|
||
|
)
|
||
|
})
|
||
|
|
||
|
it('performs the allowance check', async function () {
|
||
|
fetchTradesInfoStub.resolves(MOCK_QUOTES)
|
||
|
|
||
|
// Make it so approval is not required
|
||
|
const allowanceStub = sandbox
|
||
|
.stub(swapsController, '_getERC20Allowance')
|
||
|
.resolves(ethers.BigNumber.from(1))
|
||
|
|
||
|
await swapsController.fetchAndSetQuotes(
|
||
|
MOCK_FETCH_PARAMS,
|
||
|
MOCK_FETCH_METADATA,
|
||
|
)
|
||
|
|
||
|
assert.strictEqual(
|
||
|
allowanceStub.calledOnceWithExactly(
|
||
|
MOCK_FETCH_PARAMS.sourceToken,
|
||
|
MOCK_FETCH_PARAMS.fromAddress,
|
||
|
),
|
||
|
true,
|
||
|
)
|
||
|
})
|
||
|
|
||
|
it('gets the gas limit if approval is required', async function () {
|
||
|
fetchTradesInfoStub.resolves(MOCK_QUOTES)
|
||
|
|
||
|
// Ensure approval is required
|
||
|
sandbox
|
||
|
.stub(swapsController, '_getERC20Allowance')
|
||
|
.resolves(ethers.BigNumber.from(0))
|
||
|
|
||
|
const timedoutGasReturnResult = { gasLimit: 1000000 }
|
||
|
const timedoutGasReturnStub = sandbox
|
||
|
.stub(swapsController, 'timedoutGasReturn')
|
||
|
.resolves(timedoutGasReturnResult)
|
||
|
|
||
|
await swapsController.fetchAndSetQuotes(
|
||
|
MOCK_FETCH_PARAMS,
|
||
|
MOCK_FETCH_METADATA,
|
||
|
)
|
||
|
|
||
|
// Mocked quotes approvalNeeded is null, so it will only be called with the gas
|
||
|
assert.strictEqual(
|
||
|
timedoutGasReturnStub.calledOnceWithExactly({
|
||
|
gas: DEFAULT_ERC20_APPROVE_GAS,
|
||
|
}),
|
||
|
true,
|
||
|
)
|
||
|
})
|
||
|
|
||
|
it('marks the best quote', async function () {
|
||
|
fetchTradesInfoStub.resolves(MOCK_QUOTES)
|
||
|
|
||
|
// Make it so approval is not required
|
||
|
sandbox
|
||
|
.stub(swapsController, '_getERC20Allowance')
|
||
|
.resolves(ethers.BigNumber.from(1))
|
||
|
|
||
|
const [newQuotes, topAggId] = await swapsController.fetchAndSetQuotes(
|
||
|
MOCK_FETCH_PARAMS,
|
||
|
MOCK_FETCH_METADATA,
|
||
|
)
|
||
|
|
||
|
assert.strictEqual(topAggId, TEST_AGG_ID)
|
||
|
assert.strictEqual(newQuotes[topAggId].isBestQuote, true)
|
||
|
})
|
||
|
|
||
|
it('selects the best quote', async function () {
|
||
|
const bestAggId = 'bestAggId'
|
||
|
|
||
|
// Clone the existing mock quote and increase destination amount
|
||
|
const bestQuote = {
|
||
|
...MOCK_QUOTES[TEST_AGG_ID],
|
||
|
aggregator: bestAggId,
|
||
|
destinationAmount: ethers.BigNumber.from(
|
||
|
MOCK_QUOTES[TEST_AGG_ID].destinationAmount,
|
||
|
)
|
||
|
.add(1)
|
||
|
.toString(),
|
||
|
}
|
||
|
const quotes = { ...MOCK_QUOTES, [bestAggId]: bestQuote }
|
||
|
fetchTradesInfoStub.resolves(quotes)
|
||
|
|
||
|
// Make it so approval is not required
|
||
|
sandbox
|
||
|
.stub(swapsController, '_getERC20Allowance')
|
||
|
.resolves(ethers.BigNumber.from(1))
|
||
|
|
||
|
const [newQuotes, topAggId] = await swapsController.fetchAndSetQuotes(
|
||
|
MOCK_FETCH_PARAMS,
|
||
|
MOCK_FETCH_METADATA,
|
||
|
)
|
||
|
|
||
|
assert.strictEqual(topAggId, bestAggId)
|
||
|
assert.strictEqual(newQuotes[topAggId].isBestQuote, true)
|
||
|
})
|
||
|
|
||
|
it('does not set isBestQuote if no conversion rate exists for destination token', async function () {
|
||
|
fetchTradesInfoStub.resolves(MOCK_QUOTES)
|
||
|
|
||
|
// Make it so approval is not required
|
||
|
sandbox
|
||
|
.stub(swapsController, '_getERC20Allowance')
|
||
|
.resolves(ethers.BigNumber.from(1))
|
||
|
|
||
|
swapsController.tokenRatesStore.updateState({
|
||
|
contractExchangeRates: {},
|
||
|
})
|
||
|
const [newQuotes, topAggId] = await swapsController.fetchAndSetQuotes(
|
||
|
MOCK_FETCH_PARAMS,
|
||
|
MOCK_FETCH_METADATA,
|
||
|
)
|
||
|
|
||
|
assert.strictEqual(newQuotes[topAggId].isBestQuote, undefined)
|
||
|
})
|
||
|
})
|
||
|
|
||
|
describe('resetSwapsState', function () {
|
||
|
it('resets the swaps state correctly', function () {
|
||
|
const { swapsState: old } = swapsController.store.getState()
|
||
|
swapsController.resetSwapsState()
|
||
|
const { swapsState } = swapsController.store.getState()
|
||
|
assert.deepStrictEqual(swapsState, {
|
||
|
...EMPTY_INIT_STATE.swapsState,
|
||
|
tokens: old.tokens,
|
||
|
})
|
||
|
})
|
||
|
|
||
|
it('clears polling timeout', function () {
|
||
|
swapsController.pollingTimeout = setTimeout(
|
||
|
() => assert.fail(),
|
||
|
1000000,
|
||
|
)
|
||
|
swapsController.resetSwapsState()
|
||
|
assert.strictEqual(swapsController.pollingTimeout._idleTimeout, -1)
|
||
|
})
|
||
|
})
|
||
|
|
||
|
describe('stopPollingForQuotes', function () {
|
||
|
it('clears polling timeout', function () {
|
||
|
swapsController.pollingTimeout = setTimeout(
|
||
|
() => assert.fail(),
|
||
|
1000000,
|
||
|
)
|
||
|
swapsController.stopPollingForQuotes()
|
||
|
assert.strictEqual(swapsController.pollingTimeout._idleTimeout, -1)
|
||
|
})
|
||
|
|
||
|
it('resets quotes state correctly', function () {
|
||
|
swapsController.stopPollingForQuotes()
|
||
|
const { swapsState } = swapsController.store.getState()
|
||
|
assert.deepStrictEqual(swapsState.quotes, {})
|
||
|
assert.strictEqual(swapsState.quotesLastFetched, null)
|
||
|
})
|
||
|
})
|
||
|
|
||
|
describe('resetPostFetchState', function () {
|
||
|
it('clears polling timeout', function () {
|
||
|
swapsController.pollingTimeout = setTimeout(
|
||
|
() => assert.fail(),
|
||
|
1000000,
|
||
|
)
|
||
|
swapsController.resetPostFetchState()
|
||
|
assert.strictEqual(swapsController.pollingTimeout._idleTimeout, -1)
|
||
|
})
|
||
|
|
||
|
it('updates state correctly', function () {
|
||
|
const tokens = 'test'
|
||
|
const fetchParams = 'test'
|
||
|
const swapsFeatureIsLive = false
|
||
|
swapsController.store.updateState({
|
||
|
swapsState: { tokens, fetchParams, swapsFeatureIsLive },
|
||
|
})
|
||
|
|
||
|
swapsController.resetPostFetchState()
|
||
|
|
||
|
const { swapsState } = swapsController.store.getState()
|
||
|
assert.deepStrictEqual(swapsState, {
|
||
|
...EMPTY_INIT_STATE.swapsState,
|
||
|
tokens,
|
||
|
fetchParams,
|
||
|
swapsFeatureIsLive,
|
||
|
})
|
||
|
})
|
||
|
})
|
||
|
|
||
|
describe('_setupSwapsLivenessFetching ', function () {
|
||
|
|
||
|
let clock
|
||
|
const EXPECTED_TIME = 600000
|
||
|
|
||
|
const getLivenessState = () => {
|
||
|
return swapsController.store.getState().swapsState.swapsFeatureIsLive
|
||
|
}
|
||
|
|
||
|
// We have to do this to overwrite window.navigator.onLine
|
||
|
const stubWindow = () => {
|
||
|
sandbox.replace(global, 'window', {
|
||
|
addEventListener: window.addEventListener,
|
||
|
navigator: { onLine: true },
|
||
|
dispatchEvent: window.dispatchEvent,
|
||
|
Event: window.Event,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
beforeEach(function () {
|
||
|
stubWindow()
|
||
|
clock = sandbox.useFakeTimers()
|
||
|
sandbox.spy(clock, 'setInterval')
|
||
|
|
||
|
sandbox.stub(
|
||
|
SwapsController.prototype,
|
||
|
'_fetchAndSetSwapsLiveness',
|
||
|
).resolves(undefined)
|
||
|
|
||
|
sandbox.spy(
|
||
|
SwapsController.prototype,
|
||
|
'_setupSwapsLivenessFetching',
|
||
|
)
|
||
|
|
||
|
sandbox.spy(window, 'addEventListener')
|
||
|
})
|
||
|
|
||
|
afterEach(function () {
|
||
|
sandbox.restore()
|
||
|
})
|
||
|
|
||
|
it('calls _setupSwapsLivenessFetching in constructor', function () {
|
||
|
swapsController = getSwapsController()
|
||
|
|
||
|
assert.ok(
|
||
|
swapsController._setupSwapsLivenessFetching.calledOnce,
|
||
|
'should have called _setupSwapsLivenessFetching once',
|
||
|
)
|
||
|
assert.ok(
|
||
|
window.addEventListener.calledWith('online'),
|
||
|
)
|
||
|
assert.ok(
|
||
|
window.addEventListener.calledWith('offline'),
|
||
|
)
|
||
|
assert.ok(
|
||
|
clock.setInterval.calledOnceWithExactly(
|
||
|
sinon.match.func,
|
||
|
EXPECTED_TIME,
|
||
|
),
|
||
|
'should have set an interval',
|
||
|
)
|
||
|
})
|
||
|
|
||
|
it('handles browser being offline on boot, then coming online', async function () {
|
||
|
window.navigator.onLine = false
|
||
|
|
||
|
swapsController = getSwapsController()
|
||
|
assert.ok(
|
||
|
swapsController._setupSwapsLivenessFetching.calledOnce,
|
||
|
'should have called _setupSwapsLivenessFetching once',
|
||
|
)
|
||
|
assert.ok(
|
||
|
swapsController._fetchAndSetSwapsLiveness.notCalled,
|
||
|
'should not have called _fetchAndSetSwapsLiveness',
|
||
|
)
|
||
|
assert.ok(
|
||
|
clock.setInterval.notCalled,
|
||
|
'should not have set an interval',
|
||
|
)
|
||
|
assert.strictEqual(
|
||
|
getLivenessState(), false,
|
||
|
'swaps feature should be disabled',
|
||
|
)
|
||
|
|
||
|
const fetchPromise = new Promise((resolve) => {
|
||
|
const originalFunction = swapsController._fetchAndSetSwapsLiveness
|
||
|
swapsController._fetchAndSetSwapsLiveness = () => {
|
||
|
originalFunction()
|
||
|
resolve()
|
||
|
swapsController._fetchAndSetSwapsLiveness = originalFunction
|
||
|
}
|
||
|
})
|
||
|
|
||
|
// browser comes online
|
||
|
window.navigator.onLine = true
|
||
|
window.dispatchEvent(new window.Event('online'))
|
||
|
await fetchPromise
|
||
|
|
||
|
assert.ok(
|
||
|
swapsController._fetchAndSetSwapsLiveness.calledOnce,
|
||
|
'should have called _fetchAndSetSwapsLiveness once',
|
||
|
)
|
||
|
assert.ok(
|
||
|
clock.setInterval.calledOnceWithExactly(
|
||
|
sinon.match.func,
|
||
|
EXPECTED_TIME,
|
||
|
),
|
||
|
'should have set an interval',
|
||
|
)
|
||
|
})
|
||
|
|
||
|
it('clears interval if browser goes offline', async function () {
|
||
|
swapsController = getSwapsController()
|
||
|
|
||
|
// set feature to live
|
||
|
const { swapsState } = swapsController.store.getState()
|
||
|
swapsController.store.updateState({
|
||
|
swapsState: { ...swapsState, swapsFeatureIsLive: true },
|
||
|
})
|
||
|
|
||
|
sandbox.spy(swapsController.store, 'updateState')
|
||
|
|
||
|
assert.ok(
|
||
|
clock.setInterval.calledOnceWithExactly(
|
||
|
sinon.match.func,
|
||
|
EXPECTED_TIME,
|
||
|
),
|
||
|
'should have set an interval',
|
||
|
)
|
||
|
|
||
|
const clearIntervalPromise = new Promise((resolve) => {
|
||
|
const originalFunction = clock.clearInterval
|
||
|
clock.clearInterval = (intervalId) => {
|
||
|
originalFunction(intervalId)
|
||
|
clock.clearInterval = originalFunction
|
||
|
resolve()
|
||
|
}
|
||
|
})
|
||
|
|
||
|
// browser goes offline
|
||
|
window.navigator.onLine = false
|
||
|
window.dispatchEvent(new window.Event('offline'))
|
||
|
|
||
|
// if this resolves, clearInterval was called
|
||
|
await clearIntervalPromise
|
||
|
|
||
|
assert.ok(
|
||
|
swapsController._fetchAndSetSwapsLiveness.calledOnce,
|
||
|
'should have called _fetchAndSetSwapsLiveness once',
|
||
|
)
|
||
|
assert.ok(
|
||
|
swapsController.store.updateState.calledOnce,
|
||
|
'should have called updateState once',
|
||
|
)
|
||
|
assert.strictEqual(
|
||
|
getLivenessState(), false,
|
||
|
'swaps feature should be disabled',
|
||
|
)
|
||
|
})
|
||
|
})
|
||
|
|
||
|
describe('_fetchAndSetSwapsLiveness', function () {
|
||
|
|
||
|
const getLivenessState = () => {
|
||
|
return swapsController.store.getState().swapsState.swapsFeatureIsLive
|
||
|
}
|
||
|
|
||
|
beforeEach(function () {
|
||
|
fetchSwapsFeatureLivenessStub.reset()
|
||
|
sandbox.stub(
|
||
|
SwapsController.prototype,
|
||
|
'_setupSwapsLivenessFetching',
|
||
|
)
|
||
|
swapsController = getSwapsController()
|
||
|
})
|
||
|
|
||
|
afterEach(function () {
|
||
|
sandbox.restore()
|
||
|
})
|
||
|
|
||
|
it('fetches feature liveness as expected when API is live', async function () {
|
||
|
fetchSwapsFeatureLivenessStub.resolves(true)
|
||
|
|
||
|
assert.strictEqual(
|
||
|
getLivenessState(), false, 'liveness should be false on boot',
|
||
|
)
|
||
|
|
||
|
await swapsController._fetchAndSetSwapsLiveness()
|
||
|
|
||
|
assert.ok(
|
||
|
fetchSwapsFeatureLivenessStub.calledOnce,
|
||
|
'should have called fetch function once',
|
||
|
)
|
||
|
assert.strictEqual(
|
||
|
getLivenessState(), true, 'liveness should be true after call',
|
||
|
)
|
||
|
})
|
||
|
|
||
|
it('does not update state if fetched value is same as state value', async function () {
|
||
|
fetchSwapsFeatureLivenessStub.resolves(false)
|
||
|
sandbox.spy(swapsController.store, 'updateState')
|
||
|
|
||
|
assert.strictEqual(
|
||
|
getLivenessState(), false, 'liveness should be false on boot',
|
||
|
)
|
||
|
|
||
|
await swapsController._fetchAndSetSwapsLiveness()
|
||
|
|
||
|
assert.ok(
|
||
|
fetchSwapsFeatureLivenessStub.calledOnce,
|
||
|
'should have called fetch function once',
|
||
|
)
|
||
|
assert.ok(
|
||
|
swapsController.store.updateState.notCalled,
|
||
|
'should not have called store.updateState',
|
||
|
)
|
||
|
assert.strictEqual(
|
||
|
getLivenessState(), false, 'liveness should remain false after call',
|
||
|
)
|
||
|
})
|
||
|
|
||
|
it('tries three times before giving up if fetching fails', async function () {
|
||
|
const clock = sandbox.useFakeTimers()
|
||
|
fetchSwapsFeatureLivenessStub.rejects(new Error('foo'))
|
||
|
sandbox.spy(swapsController.store, 'updateState')
|
||
|
|
||
|
assert.strictEqual(
|
||
|
getLivenessState(), false, 'liveness should be false on boot',
|
||
|
)
|
||
|
|
||
|
swapsController._fetchAndSetSwapsLiveness()
|
||
|
await clock.runAllAsync()
|
||
|
|
||
|
assert.ok(
|
||
|
fetchSwapsFeatureLivenessStub.calledThrice,
|
||
|
'should have called fetch function three times',
|
||
|
)
|
||
|
assert.ok(
|
||
|
swapsController.store.updateState.notCalled,
|
||
|
'should not have called store.updateState',
|
||
|
)
|
||
|
assert.strictEqual(
|
||
|
getLivenessState(), false, 'liveness should remain false after call',
|
||
|
)
|
||
|
})
|
||
|
|
||
|
it('sets state after fetching on successful retry', async function () {
|
||
|
const clock = sandbox.useFakeTimers()
|
||
|
fetchSwapsFeatureLivenessStub.onCall(0).rejects(new Error('foo'))
|
||
|
fetchSwapsFeatureLivenessStub.onCall(1).rejects(new Error('foo'))
|
||
|
fetchSwapsFeatureLivenessStub.onCall(2).resolves(true)
|
||
|
|
||
|
assert.strictEqual(
|
||
|
getLivenessState(), false, 'liveness should be false on boot',
|
||
|
)
|
||
|
|
||
|
swapsController._fetchAndSetSwapsLiveness()
|
||
|
await clock.runAllAsync()
|
||
|
|
||
|
assert.strictEqual(
|
||
|
fetchSwapsFeatureLivenessStub.callCount, 3,
|
||
|
'should have called fetch function three times',
|
||
|
)
|
||
|
assert.strictEqual(
|
||
|
getLivenessState(), true, 'liveness should be true after call',
|
||
|
)
|
||
|
})
|
||
|
})
|
||
|
})
|
||
|
})
|