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

Convert NetworkController net client tests to TS (#18490)

In order to be able to better compare differences between the version of
NetworkController in this repo and the version in the `core` repo before
we replace this version with the `core` version, this commit converts
the NetworkController network client tests to TypeScript.

The added types here are copied from the `core` repo. We plan on
making more improvements on the `core` side at some point to polish the
tests and types and reduce some of the duplication, but for now we're
just trying to keep things as similar as possible.
This commit is contained in:
Elliot Winkler 2023-04-14 10:21:29 -06:00 committed by GitHub
parent 8fdbd07c91
commit 26db0aee46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 297 additions and 196 deletions

View File

@ -267,7 +267,7 @@ module.exports = {
'app/scripts/controllers/app-state.test.js', 'app/scripts/controllers/app-state.test.js',
'app/scripts/controllers/network/**/*.test.js', 'app/scripts/controllers/network/**/*.test.js',
'app/scripts/controllers/network/**/*.test.ts', 'app/scripts/controllers/network/**/*.test.ts',
'app/scripts/controllers/network/provider-api-tests/*.js', 'app/scripts/controllers/network/provider-api-tests/*.ts',
'app/scripts/controllers/permissions/**/*.test.js', 'app/scripts/controllers/permissions/**/*.test.js',
'app/scripts/lib/**/*.test.js', 'app/scripts/lib/**/*.test.js',
'app/scripts/migrations/*.test.js', 'app/scripts/migrations/*.test.js',

View File

@ -1,6 +1,15 @@
/* eslint-disable jest/require-top-level-describe, jest/no-export */ /* eslint-disable jest/require-top-level-describe, jest/no-export */
import { withMockedCommunications, withNetworkClient } from './helpers'; import {
ProviderType,
withMockedCommunications,
withNetworkClient,
} from './helpers';
type TestsForRpcMethodThatCheckForBlockHashInResponseOptions = {
providerType: ProviderType;
numberOfParameters: number;
};
/** /**
* Defines tests which exercise the behavior exhibited by an RPC method that * Defines tests which exercise the behavior exhibited by an RPC method that
@ -15,8 +24,11 @@ import { withMockedCommunications, withNetworkClient } from './helpers';
* either `infura` or `custom` (default: "infura"). * either `infura` or `custom` (default: "infura").
*/ */
export function testsForRpcMethodsThatCheckForBlockHashInResponse( export function testsForRpcMethodsThatCheckForBlockHashInResponse(
method, method: string,
{ numberOfParameters, providerType }, {
numberOfParameters,
providerType,
}: TestsForRpcMethodThatCheckForBlockHashInResponseOptions,
) { ) {
if (providerType !== 'infura' && providerType !== 'custom') { if (providerType !== 'infura' && providerType !== 'custom') {
throw new Error( throw new Error(

View File

@ -3,6 +3,7 @@
import { import {
buildMockParams, buildMockParams,
buildRequestWithReplacedBlockParam, buildRequestWithReplacedBlockParam,
ProviderType,
waitForPromiseToBeFulfilledAfterRunningAllTimers, waitForPromiseToBeFulfilledAfterRunningAllTimers,
withMockedCommunications, withMockedCommunications,
withNetworkClient, withNetworkClient,
@ -13,6 +14,12 @@ import {
buildJsonRpcEngineEmptyResponseErrorMessage, buildJsonRpcEngineEmptyResponseErrorMessage,
} from './shared-tests'; } from './shared-tests';
type TestsForRpcMethodSupportingBlockParam = {
providerType: ProviderType;
blockParamIndex: number;
numberOfParameters: number;
};
/** /**
* Defines tests which exercise the behavior exhibited by an RPC method that * Defines tests which exercise the behavior exhibited by an RPC method that
* takes a block parameter. The value of this parameter can be either a block * takes a block parameter. The value of this parameter can be either a block
@ -28,8 +35,12 @@ import {
*/ */
/* eslint-disable-next-line jest/no-export */ /* eslint-disable-next-line jest/no-export */
export function testsForRpcMethodSupportingBlockParam( export function testsForRpcMethodSupportingBlockParam(
method, method: string,
{ blockParamIndex, numberOfParameters, providerType }, {
blockParamIndex,
numberOfParameters,
providerType,
}: TestsForRpcMethodSupportingBlockParam,
) { ) {
describe.each([ describe.each([
['given no block tag', undefined], ['given no block tag', undefined],
@ -1718,9 +1729,9 @@ export function testsForRpcMethodSupportingBlockParam(
[ [
['less than the current block number', '0x200'], ['less than the current block number', '0x200'],
['equal to the curent block number', '0x100'], ['equal to the curent block number', '0x100'],
], ] as any,
'%s', '%s',
(_nestedDesc, currentBlockNumber) => { (_nestedDesc: string, currentBlockNumber: string) => {
it('makes an additional request to the RPC endpoint', async () => { it('makes an additional request to the RPC endpoint', async () => {
await withMockedCommunications({ providerType }, async (comms) => { await withMockedCommunications({ providerType }, async (comms) => {
const request = { const request = {

View File

@ -1,14 +1,13 @@
import nock from 'nock'; import nock, { Scope as NockScope } from 'nock';
import sinon from 'sinon'; import sinon from 'sinon';
import type { JSONRPCResponse } from '@json-rpc-specification/meta-schema';
import EthQuery from 'eth-query'; import EthQuery from 'eth-query';
import { createNetworkClient } from '../create-network-client'; import { Hex } from '@metamask/utils';
import { BuiltInInfuraNetwork } from '../../../../../shared/constants/network';
/** import {
* @typedef {import('nock').Scope} NockScope createNetworkClient,
* NetworkClientType,
* A object returned by the `nock` function for mocking requests to a particular } from '../create-network-client';
* base URL.
*/
/** /**
* A dummy value for the `infuraProjectId` option that `createInfuraClient` * A dummy value for the `infuraProjectId` option that `createInfuraClient`
@ -41,9 +40,9 @@ const originalSetTimeout = setTimeout;
* keeps failing, you can set `process.env.DEBUG_PROVIDER_TESTS` to `1`. This * keeps failing, you can set `process.env.DEBUG_PROVIDER_TESTS` to `1`. This
* will turn on some extra logging. * will turn on some extra logging.
* *
* @param {any[]} args - The arguments that `console.log` takes. * @param args - The arguments that `console.log` takes.
*/ */
function debug(...args) { function debug(...args: any) {
if (process.env.DEBUG_PROVIDER_TESTS === '1') { if (process.env.DEBUG_PROVIDER_TESTS === '1') {
console.log(...args); console.log(...args);
} }
@ -52,96 +51,89 @@ function debug(...args) {
/** /**
* Builds a Nock scope object for mocking provider requests. * Builds a Nock scope object for mocking provider requests.
* *
* @param {string} rpcUrl - The URL of the RPC endpoint. * @param rpcUrl - The URL of the RPC endpoint.
* @returns {NockScope} The nock scope. * @returns The nock scope.
*/ */
function buildScopeForMockingRequests(rpcUrl) { function buildScopeForMockingRequests(rpcUrl: string): NockScope {
return nock(rpcUrl).filteringRequestBody((body) => { return nock(rpcUrl).filteringRequestBody((body) => {
debug('Nock Received Request: ', body); debug('Nock Received Request: ', body);
return body; return body;
}); });
} }
/** type Request = { method: string; params?: any[] };
* @typedef {{ nockScope: NockScope, blockNumber: string }} MockBlockTrackerRequestOptions type Response = {
* id?: number | string;
* The options to `mockNextBlockTrackerRequest` and `mockAllBlockTrackerRequests`. jsonrpc?: '2.0';
*/ error?: any;
result?: any;
httpStatus?: number;
};
type ResponseBody = { body: JSONRPCResponse };
type BodyOrResponse = ResponseBody | Response;
type CurriedMockRpcCallOptions = {
request: Request;
// The response data.
response?: BodyOrResponse;
/**
* An error to throw while making the request.
* Takes precedence over `response`.
*/
error?: Error | string;
/**
* The amount of time that should pass before the
* request resolves with the response.
*/
delay?: number;
/**
* The number of times that the request is
* expected to be made.
*/
times?: number;
};
/** type MockRpcCallOptions = {
* Mocks the next request for the latest block that the block tracker will make. // A nock scope (a set of mocked requests scoped to a certain base URL).
* nockScope: nock.Scope;
* @param {MockBlockTrackerRequestOptions} args - The arguments. } & CurriedMockRpcCallOptions;
* @param {NockScope} args.nockScope - A nock scope (a set of mocked requests
* scoped to a certain base URL).
* @param {string} args.blockNumber - The block number that the block tracker
* should report, as a 0x-prefixed hex string.
*/
async function mockNextBlockTrackerRequest({
nockScope,
blockNumber = DEFAULT_LATEST_BLOCK_NUMBER,
}) {
await mockRpcCall({
nockScope,
request: { method: 'eth_blockNumber', params: [] },
response: { result: blockNumber },
});
}
/** type MockRpcCallResult = nock.Interceptor | nock.Scope;
* Mocks all requests for the latest block that the block tracker will make.
*
* @param {MockBlockTrackerRequestOptions} args - The arguments.
* @param {NockScope} args.nockScope - A nock scope (a set of mocked requests
* scoped to a certain base URL).
* @param {string} args.blockNumber - The block number that the block tracker
* should report, as a 0x-prefixed hex string.
*/
async function mockAllBlockTrackerRequests({
nockScope,
blockNumber = DEFAULT_LATEST_BLOCK_NUMBER,
}) {
await mockRpcCall({
nockScope,
request: { method: 'eth_blockNumber', params: [] },
response: { result: blockNumber },
}).persist();
}
/**
* @typedef {{ nockScope: NockScope, request: object, response: object, delay?: number }} MockRpcCallOptions
*
* The options to `mockRpcCall`.
*/
/** /**
* Mocks a JSON-RPC request sent to the provider 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. * Provider type is inferred from the base url set on the nockScope.
* *
* @param {MockRpcCallOptions} args - The arguments. * @param args - The arguments.
* @param {NockScope} args.nockScope - A nock scope (a set of mocked requests * @param args.nockScope - A nock scope (a set of mocked requests scoped to a
* scoped to a certain base URL). * certain base URL).
* @param {object} args.request - The request data. * @param args.request - The request data.
* @param {{body: string} | {httpStatus?: number; id?: number; method?: string; params?: string[]}} [args.response] - Information * @param args.response - Information concerning the response that the request
* concerning the response that the request should have. If a `body` property is * should have. If a `body` property is present, this is taken as the complete
* present, this is taken as the complete response body. If an `httpStatus` * response body. If an `httpStatus` property is present, then it is taken as
* property is present, then it is taken as the HTTP status code to respond * the HTTP status code to respond with. Properties other than these two are
* with. Properties other than these two are used to build a complete response * used to build a complete response body (including `id` and `jsonrpc`
* body (including `id` and `jsonrpc` properties). * properties).
* @param {Error | string} [args.error] - An error to throw while making the * @param args.error - An error to throw while making the request. Takes
* request. Takes precedence over `response`. * precedence over `response`.
* @param {number} [args.delay] - The amount of time that should pass before the * @param args.delay - The amount of time that should pass before the request
* request resolves with the response. * resolves with the response.
* @param {number} [args.times] - The number of times that the request is * @param args.times - The number of times that the request is expected to be
* expected to be made. * made.
* @returns {NockScope} The nock scope. * @returns The nock scope.
*/ */
function mockRpcCall({ nockScope, request, response, error, delay, times }) { function mockRpcCall({
nockScope,
request,
response,
error,
delay,
times,
}: MockRpcCallOptions): MockRpcCallResult {
// 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;
let httpStatus = 200; let httpStatus = 200;
let completeResponse = { id: 2, jsonrpc: '2.0' }; let completeResponse: JSONRPCResponse = { id: 2, jsonrpc: '2.0' };
if (response !== undefined) { if (response !== undefined) {
if ('body' in response) { if ('body' in response) {
completeResponse = response.body; completeResponse = response.body;
@ -156,6 +148,7 @@ function mockRpcCall({ nockScope, request, response, error, delay, times }) {
} }
} }
} }
/* @ts-expect-error The types for Nock do not include `basePath` in the interface for Nock.Scope. */
const url = nockScope.basePath.includes('infura.io') const url = nockScope.basePath.includes('infura.io')
? `/v3/${MOCK_INFURA_PROJECT_ID}` ? `/v3/${MOCK_INFURA_PROJECT_ID}`
: '/'; : '/';
@ -189,7 +182,7 @@ function mockRpcCall({ nockScope, request, response, error, delay, times }) {
if (error !== undefined) { if (error !== undefined) {
return nockRequest.replyWithError(error); return nockRequest.replyWithError(error);
} else if (completeResponse !== undefined) { } else if (completeResponse !== undefined) {
return nockRequest.reply(httpStatus, (_, requestBody) => { return nockRequest.reply(httpStatus, (_, requestBody: any) => {
if (response !== undefined && !('body' in response)) { if (response !== undefined && !('body' in response)) {
if (response.id === undefined) { if (response.id === undefined) {
completeResponse.id = requestBody.id; completeResponse.id = requestBody.id;
@ -204,16 +197,72 @@ function mockRpcCall({ nockScope, request, response, error, delay, times }) {
return nockRequest; return nockRequest;
} }
type MockBlockTrackerRequestOptions = {
/**
* A nock scope (a set of mocked requests scoped to a certain base url).
*/
nockScope: NockScope;
/**
* The block number that the block tracker should report, as a 0x-prefixed hex
* string.
*/
blockNumber: string;
};
/**
* Mocks the next request for the latest block that the block tracker will make.
*
* @param args - The arguments.
* @param args.nockScope - A nock scope (a set of mocked requests scoped to a
* certain base URL).
* @param args.blockNumber - The block number that the block tracker should
* report, as a 0x-prefixed hex string.
*/
function mockNextBlockTrackerRequest({
nockScope,
blockNumber = DEFAULT_LATEST_BLOCK_NUMBER,
}: MockBlockTrackerRequestOptions) {
mockRpcCall({
nockScope,
request: { method: 'eth_blockNumber', params: [] },
response: { result: blockNumber },
});
}
/**
* Mocks all requests for the latest block that the block tracker will make.
*
* @param args - The arguments.
* @param args.nockScope - A nock scope (a set of mocked requests scoped to a
* certain base URL).
* @param args.blockNumber - The block number that the block tracker should
* report, as a 0x-prefixed hex string.
*/
async function mockAllBlockTrackerRequests({
nockScope,
blockNumber = DEFAULT_LATEST_BLOCK_NUMBER,
}: MockBlockTrackerRequestOptions) {
const result = await mockRpcCall({
nockScope,
request: { method: 'eth_blockNumber', params: [] },
response: { result: blockNumber },
});
if ('persist' in result) {
result.persist();
}
}
/** /**
* Makes a JSON-RPC call through the given eth-query object. * Makes a JSON-RPC call through the given eth-query object.
* *
* @param {any} ethQuery - The eth-query object. * @param ethQuery - The eth-query object.
* @param {object} request - The request data. * @param request - The request data.
* @returns {Promise<any>} A promise that either resolves with the result from * @returns A promise that either resolves with the result from the JSON-RPC
* the JSON-RPC response if it is successful or rejects with the error from the * response if it is successful or rejects with the error from the JSON-RPC
* JSON-RPC response otherwise. * response otherwise.
*/ */
function makeRpcCall(ethQuery, request) { function makeRpcCall(ethQuery: EthQuery, request: Request) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
debug('[makeRpcCall] making request', request); debug('[makeRpcCall] making request', request);
ethQuery.sendAsync(request, (error, result) => { ethQuery.sendAsync(request, (error, result) => {
@ -227,41 +276,43 @@ function makeRpcCall(ethQuery, request) {
}); });
} }
/** export type ProviderType = 'infura' | 'custom';
* @typedef {{providerType: 'infura' | 'custom', infuraNetwork?: string}} WithMockedCommunicationsOptions
*
* The options bag that `Communications` takes.
*/
/** export type MockOptions = {
* @typedef {{mockNextBlockTrackerRequest: (options: Omit<MockBlockTrackerRequestOptions, 'nockScope'>) => void, mockAllBlockTrackerRequests: (options: Omit<MockBlockTrackerRequestOptions, 'nockScope'>) => void, mockRpcCall: (options: Omit<MockRpcCallOptions, 'nockScope'>) => NockScope, rpcUrl: string, infuraNetwork: string}} Communications infuraNetwork?: BuiltInInfuraNetwork;
* providerType: ProviderType;
* Provides methods to mock different kinds of requests to the provider. customRpcUrl?: string;
*/ customChainId?: Hex;
};
/** export type MockCommunications = {
* @typedef {(comms: Communications) => Promise<any>} WithMockedCommunicationsCallback mockNextBlockTrackerRequest: (options?: any) => void;
* mockAllBlockTrackerRequests: (options?: any) => void;
* The callback that `mockingCommunications` takes. mockRpcCall: (options: CurriedMockRpcCallOptions) => MockRpcCallResult;
*/ rpcUrl: string;
infuraNetwork: BuiltInInfuraNetwork;
};
/** /**
* Sets up request mocks for requests to the provider. * Sets up request mocks for requests to the provider.
* *
* @param {WithMockedCommunicationsOptions} options - An options bag. * @param options - An options bag.
* @param {"infura" | "custom"} options.providerType - The type of network * @param options.providerType - The type of network client being tested.
* client being tested. * @param options.infuraNetwork - The name of the Infura network being tested,
* @param {string} [options.infuraNetwork] - The name of the Infura network being * assuming that `providerType` is "infura" (default: "mainnet").
* tested, assuming that `providerType` is "infura" (default: "mainnet"). * @param options.customRpcUrl - The URL of the custom RPC endpoint, assuming
* @param {string} [options.customRpcUrl] - The URL of the custom RPC endpoint, * that `providerType` is "custom".
* assuming that `providerType` is "custom". * @param fn - A function which will be called with an object that allows
* @param {WithMockedCommunicationsCallback} fn - A function which will be * interaction with the network client.
* called with an object that allows interaction with the network client. * @returns The return value of the given function.
* @returns {Promise<any>} The return value of the given function.
*/ */
export async function withMockedCommunications( export async function withMockedCommunications(
{ providerType, infuraNetwork = 'mainnet', customRpcUrl = MOCK_RPC_URL }, {
fn, providerType,
infuraNetwork = 'mainnet',
customRpcUrl = MOCK_RPC_URL,
}: MockOptions,
fn: (comms: MockCommunications) => Promise<void>,
) { ) {
if (providerType !== 'infura' && providerType !== 'custom') { if (providerType !== 'infura' && providerType !== 'custom') {
throw new Error( throw new Error(
@ -274,11 +325,11 @@ export async function withMockedCommunications(
? `https://${infuraNetwork}.infura.io` ? `https://${infuraNetwork}.infura.io`
: customRpcUrl; : customRpcUrl;
const nockScope = buildScopeForMockingRequests(rpcUrl); const nockScope = buildScopeForMockingRequests(rpcUrl);
const curriedMockNextBlockTrackerRequest = (localOptions) => const curriedMockNextBlockTrackerRequest = (localOptions: any) =>
mockNextBlockTrackerRequest({ nockScope, ...localOptions }); mockNextBlockTrackerRequest({ nockScope, ...localOptions });
const curriedMockAllBlockTrackerRequests = (localOptions) => const curriedMockAllBlockTrackerRequests = (localOptions: any) =>
mockAllBlockTrackerRequests({ nockScope, ...localOptions }); mockAllBlockTrackerRequests({ nockScope, ...localOptions });
const curriedMockRpcCall = (localOptions) => const curriedMockRpcCall = (localOptions: any) =>
mockRpcCall({ nockScope, ...localOptions }); mockRpcCall({ nockScope, ...localOptions });
const comms = { const comms = {
@ -297,12 +348,12 @@ export async function withMockedCommunications(
} }
} }
/** type MockNetworkClient = {
* @typedef {{blockTracker: import('eth-block-tracker').PollingBlockTracker, clock: sinon.SinonFakeTimers, makeRpcCall: (request: Partial<JsonRpcRequest>) => Promise<any>, makeRpcCallsInSeries: (requests: Partial<JsonRpcRequest>[]) => Promise<any>}} MockNetworkClient blockTracker: any;
* clock: sinon.SinonFakeTimers;
* Provides methods to interact with the suite of middleware that makeRpcCall: (request: Request) => Promise<any>;
* `createInfuraClient` or `createJsonRpcClient` exposes. makeRpcCallsInSeries: (requests: Request[]) => Promise<any[]>;
*/ };
/** /**
* Some middleware contain logic which retries the request if some condition * Some middleware contain logic which retries the request if some condition
@ -321,14 +372,14 @@ export async function withMockedCommunications(
* `setTimeout` handler. * `setTimeout` handler.
*/ */
export async function waitForPromiseToBeFulfilledAfterRunningAllTimers( export async function waitForPromiseToBeFulfilledAfterRunningAllTimers(
promise, promise: any,
clock, clock: any,
) { ) {
let hasPromiseBeenFulfilled = false; let hasPromiseBeenFulfilled = false;
let numTimesClockHasBeenAdvanced = 0; let numTimesClockHasBeenAdvanced = 0;
promise promise
.catch((error) => { .catch((error: any) => {
// This is used to silence Node.js warnings about the rejection // This is used to silence Node.js warnings about the rejection
// being handled asynchronously. The error is handled later when // being handled asynchronously. The error is handled later when
// `promise` is awaited, but we log it here anyway in case it gets // `promise` is awaited, but we log it here anyway in case it gets
@ -350,36 +401,22 @@ export async function waitForPromiseToBeFulfilledAfterRunningAllTimers(
return promise; return promise;
} }
/**
* @typedef {{providerType: "infura" | "custom", infuraNetwork?: string, customRpcUrl?: string, customChainId?: string}} WithClientOptions
*
* The options bag that `withNetworkClient` takes.
*/
/**
* @typedef {(client: MockNetworkClient) => Promise<any>} WithClientCallback
*
* The callback that `withNetworkClient` takes.
*/
/** /**
* Builds a provider from the middleware (for the provider type) along with a * Builds a provider from the middleware (for the provider type) along with a
* block tracker, runs the given function with those two things, and then * block tracker, runs the given function with those two things, and then
* ensures the block tracker is stopped at the end. * ensures the block tracker is stopped at the end.
* *
* @param {WithClientOptions} options - An options bag. * @param options - An options bag.
* @param {"infura" | "custom"} options.providerType - The type of network * @param options.providerType - The type of network client being tested.
* client being tested. * @param options.infuraNetwork - The name of the Infura network being tested,
* @param {string} [options.infuraNetwork] - The name of the Infura network being * assuming that `providerType` is "infura" (default: "mainnet").
* tested, assuming that `providerType` is "infura" (default: "mainnet"). * @param options.customRpcUrl - The URL of the custom RPC endpoint, assuming
* @param {string} [options.customRpcUrl] - The URL of the custom RPC endpoint, * that `providerType` is "custom".
* assuming that `providerType` is "custom". * @param options.customChainId - The chain id belonging to the custom RPC
* @param {string} [options.customChainId] - The chain id belonging to the * endpoint, assuming that `providerType` is "custom" (default: "0x1").
* custom RPC endpoint, assuming that `providerType` is "custom" (default: * @param fn - A function which will be called with an object that allows
* "0x1"). * interaction with the network client.
* @param {WithClientCallback} fn - A function which will be called with an * @returns The return value of the given function.
* object that allows interaction with the network client.
* @returns {Promise<any>} The return value of the given function.
*/ */
export async function withNetworkClient( export async function withNetworkClient(
{ {
@ -387,8 +424,8 @@ export async function withNetworkClient(
infuraNetwork = 'mainnet', infuraNetwork = 'mainnet',
customRpcUrl = MOCK_RPC_URL, customRpcUrl = MOCK_RPC_URL,
customChainId = '0x1', customChainId = '0x1',
}, }: MockOptions,
fn, fn: (client: MockNetworkClient) => Promise<any>,
) { ) {
if (providerType !== 'infura' && providerType !== 'custom') { if (providerType !== 'infura' && providerType !== 'custom') {
throw new Error( throw new Error(
@ -414,20 +451,21 @@ export async function withNetworkClient(
? createNetworkClient({ ? createNetworkClient({
network: infuraNetwork, network: infuraNetwork,
infuraProjectId: MOCK_INFURA_PROJECT_ID, infuraProjectId: MOCK_INFURA_PROJECT_ID,
type: 'infura', type: NetworkClientType.Infura,
}) })
: createNetworkClient({ : createNetworkClient({
chainId: customChainId, chainId: customChainId,
rpcUrl: customRpcUrl, rpcUrl: customRpcUrl,
type: 'custom', type: NetworkClientType.Custom,
}); });
process.env.IN_TEST = inTest; process.env.IN_TEST = inTest;
const { provider, blockTracker } = clientUnderTest; const { provider, blockTracker } = clientUnderTest;
const ethQuery = new EthQuery(provider); const ethQuery = new EthQuery(provider);
const curriedMakeRpcCall = (request) => makeRpcCall(ethQuery, request); const curriedMakeRpcCall = (request: Request) =>
const makeRpcCallsInSeries = async (requests) => { makeRpcCall(ethQuery, request);
const makeRpcCallsInSeries = async (requests: Request[]) => {
const responses = []; const responses = [];
for (const request of requests) { for (const request of requests) {
responses.push(await curriedMakeRpcCall(request)); responses.push(await curriedMakeRpcCall(request));
@ -451,6 +489,13 @@ export async function withNetworkClient(
} }
} }
type BuildMockParamsOptions = {
// The block parameter value to set.
blockParam: any;
// The index of the block parameter.
blockParamIndex: number;
};
/** /**
* Build mock parameters for a JSON-RPC call. * Build mock parameters for a JSON-RPC call.
* *
@ -460,16 +505,15 @@ export async function withNetworkClient(
* The block parameter can be set to a custom value. If no value is given, it * The block parameter can be set to a custom value. If no value is given, it
* is set as undefined. * is set as undefined.
* *
* @param {object} args - Arguments. * @param args - Arguments.
* @param {number} args.blockParamIndex - The index of the block parameter. * @param args.blockParamIndex - The index of the block parameter.
* @param {any} [args.blockParam] - The block parameter value to set. * @param args.blockParam - The block parameter value to set.
* @returns {any[]} The mock params. * @returns The mock params.
*/ */
export function buildMockParams({ blockParam, blockParamIndex }) { export function buildMockParams({
if (blockParamIndex === undefined) { blockParam,
throw new Error(`Missing 'blockParamIndex'`); blockParamIndex,
} }: BuildMockParamsOptions) {
const params = new Array(blockParamIndex).fill('some value'); const params = new Array(blockParamIndex).fill('some value');
params[blockParamIndex] = blockParam; params[blockParamIndex] = blockParam;
@ -480,18 +524,18 @@ export function buildMockParams({ blockParam, blockParamIndex }) {
* Returns a partial JSON-RPC request object, with the "block" param replaced * Returns a partial JSON-RPC request object, with the "block" param replaced
* with the given value. * with the given value.
* *
* @param {object} request - The request object. * @param request - The request object.
* @param {string} request.method - The request method. * @param request.method - The request method.
* @param {params} [request.params] - The request params. * @param request.params - The request params.
* @param {number} blockParamIndex - The index within the `params` array of the * @param blockParamIndex - The index within the `params` array of the block
* block param. * param.
* @param {any} blockParam - The desired block param value. * @param blockParam - The desired block param value.
* @returns {object} The updated request object. * @returns The updated request object.
*/ */
export function buildRequestWithReplacedBlockParam( export function buildRequestWithReplacedBlockParam(
{ method, params = [] }, { method, params = [] }: Request,
blockParamIndex, blockParamIndex: number,
blockParam, blockParam: any,
) { ) {
const updatedParams = params.slice(); const updatedParams = params.slice();
updatedParams[blockParamIndex] = blockParam; updatedParams[blockParamIndex] = blockParam;

View File

@ -1,6 +1,7 @@
/* eslint-disable jest/require-top-level-describe, jest/no-export */ /* eslint-disable jest/require-top-level-describe, jest/no-export */
import { import {
ProviderType,
waitForPromiseToBeFulfilledAfterRunningAllTimers, waitForPromiseToBeFulfilledAfterRunningAllTimers,
withMockedCommunications, withMockedCommunications,
withNetworkClient, withNetworkClient,
@ -11,6 +12,11 @@ import {
buildJsonRpcEngineEmptyResponseErrorMessage, buildJsonRpcEngineEmptyResponseErrorMessage,
} from './shared-tests'; } from './shared-tests';
type TestsForRpcMethodAssumingNoBlockParamOptions = {
providerType: ProviderType;
numberOfParameters: number;
};
/** /**
* Defines tests which exercise the behavior exhibited by an RPC method which is * Defines tests which exercise the behavior exhibited by an RPC method which is
* assumed to not take a block parameter. Even if it does, the value of this * assumed to not take a block parameter. Even if it does, the value of this
@ -23,8 +29,11 @@ import {
* either `infura` or `custom` (default: "infura"). * either `infura` or `custom` (default: "infura").
*/ */
export function testsForRpcMethodAssumingNoBlockParam( export function testsForRpcMethodAssumingNoBlockParam(
method, method: string,
{ numberOfParameters, providerType }, {
numberOfParameters,
providerType,
}: TestsForRpcMethodAssumingNoBlockParamOptions,
) { ) {
if (providerType !== 'infura' && providerType !== 'custom') { if (providerType !== 'infura' && providerType !== 'custom') {
throw new Error( throw new Error(

View File

@ -1,7 +1,16 @@
/* eslint-disable jest/require-top-level-describe, jest/no-export */ /* eslint-disable jest/require-top-level-describe, jest/no-export */
import { fill } from 'lodash'; import { fill } from 'lodash';
import { withMockedCommunications, withNetworkClient } from './helpers'; import {
ProviderType,
withMockedCommunications,
withNetworkClient,
} from './helpers';
type TestsForRpcMethodNotHandledByMiddlewareOptions = {
providerType: ProviderType;
numberOfParameters: number;
};
/** /**
* Defines tests which exercise the behavior exhibited by an RPC method that * Defines tests which exercise the behavior exhibited by an RPC method that
@ -15,8 +24,11 @@ import { withMockedCommunications, withNetworkClient } from './helpers';
* RPC method takes. * RPC method takes.
*/ */
export function testsForRpcMethodNotHandledByMiddleware( export function testsForRpcMethodNotHandledByMiddleware(
method, method: string,
{ providerType, numberOfParameters }, {
providerType,
numberOfParameters,
}: TestsForRpcMethodNotHandledByMiddlewareOptions,
) { ) {
if (providerType !== 'infura' && providerType !== 'custom') { if (providerType !== 'infura' && providerType !== 'custom') {
throw new Error( throw new Error(

View File

@ -2,7 +2,11 @@
import { testsForRpcMethodsThatCheckForBlockHashInResponse } from './block-hash-in-response'; import { testsForRpcMethodsThatCheckForBlockHashInResponse } from './block-hash-in-response';
import { testsForRpcMethodSupportingBlockParam } from './block-param'; import { testsForRpcMethodSupportingBlockParam } from './block-param';
import { withMockedCommunications, withNetworkClient } from './helpers'; import {
ProviderType,
withMockedCommunications,
withNetworkClient,
} from './helpers';
import { testsForRpcMethodAssumingNoBlockParam } from './no-block-param'; import { testsForRpcMethodAssumingNoBlockParam } from './no-block-param';
import { testsForRpcMethodNotHandledByMiddleware } from './not-handled-by-middleware'; import { testsForRpcMethodNotHandledByMiddleware } from './not-handled-by-middleware';
@ -13,7 +17,7 @@ import { testsForRpcMethodNotHandledByMiddleware } from './not-handled-by-middle
* @param reason - The exact reason for failure. * @param reason - The exact reason for failure.
* @returns The error message. * @returns The error message.
*/ */
export function buildInfuraClientRetriesExhaustedErrorMessage(reason) { export function buildInfuraClientRetriesExhaustedErrorMessage(reason: string) {
return new RegExp( return new RegExp(
`^InfuraProvider - cannot complete request. All retries exhausted\\..+${reason}`, `^InfuraProvider - cannot complete request. All retries exhausted\\..+${reason}`,
'us', 'us',
@ -27,7 +31,7 @@ export function buildInfuraClientRetriesExhaustedErrorMessage(reason) {
* @param method - The RPC method. * @param method - The RPC method.
* @returns The error message. * @returns The error message.
*/ */
export function buildJsonRpcEngineEmptyResponseErrorMessage(method) { export function buildJsonRpcEngineEmptyResponseErrorMessage(method: string) {
return new RegExp( return new RegExp(
`^JsonRpcEngine: Response has no error or result for request:.+"method": "${method}"`, `^JsonRpcEngine: Response has no error or result for request:.+"method": "${method}"`,
'us', 'us',
@ -42,7 +46,7 @@ export function buildJsonRpcEngineEmptyResponseErrorMessage(method) {
* @param reason - The reason. * @param reason - The reason.
* @returns The error message. * @returns The error message.
*/ */
export function buildFetchFailedErrorMessage(url, reason) { export function buildFetchFailedErrorMessage(url: string, reason: string) {
return new RegExp( return new RegExp(
`^request to ${url}(/[^/ ]*)+ failed, reason: ${reason}`, `^request to ${url}(/[^/ ]*)+ failed, reason: ${reason}`,
'us', 'us',
@ -57,7 +61,7 @@ export function buildFetchFailedErrorMessage(url, reason) {
* exposed by `createInfuraClient` is tested; if `custom`, then the middleware * exposed by `createInfuraClient` is tested; if `custom`, then the middleware
* exposed by `createJsonRpcClient` will be tested. * exposed by `createJsonRpcClient` will be tested.
*/ */
export function testsForProviderType(providerType) { export function testsForProviderType(providerType: ProviderType) {
// Ethereum JSON-RPC spec: <https://ethereum.github.io/execution-apis/api-documentation/> // Ethereum JSON-RPC spec: <https://ethereum.github.io/execution-apis/api-documentation/>
// Infura documentation: <https://docs.infura.io/infura/networks/ethereum/json-rpc-methods> // Infura documentation: <https://docs.infura.io/infura/networks/ethereum/json-rpc-methods>

View File

@ -368,6 +368,7 @@
"@babel/preset-typescript": "^7.16.7", "@babel/preset-typescript": "^7.16.7",
"@babel/register": "^7.5.5", "@babel/register": "^7.5.5",
"@ethersproject/bignumber": "^5.7.0", "@ethersproject/bignumber": "^5.7.0",
"@json-rpc-specification/meta-schema": "^1.0.6",
"@lavamoat/allow-scripts": "^2.0.3", "@lavamoat/allow-scripts": "^2.0.3",
"@lavamoat/lavapack": "^5.0.0", "@lavamoat/lavapack": "^5.0.0",
"@metamask/auto-changelog": "^2.1.0", "@metamask/auto-changelog": "^2.1.0",

View File

@ -3286,6 +3286,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@json-rpc-specification/meta-schema@npm:^1.0.6":
version: 1.0.6
resolution: "@json-rpc-specification/meta-schema@npm:1.0.6"
checksum: 2eb9c6c6c73bb38350c7180d1ad3c5b8462406926cae753741895b457d7b1b9f0b74148daf3462bb167cef39efdd1d9090308edf4d4938956863acb643c146eb
languageName: node
linkType: hard
"@keystonehq/base-eth-keyring@npm:^0.7.1": "@keystonehq/base-eth-keyring@npm:^0.7.1":
version: 0.7.1 version: 0.7.1
resolution: "@keystonehq/base-eth-keyring@npm:0.7.1" resolution: "@keystonehq/base-eth-keyring@npm:0.7.1"
@ -24143,6 +24150,7 @@ __metadata:
"@ethersproject/providers": ^5.7.2 "@ethersproject/providers": ^5.7.2
"@formatjs/intl-relativetimeformat": ^5.2.6 "@formatjs/intl-relativetimeformat": ^5.2.6
"@fortawesome/fontawesome-free": ^5.13.0 "@fortawesome/fontawesome-free": ^5.13.0
"@json-rpc-specification/meta-schema": ^1.0.6
"@keystonehq/bc-ur-registry-eth": ^0.12.1 "@keystonehq/bc-ur-registry-eth": ^0.12.1
"@keystonehq/metamask-airgapped-keyring": ^0.6.1 "@keystonehq/metamask-airgapped-keyring": ^0.6.1
"@lavamoat/allow-scripts": ^2.0.3 "@lavamoat/allow-scripts": ^2.0.3