1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-02 06:07:06 +01:00
metamask-extension/app/scripts/controllers/network/provider-api-tests/shared-tests.ts
Elliot Winkler 26db0aee46
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.
2023-04-14 10:21:29 -06:00

374 lines
13 KiB
TypeScript

/* eslint-disable jest/require-top-level-describe, jest/no-export, jest/no-identical-title */
import { testsForRpcMethodsThatCheckForBlockHashInResponse } from './block-hash-in-response';
import { testsForRpcMethodSupportingBlockParam } from './block-param';
import {
ProviderType,
withMockedCommunications,
withNetworkClient,
} from './helpers';
import { testsForRpcMethodAssumingNoBlockParam } from './no-block-param';
import { testsForRpcMethodNotHandledByMiddleware } from './not-handled-by-middleware';
/**
* Constructs an error message that the Infura client would produce in the event
* that it has attempted to retry the request to Infura and has failed.
*
* @param reason - The exact reason for failure.
* @returns The error message.
*/
export function buildInfuraClientRetriesExhaustedErrorMessage(reason: string) {
return new RegExp(
`^InfuraProvider - cannot complete request. All retries exhausted\\..+${reason}`,
'us',
);
}
/**
* Constructs an error message that JsonRpcEngine would produce in the event
* that the response object is empty as it leaves the middleware.
*
* @param method - The RPC method.
* @returns The error message.
*/
export function buildJsonRpcEngineEmptyResponseErrorMessage(method: string) {
return new RegExp(
`^JsonRpcEngine: Response has no error or result for request:.+"method": "${method}"`,
'us',
);
}
/**
* Constructs an error message that `fetch` with throw if it cannot make a
* request.
*
* @param url - The URL being fetched
* @param reason - The reason.
* @returns The error message.
*/
export function buildFetchFailedErrorMessage(url: string, reason: string) {
return new RegExp(
`^request to ${url}(/[^/ ]*)+ failed, reason: ${reason}`,
'us',
);
}
/**
* Defines tests that are common to both the Infura and JSON-RPC network client.
*
* @param providerType - The type of provider being tested, which determines
* which suite of middleware is being tested. If `infura`, then the middleware
* exposed by `createInfuraClient` is tested; if `custom`, then the middleware
* exposed by `createJsonRpcClient` will be tested.
*/
export function testsForProviderType(providerType: ProviderType) {
// Ethereum JSON-RPC spec: <https://ethereum.github.io/execution-apis/api-documentation/>
// Infura documentation: <https://docs.infura.io/infura/networks/ethereum/json-rpc-methods>
describe('methods included in the Ethereum JSON-RPC spec', () => {
describe('methods not handled by middleware', () => {
const notHandledByMiddleware = [
// This list is presented in the same order as in the network client
// tests on the core side.
{ name: 'eth_newFilter', numberOfParameters: 1 },
{ name: 'eth_getFilterChanges', numberOfParameters: 1 },
{ name: 'eth_newBlockFilter', numberOfParameters: 0 },
{ name: 'eth_newPendingTransactionFilter', numberOfParameters: 0 },
{ name: 'eth_uninstallFilter', numberOfParameters: 1 },
{ name: 'eth_sendRawTransaction', numberOfParameters: 1 },
{ name: 'eth_sendTransaction', numberOfParameters: 1 },
{ name: 'eth_sign', numberOfParameters: 2 },
{ name: 'eth_createAccessList', numberOfParameters: 2 },
{ name: 'eth_getLogs', numberOfParameters: 1 },
{ name: 'eth_getProof', numberOfParameters: 3 },
{ name: 'eth_getWork', numberOfParameters: 0 },
{ name: 'eth_maxPriorityFeePerGas', numberOfParameters: 0 },
{ name: 'eth_submitHashRate', numberOfParameters: 2 },
{ name: 'eth_submitWork', numberOfParameters: 3 },
{ name: 'eth_syncing', numberOfParameters: 0 },
{ name: 'eth_feeHistory', numberOfParameters: 3 },
{ name: 'debug_getRawHeader', numberOfParameters: 1 },
{ name: 'debug_getRawBlock', numberOfParameters: 1 },
{ name: 'debug_getRawTransaction', numberOfParameters: 1 },
{ name: 'debug_getRawReceipts', numberOfParameters: 1 },
{ name: 'debug_getBadBlocks', numberOfParameters: 0 },
{ name: 'eth_accounts', numberOfParameters: 0 },
{ name: 'eth_coinbase', numberOfParameters: 0 },
{ name: 'eth_hashrate', numberOfParameters: 0 },
{ name: 'eth_mining', numberOfParameters: 0 },
{ name: 'eth_signTransaction', numberOfParameters: 1 },
];
notHandledByMiddleware.forEach(({ name, numberOfParameters }) => {
describe(`method name: ${name}`, () => {
testsForRpcMethodNotHandledByMiddleware(name, {
providerType,
numberOfParameters,
});
});
});
});
describe('methods with block hashes in their result', () => {
const methodsWithBlockHashInResponse = [
{ name: 'eth_getTransactionByHash', numberOfParameters: 1 },
{ name: 'eth_getTransactionReceipt', numberOfParameters: 1 },
];
methodsWithBlockHashInResponse.forEach(({ name, numberOfParameters }) => {
describe(`method name: ${name}`, () => {
testsForRpcMethodsThatCheckForBlockHashInResponse(name, {
numberOfParameters,
providerType,
});
});
});
});
describe('methods that assume there is no block param', () => {
const assumingNoBlockParam = [
{ name: 'eth_getFilterLogs', numberOfParameters: 1 },
{ name: 'eth_blockNumber', numberOfParameters: 0 },
{ name: 'eth_estimateGas', numberOfParameters: 2 },
{ name: 'eth_gasPrice', numberOfParameters: 0 },
{ name: 'eth_getBlockByHash', numberOfParameters: 2 },
{
name: 'eth_getBlockTransactionCountByHash',
numberOfParameters: 1,
},
{
name: 'eth_getTransactionByBlockHashAndIndex',
numberOfParameters: 2,
},
{ name: 'eth_getUncleByBlockHashAndIndex', numberOfParameters: 2 },
{ name: 'eth_getUncleCountByBlockHash', numberOfParameters: 1 },
];
const blockParamIgnored = [
{ name: 'eth_getUncleCountByBlockNumber', numberOfParameters: 1 },
{ name: 'eth_getUncleByBlockNumberAndIndex', numberOfParameters: 2 },
{
name: 'eth_getTransactionByBlockNumberAndIndex',
numberOfParameters: 2,
},
{
name: 'eth_getBlockTransactionCountByNumber',
numberOfParameters: 1,
},
];
assumingNoBlockParam
.concat(blockParamIgnored)
.forEach(({ name, numberOfParameters }) =>
describe(`method name: ${name}`, () => {
testsForRpcMethodAssumingNoBlockParam(name, {
providerType,
numberOfParameters,
});
}),
);
});
describe('methods that have a param to specify the block', () => {
const supportingBlockParam = [
{
name: 'eth_call',
blockParamIndex: 1,
numberOfParameters: 2,
},
{
name: 'eth_getBalance',
blockParamIndex: 1,
numberOfParameters: 2,
},
{
name: 'eth_getBlockByNumber',
blockParamIndex: 0,
numberOfParameters: 2,
},
{ name: 'eth_getCode', blockParamIndex: 1, numberOfParameters: 2 },
{
name: 'eth_getStorageAt',
blockParamIndex: 2,
numberOfParameters: 3,
},
{
name: 'eth_getTransactionCount',
blockParamIndex: 1,
numberOfParameters: 2,
},
];
supportingBlockParam.forEach(
({ name, blockParamIndex, numberOfParameters }) => {
describe(`method name: ${name}`, () => {
testsForRpcMethodSupportingBlockParam(name, {
providerType,
blockParamIndex,
numberOfParameters,
});
});
},
);
});
describe('other methods', () => {
describe('eth_getTransactionByHash', () => {
it("refreshes the block tracker's current block if it is less than the block number that comes back in the response", async () => {
const method = 'eth_getTransactionByHash';
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' });
// This is our request.
comms.mockRpcCall({
request,
response: {
result: {
blockNumber: '0x200',
},
},
});
comms.mockNextBlockTrackerRequest({ blockNumber: '0x300' });
await withNetworkClient(
{ providerType },
async ({ makeRpcCall, blockTracker }) => {
await makeRpcCall(request);
expect(blockTracker.getCurrentBlock()).toStrictEqual('0x300');
},
);
});
});
});
describe('eth_getTransactionReceipt', () => {
it("refreshes the block tracker's current block if it is less than the block number that comes back in the response", async () => {
const method = 'eth_getTransactionReceipt';
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' });
// This is our request.
comms.mockRpcCall({
request,
response: {
result: {
blockNumber: '0x200',
},
},
});
comms.mockNextBlockTrackerRequest({ blockNumber: '0x300' });
await withNetworkClient(
{ providerType },
async ({ makeRpcCall, blockTracker }) => {
await makeRpcCall(request);
expect(blockTracker.getCurrentBlock()).toStrictEqual('0x300');
},
);
});
});
});
describe('eth_chainId', () => {
it('does not hit the RPC endpoint, instead returning the configured chain id', async () => {
const networkId = await withNetworkClient(
{ providerType: 'custom', customChainId: '0x1' },
({ makeRpcCall }) => {
return makeRpcCall({ method: 'eth_chainId' });
},
);
expect(networkId).toStrictEqual('0x1');
});
});
});
});
describe('methods not included in the Ethereum JSON-RPC spec', () => {
describe('methods not handled by middleware', () => {
const notHandledByMiddleware = [
// This list is presented in the same order as in the network client
// tests on the core side.
{ name: 'net_listening', numberOfParameters: 0 },
// TODO: Methods to add back when we add testing for subscribe middleware
// { name: 'eth_subscribe', numberOfParameters: 1 },
// { name: 'eth_unsubscribe', numberOfParameters: 1 },
{ name: 'custom_rpc_method', numberOfParameters: 1 },
{ name: 'net_peerCount', numberOfParameters: 0 },
{ name: 'parity_nextNonce', numberOfParameters: 1 },
];
notHandledByMiddleware.forEach(({ name, numberOfParameters }) => {
describe(`method name: ${name}`, () => {
testsForRpcMethodNotHandledByMiddleware(name, {
providerType,
numberOfParameters,
});
});
});
});
describe('methods that assume there is no block param', () => {
const assumingNoBlockParam = [
{ name: 'web3_clientVersion', numberOfParameters: 0 },
{ name: 'eth_protocolVersion', numberOfParameters: 0 },
];
assumingNoBlockParam.forEach(({ name, numberOfParameters }) =>
describe(`method name: ${name}`, () => {
testsForRpcMethodAssumingNoBlockParam(name, {
providerType,
numberOfParameters,
});
}),
);
});
describe('other methods', () => {
describe('net_version', () => {
// The Infura middleware includes `net_version` in its scaffold
// middleware, whereas the custom RPC middleware does not.
if (providerType === 'infura') {
it('does not hit Infura, instead returning the network ID that maps to the Infura network, as a decimal string', async () => {
const networkId = await withNetworkClient(
{ providerType: 'infura', infuraNetwork: 'goerli' },
({ makeRpcCall }) => {
return makeRpcCall({
method: 'net_version',
});
},
);
expect(networkId).toStrictEqual('5');
});
} else {
it('hits the RPC endpoint', async () => {
await withMockedCommunications(
{ providerType: 'custom' },
async (comms) => {
comms.mockRpcCall({
request: { method: 'net_version' },
response: { result: '1' },
});
const networkId = await withNetworkClient(
{ providerType: 'custom' },
({ makeRpcCall }) => {
return makeRpcCall({
method: 'net_version',
});
},
);
expect(networkId).toStrictEqual('1');
},
);
});
}
});
});
});
}