1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Add tests for custom JSON-RPC network client (#16337)

Previously we had written tests for `createInfuraClient`, which creates a middleware stack designed to connect to an Infura provider. These tests exercise various RPC methods that relate to the behavior that the middleware provides (mainly around caching). 

Now we need to write the same tests but for `createJsonRpcClient`, which creates a middleware stack designed to connect to a non-Infura RPC endpoint. To do this, we had to:

- Consolidate the tests for both types of RPC client into a single test file.
- Add conditions around tests or assertions in tests to account for differences in behavior between the two sets of middleware stacks.
- Relocate code in `createJsonRpcClient` which slows down `eth_estimateGas` calls just for tests so that this behavior can be disabled in the network client tests.

Eventually, as we unify the network controllers in this repo and in the core repo, we will move these tests into the core repo.

Co-authored-by: Elliot Winkler <elliot.winkler@gmail.com>
This commit is contained in:
Zachary Belford 2023-01-06 09:10:17 -08:00 committed by GitHub
parent 6ace8955d3
commit cd2249f193
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 2104 additions and 782 deletions

View File

@ -1,389 +1,5 @@
/** import { testsForProviderType } from './provider-api-tests/shared-tests';
* @jest-environment node
*/
import {
withMockedInfuraCommunications,
withInfuraClient,
} from './provider-api-tests/helpers';
import {
testsForRpcMethodNotHandledByMiddleware,
testsForRpcMethodAssumingNoBlockParam,
testsForRpcMethodsThatCheckForBlockHashInResponse,
testsForRpcMethodSupportingBlockParam,
} from './provider-api-tests/shared-tests';
describe('createInfuraClient', () => { describe('createInfuraClient', () => {
// Infura documentation: <https://docs.infura.io/infura/networks/ethereum/json-rpc-methods> testsForProviderType('infura');
// Ethereum JSON-RPC spec: <https://ethereum.github.io/execution-apis/api-documentation/>
describe('RPC methods supported by Infura and listed in the JSON-RPC spec', () => {
describe('eth_accounts', () => {
testsForRpcMethodNotHandledByMiddleware('eth_accounts', {
numberOfParameters: 0,
});
});
describe('eth_blockNumber', () => {
testsForRpcMethodAssumingNoBlockParam('eth_blockNumber');
});
describe('eth_call', () => {
testsForRpcMethodSupportingBlockParam('eth_call', {
blockParamIndex: 1,
});
});
describe('eth_chainId', () => {
it('does not hit Infura, instead returning the chain id that maps to the Infura network, as a hex string', async () => {
const chainId = await withInfuraClient(
{ network: 'goerli' },
({ makeRpcCall }) => {
return makeRpcCall({
method: 'eth_chainId',
});
},
);
expect(chainId).toStrictEqual('0x5');
});
});
describe('eth_coinbase', () => {
testsForRpcMethodNotHandledByMiddleware('eth_coinbase', {
numberOfParameters: 0,
});
});
describe('eth_estimateGas', () => {
testsForRpcMethodAssumingNoBlockParam('eth_estimateGas');
});
describe('eth_feeHistory', () => {
testsForRpcMethodNotHandledByMiddleware('eth_feeHistory', {
numberOfParameters: 3,
});
});
describe('eth_getBalance', () => {
testsForRpcMethodSupportingBlockParam('eth_getBalance', {
blockParamIndex: 1,
});
});
describe('eth_gasPrice', () => {
testsForRpcMethodAssumingNoBlockParam('eth_gasPrice');
});
describe('eth_getBlockByHash', () => {
testsForRpcMethodAssumingNoBlockParam('eth_getBlockByHash');
});
describe('eth_getBlockByNumber', () => {
testsForRpcMethodSupportingBlockParam('eth_getBlockByNumber', {
blockParamIndex: 0,
});
});
describe('eth_getBlockTransactionCountByHash', () => {
testsForRpcMethodAssumingNoBlockParam(
'eth_getBlockTransactionCountByHash',
);
});
describe('eth_getBlockTransactionCountByNumber', () => {
// NOTE: eth_getBlockTransactionCountByNumber does take a block param at
// the 0th index, but this is not handled by our cache middleware
// currently
testsForRpcMethodAssumingNoBlockParam(
'eth_getBlockTransactionCountByNumber',
);
});
describe('eth_getCode', () => {
testsForRpcMethodSupportingBlockParam('eth_getCode', {
blockParamIndex: 1,
});
});
describe('eth_getFilterChanges', () => {
testsForRpcMethodNotHandledByMiddleware('eth_getFilterChanges', {
numberOfParameters: 1,
});
});
describe('eth_getFilterLogs', () => {
testsForRpcMethodAssumingNoBlockParam('eth_getFilterLogs');
});
describe('eth_getLogs', () => {
testsForRpcMethodNotHandledByMiddleware('eth_getLogs', {
numberOfParameters: 1,
});
});
describe('eth_getStorageAt', () => {
testsForRpcMethodSupportingBlockParam('eth_getStorageAt', {
blockParamIndex: 2,
});
});
describe('eth_getTransactionByBlockHashAndIndex', () => {
testsForRpcMethodAssumingNoBlockParam(
'eth_getTransactionByBlockHashAndIndex',
);
});
describe('eth_getTransactionByBlockNumberAndIndex', () => {
// NOTE: eth_getTransactionByBlockNumberAndIndex does take a block param
// at the 0th index, but this is not handled by our cache middleware
// currently
testsForRpcMethodAssumingNoBlockParam(
'eth_getTransactionByBlockNumberAndIndex',
);
});
describe('eth_getTransactionByHash', () => {
const method = 'eth_getTransactionByHash';
testsForRpcMethodsThatCheckForBlockHashInResponse(method);
it("refreshes the block tracker's current block if it is less than the block number that comes back in the response", async () => {
await withMockedInfuraCommunications(async (comms) => {
const request = { method };
// The first time a block-cacheable request is made, the latest
// block number is retrieved through the block tracker first.
comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' });
// This is our request.
comms.mockInfuraRpcCall({
request,
response: {
result: {
blockNumber: '0x200',
},
},
});
// The block-tracker-inspector middleware will request the latest
// block through the block tracker again.
comms.mockNextBlockTrackerRequest({ blockNumber: '0x300' });
await withInfuraClient(async ({ makeRpcCall, blockTracker }) => {
await makeRpcCall(request);
expect(blockTracker.getCurrentBlock()).toStrictEqual('0x300');
});
});
});
});
describe('eth_getTransactionCount', () => {
testsForRpcMethodSupportingBlockParam('eth_getTransactionCount', {
blockParamIndex: 1,
});
});
describe('eth_getTransactionReceipt', () => {
const method = 'eth_getTransactionReceipt';
testsForRpcMethodsThatCheckForBlockHashInResponse(method);
it("refreshes the block tracker's current block if it is less than the block number that comes back in the response", async () => {
await withMockedInfuraCommunications(async (comms) => {
const request = { method };
// The first time a block-cacheable request is made, the latest
// block number is retrieved through the block tracker first.
comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' });
// This is our request.
comms.mockInfuraRpcCall({
request,
response: {
result: {
blockNumber: '0x200',
},
},
});
// The block-tracker-inspector middleware will request the latest
// block through the block tracker again.
comms.mockNextBlockTrackerRequest({ blockNumber: '0x300' });
await withInfuraClient(async ({ makeRpcCall, blockTracker }) => {
await makeRpcCall(request);
expect(blockTracker.getCurrentBlock()).toStrictEqual('0x300');
});
});
});
});
describe('eth_getUncleByBlockHashAndIndex', () => {
testsForRpcMethodAssumingNoBlockParam('eth_getUncleByBlockHashAndIndex');
});
describe('eth_getUncleByBlockNumberAndIndex', () => {
// NOTE: eth_getUncleByBlockNumberAndIndex does take a block param at the
// 0th index, but this is not handled by our cache middleware currently
testsForRpcMethodAssumingNoBlockParam(
'eth_getUncleByBlockNumberAndIndex',
);
});
describe('eth_getUncleCountByBlockHash', () => {
testsForRpcMethodAssumingNoBlockParam('eth_getUncleCountByBlockHash');
});
describe('eth_getUncleCountByBlockNumber', () => {
// NOTE: eth_getUncleCountByBlockNumber does take a block param at the 0th
// index, but this is not handled by our cache middleware currently
testsForRpcMethodAssumingNoBlockParam('eth_getUncleCountByBlockNumber');
});
describe('eth_getWork', () => {
testsForRpcMethodNotHandledByMiddleware('eth_getWork', {
numberOfParameters: 0,
});
});
describe('eth_hashrate', () => {
testsForRpcMethodNotHandledByMiddleware('eth_hashrate', {
numberOfParameters: 0,
});
});
describe('eth_mining', () => {
testsForRpcMethodNotHandledByMiddleware('eth_mining', {
numberOfParameters: 0,
});
});
describe('eth_newBlockFilter', () => {
testsForRpcMethodNotHandledByMiddleware('eth_newBlockFilter', {
numberOfParameters: 0,
});
});
describe('eth_newFilter', () => {
testsForRpcMethodNotHandledByMiddleware('eth_newFilter', {
numberOfParameters: 1,
});
});
describe('eth_newPendingTransactionFilter', () => {
testsForRpcMethodNotHandledByMiddleware(
'eth_newPendingTransactionFilter',
{ numberOfParameters: 0 },
);
});
describe('eth_protocolVersion', () => {
testsForRpcMethodAssumingNoBlockParam('eth_protocolVersion');
});
describe('eth_sendRawTransaction', () => {
testsForRpcMethodNotHandledByMiddleware('eth_sendRawTransaction', {
numberOfParameters: 1,
});
});
describe('eth_sendTransaction', () => {
testsForRpcMethodNotHandledByMiddleware('eth_sendTransaction', {
numberOfParameters: 1,
});
});
describe('eth_sign', () => {
testsForRpcMethodNotHandledByMiddleware('eth_sign', {
numberOfParameters: 2,
});
});
describe('eth_submitWork', () => {
testsForRpcMethodNotHandledByMiddleware('eth_submitWork', {
numberOfParameters: 3,
});
});
describe('eth_syncing', () => {
testsForRpcMethodNotHandledByMiddleware('eth_syncing', {
numberOfParameters: 0,
});
});
describe('eth_uninstallFilter', () => {
testsForRpcMethodNotHandledByMiddleware('eth_uninstallFilter', {
numberOfParameters: 1,
});
});
});
describe('RPC methods supported by Infura but not listed in the JSON-RPC spec', () => {
describe('eth_subscribe', () => {
testsForRpcMethodNotHandledByMiddleware('eth_subscribe', {
numberOfParameters: 1,
});
});
describe('eth_unsubscribe', () => {
testsForRpcMethodNotHandledByMiddleware('eth_unsubscribe', {
numberOfParameters: 1,
});
});
describe('net_listening', () => {
testsForRpcMethodNotHandledByMiddleware('net_listening', {
numberOfParameters: 0,
});
});
describe('net_peerCount', () => {
testsForRpcMethodNotHandledByMiddleware('net_peerCount', {
numberOfParameters: 0,
});
});
describe('net_version', () => {
it('does not hit Infura, instead returning the chain id that maps to the Infura network, as a decimal string', async () => {
const chainId = await withInfuraClient(
{ network: 'goerli' },
({ makeRpcCall }) => {
return makeRpcCall({
method: 'net_version',
});
},
);
expect(chainId).toStrictEqual('5');
});
});
describe('parity_nextNonce', () => {
testsForRpcMethodNotHandledByMiddleware('parity_nextNonce', {
numberOfParameters: 1,
});
});
describe('web3_clientVersion', () => {
testsForRpcMethodAssumingNoBlockParam('web3_clientVersion');
});
});
// NOTE: The following methods are omitted because although they are listed in
// the Ethereum spec, they do not seem to be supported by Infura:
//
// - debug_getBadBlocks
// - debug_getRawBlock
// - debug_getRawHeader
// - debug_getRawReceipts
// - eth_createAccessList
// - eth_compileLLL
// - eth_compileSerpent
// - eth_compileSolidity
// - eth_getCompilers
// - eth_getProof
// - eth_maxPriorityFeePerGas
// - eth_submitHashrate
// - web3_sha3
testsForRpcMethodNotHandledByMiddleware('custom_rpc_method', {
numberOfParameters: 1,
});
}); });

View File

@ -10,22 +10,22 @@ import {
import { PollingBlockTracker } from 'eth-block-tracker'; import { PollingBlockTracker } from 'eth-block-tracker';
import { SECOND } from '../../../../shared/constants/time'; import { SECOND } from '../../../../shared/constants/time';
const inTest = process.env.IN_TEST;
const blockTrackerOpts = inTest ? { pollingInterval: SECOND } : {};
const getTestMiddlewares = () => {
return inTest ? [createEstimateGasDelayTestMiddleware()] : [];
};
export default function createJsonRpcClient({ rpcUrl, chainId }) { export default function createJsonRpcClient({ rpcUrl, chainId }) {
const blockTrackerOpts = process.env.IN_TEST
? { pollingInterval: SECOND }
: {};
const fetchMiddleware = createFetchMiddleware({ rpcUrl }); const fetchMiddleware = createFetchMiddleware({ rpcUrl });
const blockProvider = providerFromMiddleware(fetchMiddleware); const blockProvider = providerFromMiddleware(fetchMiddleware);
const blockTracker = new PollingBlockTracker({ const blockTracker = new PollingBlockTracker({
...blockTrackerOpts, ...blockTrackerOpts,
provider: blockProvider, provider: blockProvider,
}); });
const testMiddlewares = process.env.IN_TEST
? [createEstimateGasDelayTestMiddleware()]
: [];
const networkMiddleware = mergeMiddleware([ const networkMiddleware = mergeMiddleware([
...getTestMiddlewares(), ...testMiddlewares,
createChainIdMiddleware(chainId), createChainIdMiddleware(chainId),
createBlockRefRewriteMiddleware({ blockTracker }), createBlockRefRewriteMiddleware({ blockTracker }),
createBlockCacheMiddleware({ blockTracker }), createBlockCacheMiddleware({ blockTracker }),

View File

@ -0,0 +1,5 @@
import { testsForProviderType } from './provider-api-tests/shared-tests';
describe('createJsonRpcClient', () => {
testsForProviderType('custom');
});

View File

@ -4,37 +4,32 @@ import { JsonRpcEngine } from 'json-rpc-engine';
import { providerFromEngine } from 'eth-json-rpc-middleware'; import { providerFromEngine } from 'eth-json-rpc-middleware';
import EthQuery from 'eth-query'; import EthQuery from 'eth-query';
import createInfuraClient from '../createInfuraClient'; import createInfuraClient from '../createInfuraClient';
import createJsonRpcClient from '../createJsonRpcClient';
/** /**
* @typedef {import('nock').Scope} NockScope * @typedef {import('nock').Scope} NockScope
* *
* A object returned by `nock(...)` for mocking requests to a particular base * A object returned by the `nock` function for mocking requests to a particular
* URL. * base URL.
*/ */
/** /**
* @typedef {{makeRpcCall: (request: Partial<JsonRpcRequest>) => Promise<any>, makeRpcCallsInSeries: (requests: Partial<JsonRpcRequest>[]) => Promise<any>}} InfuraClient * @typedef {{blockTracker: import('eth-block-tracker').PollingBlockTracker, clock: sinon.SinonFakeTimers, makeRpcCall: (request: Partial<JsonRpcRequest>) => Promise<any>, makeRpcCallsInSeries: (requests: Partial<JsonRpcRequest>[]) => Promise<any>}} Client
* *
* Provides methods to interact with the suite of middleware that * Provides methods to interact with the suite of middleware that
* `createInfuraClient` exposes. * `createInfuraClient` or `createJsonRpcClient` exposes.
*/ */
/** /**
* @typedef {{network: string}} WithInfuraClientOptions * @typedef {{providerType: "infura" | "custom", infuraNetwork?: string, customRpcUrl?: string, customChainId?: string}} WithClientOptions
* *
* The options bag that `withInfuraClient` takes. * The options bag that `withNetworkClient` takes.
*/ */
/** /**
* @typedef {(client: InfuraClient) => Promise<any>} WithInfuraClientCallback * @typedef {(client: Client) => Promise<any>} WithClientCallback
* *
* The callback that `withInfuraClient` takes. * The callback that `withNetworkClient` takes.
*/
/**
* @typedef {[WithInfuraClientOptions, WithInfuraClientCallback] | [WithInfuraClientCallback]} WithInfuraClientArgs
*
* The arguments to `withInfuraClient`.
*/ */
/** /**
@ -44,36 +39,47 @@ import createInfuraClient from '../createInfuraClient';
*/ */
/** /**
* @typedef {{ nockScope: NockScope, request: object, response: object, delay?: number }} MockInfuraRpcCallOptions * @typedef {{ nockScope: NockScope, request: object, response: object, delay?: number }} MockRpcCallOptions
* *
* The options to `mockInfuraRpcCall`. * The options to `mockRpcCall`.
*/ */
/** /**
* @typedef {{mockNextBlockTrackerRequest: (options: Omit<MockBlockTrackerRequestOptions, 'nockScope'>) => void, mockAllBlockTrackerRequests: (options: Omit<MockBlockTrackerRequestOptions, 'nockScope'>) => void, mockInfuraRpcCall: (options: Omit<MockInfuraRpcCallOptions, 'nockScope'>) => NockScope}} InfuraCommunications * @typedef {{mockNextBlockTrackerRequest: (options: Omit<MockBlockTrackerRequestOptions, 'nockScope'>) => void, mockAllBlockTrackerRequests: (options: Omit<MockBlockTrackerRequestOptions, 'nockScope'>) => void, mockRpcCall: (options: Omit<MockRpcCallOptions, 'nockScope'>) => NockScope, rpcUrl: string, infuraNetwork: string}} Communications
* *
* Provides methods to mock different kinds of requests to Infura. * Provides methods to mock different kinds of requests to the provider.
*/ */
/** /**
* @typedef {{network: string}} WithMockedInfuraCommunicationsOptions * @typedef {{providerType: 'infura' | 'custom', infuraNetwork?: string}} WithMockedCommunicationsOptions
* *
* The options bag that `mockingInfuraCommunications` takes. * The options bag that `Communications` takes.
*/ */
/** /**
* @typedef {(comms: InfuraCommunications) => Promise<any>} WithMockedInfuraCommunicationsCallback * @typedef {(comms: Communications) => Promise<any>} WithMockedCommunicationsCallback
* *
* The callback that `mockingInfuraCommunications` takes. * The callback that `mockingCommunications` takes.
*/ */
/** /**
* @typedef {[WithMockedInfuraCommunicationsOptions, WithMockedInfuraCommunicationsCallback] | [WithMockedInfuraCommunicationsCallback]} WithMockedInfuraCommunicationsArgs * A dummy value for the `infuraProjectId` option that `createInfuraClient`
* * needs. (Infura should not be hit during tests, but just in case, this should
* The arguments to `mockingInfuraCommunications`. * not refer to a real project ID.)
*/ */
const MOCK_INFURA_PROJECT_ID = 'abc123';
const INFURA_PROJECT_ID = 'abc123'; /**
* A dummy value for the `rpcUrl` option that `createJsonRpcClient` needs. (This
* should not be hit during tests, but just in case, this should also not refer
* to a real Infura URL.)
*/
const MOCK_RPC_URL = 'http://foo.com';
/**
* A default value for the `eth_blockNumber` request that the block tracker
* makes.
*/
const DEFAULT_LATEST_BLOCK_NUMBER = '0x42'; const DEFAULT_LATEST_BLOCK_NUMBER = '0x42';
/** /**
@ -90,19 +96,16 @@ function debug(...args) {
} }
/** /**
* Builds a Nock scope object for mocking requests to a particular network that * Builds a Nock scope object for mocking provider requests.
* Infura supports.
* *
* @param {object} options - The options. * @param {string} rpcUrl - The URL of the RPC endpoint.
* @param {string} options.network - The Infura network you're testing with
* (default: "mainnet").
* @returns {NockScope} The nock scope. * @returns {NockScope} The nock scope.
*/ */
function buildScopeForMockingInfuraRequests({ network = 'mainnet' } = {}) { function buildScopeForMockingRequests(rpcUrl) {
return nock(`https://${network}.infura.io`).filteringRequestBody((body) => { return nock(rpcUrl).filteringRequestBody((body) => {
const copyOfBody = JSON.parse(body); const copyOfBody = JSON.parse(body);
// some ids are random, so remove them entirely from the request to // Some IDs are random, so remove them entirely from the request to make it
// make it possible to mock these requests // possible to mock these requests
delete copyOfBody.id; delete copyOfBody.id;
return JSON.stringify(copyOfBody); return JSON.stringify(copyOfBody);
}); });
@ -121,7 +124,7 @@ async function mockNextBlockTrackerRequest({
nockScope, nockScope,
blockNumber = DEFAULT_LATEST_BLOCK_NUMBER, blockNumber = DEFAULT_LATEST_BLOCK_NUMBER,
}) { }) {
await mockInfuraRpcCall({ await mockRpcCall({
nockScope, nockScope,
request: { method: 'eth_blockNumber', params: [] }, request: { method: 'eth_blockNumber', params: [] },
response: { result: blockNumber }, response: { result: blockNumber },
@ -141,7 +144,7 @@ async function mockAllBlockTrackerRequests({
nockScope, nockScope,
blockNumber = DEFAULT_LATEST_BLOCK_NUMBER, blockNumber = DEFAULT_LATEST_BLOCK_NUMBER,
}) { }) {
await mockInfuraRpcCall({ await mockRpcCall({
nockScope, nockScope,
request: { method: 'eth_blockNumber', params: [] }, request: { method: 'eth_blockNumber', params: [] },
response: { result: blockNumber }, response: { result: blockNumber },
@ -149,9 +152,10 @@ async function mockAllBlockTrackerRequests({
} }
/** /**
* Mocks a JSON-RPC request sent to Infura with the given response. * Mocks a JSON-RPC request sent to the provider with the given response.
* Provider type is inferred from the base url set on the nockScope.
* *
* @param {MockInfuraRpcCallOptions} args - The arguments. * @param {MockRpcCallOptions} args - The arguments.
* @param {NockScope} args.nockScope - A nock scope (a set of mocked requests * @param {NockScope} args.nockScope - A nock scope (a set of mocked requests
* scoped to a certain base URL). * scoped to a certain base URL).
* @param {object} args.request - The request data. * @param {object} args.request - The request data.
@ -169,14 +173,7 @@ async function mockAllBlockTrackerRequests({
* expected to be made. * expected to be made.
* @returns {NockScope} The nock scope. * @returns {NockScope} The nock scope.
*/ */
function mockInfuraRpcCall({ function mockRpcCall({ nockScope, request, response, error, delay, times }) {
nockScope,
request,
response,
error,
delay,
times,
}) {
// eth-query always passes `params`, so even if we don't supply this property, // eth-query always passes `params`, so even if we don't supply this property,
// for consistency with makeRpcCall, assume that the `body` contains it // for consistency with makeRpcCall, assume that the `body` contains it
const { method, params = [], ...rest } = request; const { method, params = [], ...rest } = request;
@ -194,7 +191,10 @@ function mockInfuraRpcCall({
completeResponse = response.body; completeResponse = response.body;
} }
} }
let nockRequest = nockScope.post(`/v3/${INFURA_PROJECT_ID}`, { const url = nockScope.basePath.includes('infura.io')
? `/v3/${MOCK_INFURA_PROJECT_ID}`
: '/';
let nockRequest = nockScope.post(url, {
jsonrpc: '2.0', jsonrpc: '2.0',
method, method,
params, params,
@ -241,29 +241,46 @@ function makeRpcCall(ethQuery, request) {
} }
/** /**
* Sets up request mocks for requests to Infura. * Sets up request mocks for requests to the provider.
* *
* @param {WithMockedInfuraCommunicationsArgs} args - Either an options bag + a * @param {WithMockedCommunicationsOptions} options - An options bag.
* function, or just a function. The options bag, at the moment, may contain * @param {"infura" | "custom"} options.providerType - The type of network
* `network` (that is, the Infura network; defaults to "mainnet"). The function * client being tested.
* is called with an object that allows you to mock different kinds of requests. * @param {string} [options.infuraNetwork] - The name of the Infura network being
* tested, assuming that `providerType` is "infura" (default: "mainnet").
* @param {string} [options.customRpcUrl] - The URL of the custom RPC endpoint,
* assuming that `providerType` is "custom".
* @param {WithMockedCommunicationsCallback} fn - A function which will be
* called with an object that allows interaction with the network client.
* @returns {Promise<any>} The return value of the given function. * @returns {Promise<any>} The return value of the given function.
*/ */
export async function withMockedInfuraCommunications(...args) { export async function withMockedCommunications(
const [options, fn] = args.length === 2 ? args : [{}, args[0]]; { providerType, infuraNetwork = 'mainnet', customRpcUrl = MOCK_RPC_URL },
const { network = 'mainnet' } = options; fn,
) {
if (providerType !== 'infura' && providerType !== 'custom') {
throw new Error(
`providerType must be either "infura" or "custom", was "${providerType}" instead`,
);
}
const nockScope = buildScopeForMockingInfuraRequests({ network }); const rpcUrl =
providerType === 'infura'
? `https://${infuraNetwork}.infura.io`
: customRpcUrl;
const nockScope = buildScopeForMockingRequests(rpcUrl);
const curriedMockNextBlockTrackerRequest = (localOptions) => const curriedMockNextBlockTrackerRequest = (localOptions) =>
mockNextBlockTrackerRequest({ nockScope, ...localOptions }); mockNextBlockTrackerRequest({ nockScope, ...localOptions });
const curriedMockAllBlockTrackerRequests = (localOptions) => const curriedMockAllBlockTrackerRequests = (localOptions) =>
mockAllBlockTrackerRequests({ nockScope, ...localOptions }); mockAllBlockTrackerRequests({ nockScope, ...localOptions });
const curriedMockInfuraRpcCall = (localOptions) => const curriedMockRpcCall = (localOptions) =>
mockInfuraRpcCall({ nockScope, ...localOptions }); mockRpcCall({ nockScope, ...localOptions });
const comms = { const comms = {
mockNextBlockTrackerRequest: curriedMockNextBlockTrackerRequest, mockNextBlockTrackerRequest: curriedMockNextBlockTrackerRequest,
mockAllBlockTrackerRequests: curriedMockAllBlockTrackerRequests, mockAllBlockTrackerRequests: curriedMockAllBlockTrackerRequests,
mockInfuraRpcCall: curriedMockInfuraRpcCall, mockRpcCall: curriedMockRpcCall,
rpcUrl,
infuraNetwork,
}; };
try { try {
@ -275,25 +292,55 @@ export async function withMockedInfuraCommunications(...args) {
} }
/** /**
* Builds a provider from the Infura middleware along with a block tracker, runs * Builds a provider from the middleware (for the provider type) along with a
* the given function with those two things, and then ensures the block tracker * block tracker, runs the given function with those two things, and then
* is stopped at the end. * ensures the block tracker is stopped at the end.
* *
* @param {WithInfuraClientArgs} args - Either an options bag + a function, or * @param {WithClientOptions} options - An options bag.
* just a function. The options bag, at the moment, may contain `network` (that * @param {"infura" | "custom"} options.providerType - The type of network
* is, the Infura network; defaults to "mainnet"). The function is called with * client being tested.
* an object that allows you to interact with the client via a couple of methods * @param {string} [options.infuraNetwork] - The name of the Infura network being
* on that object. * tested, assuming that `providerType` is "infura" (default: "mainnet").
* @param {string} [options.customRpcUrl] - The URL of the custom RPC endpoint,
* assuming that `providerType` is "custom".
* @param {string} [options.customChainId] - The chain id belonging to the
* custom RPC endpoint, assuming that `providerType` is "custom" (default:
* "0x1").
* @param {WithClientCallback} fn - A function which will be called with an
* object that allows interaction with the network client.
* @returns {Promise<any>} The return value of the given function. * @returns {Promise<any>} The return value of the given function.
*/ */
export async function withInfuraClient(...args) { export async function withNetworkClient(
const [options, fn] = args.length === 2 ? args : [{}, args[0]]; {
const { network = 'mainnet' } = options; providerType,
infuraNetwork = 'mainnet',
customRpcUrl = MOCK_RPC_URL,
customChainId = '0x1',
},
fn,
) {
if (providerType !== 'infura' && providerType !== 'custom') {
throw new Error(
`providerType must be either "infura" or "custom", was "${providerType}" instead`,
);
}
const { networkMiddleware, blockTracker } = createInfuraClient({ // The JSON-RPC client wraps `eth_estimateGas` so that it takes 2 seconds longer
network, // than it usually would to complete. Or at least it should — this doesn't
projectId: INFURA_PROJECT_ID, // appear to be working correctly. Unset `IN_TEST` on `process.env` to prevent
}); // this behavior.
const inTest = process.env.IN_TEST;
delete process.env.IN_TEST;
const clientUnderTest =
providerType === 'infura'
? createInfuraClient({
network: infuraNetwork,
projectId: MOCK_INFURA_PROJECT_ID,
})
: createJsonRpcClient({ rpcUrl: customRpcUrl, chainId: customChainId });
process.env.IN_TEST = inTest;
const { networkMiddleware, blockTracker } = clientUnderTest;
const engine = new JsonRpcEngine(); const engine = new JsonRpcEngine();
engine.push(networkMiddleware); engine.push(networkMiddleware);