From e29faca3a6a39d1c92577b36ae670989c658795f Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Mon, 15 May 2023 13:34:34 -0230 Subject: [PATCH] Refactor `lookupNetwork` unit tests (#19070) The `lookupNetwork` unit tests have been updated to expand test coverage and match the unit tests for the core network controller. A helper function `lookupNetworkTests` has been copied from core. It covers most of the behavior of the function. Vidation tests and functional tests not covered in core have been retained, but any tests that are now redundant have been deleted. Relates to #1197 --- .../network/network-controller.test.ts | 3058 +++++++---------- 1 file changed, 1271 insertions(+), 1787 deletions(-) diff --git a/app/scripts/controllers/network/network-controller.test.ts b/app/scripts/controllers/network/network-controller.test.ts index 2099e427e..7ceb32d2a 100644 --- a/app/scripts/controllers/network/network-controller.test.ts +++ b/app/scripts/controllers/network/network-controller.test.ts @@ -15,6 +15,7 @@ import { NetworkControllerEventType, NetworkControllerOptions, NetworkControllerState, + ProviderConfiguration, } from './network-controller'; import { createNetworkClient, @@ -86,19 +87,19 @@ const DEFAULT_INFURA_PROJECT_ID = 'fake-infura-project-id'; const INFURA_NETWORKS = [ { networkType: NETWORK_TYPES.MAINNET, - chainId: '0x1', + chainId: '0x1' as const, ticker: 'ETH', blockExplorerUrl: 'https://etherscan.io', }, { networkType: NETWORK_TYPES.GOERLI, - chainId: '0x5', + chainId: '0x5' as const, ticker: 'GoerliETH', blockExplorerUrl: 'https://goerli.etherscan.io', }, { networkType: NETWORK_TYPES.SEPOLIA, - chainId: '0xaa36a7', + chainId: '0xaa36a7' as const, ticker: 'SepoliaETH', blockExplorerUrl: 'https://sepolia.etherscan.io', }, @@ -1252,852 +1253,6 @@ describe('NetworkController', () => { INFURA_NETWORKS.forEach(({ networkType }) => { describe(`when the type in the provider configuration is "${networkType}"`, () => { - describe('if the request for eth_getBlockByNumber responds successfully', () => { - it('stores the fact that the network is available', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkStatus).toBe( - 'unknown', - ); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkStatus).toBe( - 'available', - ); - }, - ); - }); - - it('stores the ID of the network', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkId).toBeNull(); - - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkId).toBe('1'); - }, - ); - }); - - it('stores the fact that the network supports EIP-1559 when baseFeePerGas is in the block header', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', - }, - networkDetails: { - EIPS: {}, - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect( - controller.store.getState().networkDetails.EIPS[1559], - ).toBeTruthy(); - }, - ); - }); - - it('stores the fact that the network does not support EIP-1559 when baseFeePerGas is not in the block header', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', - }, - networkDetails: { - EIPS: {}, - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect( - controller.store.getState().networkDetails.EIPS[1559], - ).toBe(false); - }, - ); - }); - - it('emits infuraIsUnblocked', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID - // of the network selected, it just needs to exist - chainId: '0x9999999', - }, - }, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - - const infuraIsUnblocked = await waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.InfuraIsUnblocked, - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(infuraIsUnblocked).toBeTruthy(); - }, - ); - }); - }); - - describe('if the request for eth_blockNumber responds with a "countryBlocked" error', () => { - it('stores the fact that the network is blocked', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID - // of the network selected, it just needs to exist - chainId: '0x9999999', - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkStatus).toBe( - 'blocked', - ); - }, - ); - }); - - it('clears the ID of the network from state', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - { - request: { - method: 'net_version', - }, - response: { - result: '2', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkId).toBe('1'); - - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - expect(controller.store.getState().networkId).toBeNull(); - }, - ); - }); - - it('clears whether the network supports EIP-1559 from state along with any other network details', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', - }, - networkDetails: { - EIPS: { - 1559: true, - }, - other: 'details', - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - expect( - controller.store.getState().networkDetails, - ).toStrictEqual({ - EIPS: { - 1559: undefined, - }, - }); - }, - ); - }); - - it('emits infuraIsBlocked', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID - // of the network selected, it just needs to exist - chainId: '0x9999999', - }, - }, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - - const infuraIsBlocked = await waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.InfuraIsBlocked, - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(infuraIsBlocked).toBeTruthy(); - }, - ); - }); - - it('does not emit infuraIsUnblocked', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID - // of the network selected, it just needs to exist - chainId: '0x9999999', - }, - }, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - - const promiseForNoInfuraIsUnblockedEvents = - waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.InfuraIsUnblocked, - count: 0, - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(await promiseForNoInfuraIsUnblockedEvents).toBeTruthy(); - }, - ); - }); - }); - - describe('if the request for eth_getBlockByNumber responds with a generic error', () => { - it('stores the network status as unavailable if the error does not translate to an internal RPC error', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - { - request: { - method: 'net_version', - }, - response: { - result: '2', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - error: ethErrors.rpc.methodNotFound(), - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkStatus).toBe( - 'available', - ); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - expect(controller.store.getState().networkStatus).toBe( - 'unavailable', - ); - }, - ); - }); - - it('stores the network status as unknown if the error translates to an internal RPC error', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - { - request: { - method: 'net_version', - }, - response: { - result: '2', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkStatus).toBe( - 'available', - ); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - expect(controller.store.getState().networkStatus).toBe( - 'unknown', - ); - }, - ); - }); - - it('clears the ID of the network from state', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - { - request: { - method: 'net_version', - }, - response: { - result: '2', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkId).toBe('1'); - - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - expect(controller.store.getState().networkId).toBeNull(); - }, - ); - }); - - it('clears whether the network supports EIP-1559 from state along with any other network details', async () => { - const intentionalErrorMessage = - 'intentional error from eth_getBlockByNumber'; - - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', - }, - networkDetails: { - EIPS: { - 1559: true, - }, - other: 'details', - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation: async () => { - try { - await controller.lookupNetwork(); - } catch (error) { - if (error !== intentionalErrorMessage) { - console.error(error); - } - } - }, - }); - expect( - controller.store.getState().networkDetails, - ).toStrictEqual({ - EIPS: { - 1559: undefined, - }, - }); - }, - ); - }); - - it('does not emit infuraIsBlocked', async () => { - await withController(async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - - const promiseForNoInfuraIsBlockedEvents = waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.InfuraIsBlocked, - count: 0, - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(await promiseForNoInfuraIsBlockedEvents).toBeTruthy(); - }); - }); - - it('does not emit infuraIsUnblocked', async () => { - await withController( - { - state: { - providerConfig: { - type: networkType, - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', - }, - }, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - - const promiseForNoInfuraIsUnblockedEvents = - waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.InfuraIsUnblocked, - count: 0, - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(await promiseForNoInfuraIsUnblockedEvents).toBeTruthy(); - }, - ); - }); - }); - describe('if the network was switched after the eth_getBlockByNumber request started but before it completed', () => { it('stores the network status of the second network, not the first', async () => { await withController( @@ -2482,948 +1637,20 @@ describe('NetworkController', () => { ); }); }); + + lookupNetworkTests({ + expectedProviderConfig: buildProviderConfig({ type: networkType }), + initialState: { + providerConfig: buildProviderConfig({ type: networkType }), + }, + operation: async (controller) => { + await controller.lookupNetwork(); + }, + }); }); }); describe('when the type in the provider configuration is "rpc"', () => { - describe('if both net_version and eth_getBlockByNumber respond successfully', () => { - it('stores the fact the network is available', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkStatus).toBe('unknown'); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkStatus).toBe( - 'available', - ); - }, - ); - }); - - it('stores the ID of the network', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: { - result: '42', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkId).toBe(null); - - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkId).toBe('42'); - }, - ); - }); - - it('stores the fact that the network supports EIP-1559 when baseFeePerGas is in the block header', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect( - controller.store.getState().networkDetails.EIPS[1559], - ).toBeTruthy(); - }, - ); - }); - - it('stores the fact that the network does not support EIP-1559 when baseFeePerGas is not in the block header', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect( - controller.store.getState().networkDetails.EIPS[1559], - ).toBe(false); - }, - ); - }); - - it('emits infuraIsUnblocked', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, - }, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - - const infuraIsUnblocked = await waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.InfuraIsUnblocked, - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(infuraIsUnblocked).toBeTruthy(); - }, - ); - }); - }); - - describe('if the request for eth_getBlockByNumber responds successfully, but the request for net_version responds with a generic error', () => { - it('stores the network status as available if the error does not translate to an internal RPC error', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - { - request: { - method: 'net_version', - }, - error: ethErrors.rpc.methodNotFound(), - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkStatus).toBe( - 'available', - ); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkStatus).toBe( - 'unavailable', - ); - }, - ); - }); - - it('stores the network status as unknown if the error translates to an internal RPC error', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - { - request: { - method: 'net_version', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkStatus).toBe( - 'available', - ); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkStatus).toBe('unknown'); - }, - ); - }); - - it('clears the ID of the network from state', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: { - result: '42', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - { - request: { - method: 'net_version', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkId).toBe('42'); - - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkId).toBeNull(); - }, - ); - }); - - it('clears whether the network supports EIP-1559 from state along with any other network details', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, - networkDetails: { - EIPS: { - 1559: true, - }, - other: 'details', - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - { - request: { - method: 'net_version', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: false, - }, - other: 'details', - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: undefined, - }, - }); - }, - ); - }); - - it('does not emit infuraIsBlocked', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, - }, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - - const promiseForNoInfuraIsBlockedEvents = waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.InfuraIsBlocked, - count: 0, - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(await promiseForNoInfuraIsBlockedEvents).toBeTruthy(); - }, - ); - }); - - it('emits infuraIsUnblocked', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, - }, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - - const infuraIsUnblocked = await waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.InfuraIsUnblocked, - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(infuraIsUnblocked).toBeTruthy(); - }, - ); - }); - }); - - describe('if the request for net_version responds successfully, but the request for eth_getBlockByNumber responds with a generic error', () => { - it('stores the fact that the network is unavailable', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkStatus).toBe( - 'available', - ); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkStatus).toBe('unknown'); - }, - ); - }); - - it('clears the ID of the network from state', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: { - result: '42', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - { - request: { - method: 'net_version', - }, - response: { - result: '42', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkId).toBe('42'); - - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkId).toBeNull(); - }, - ); - }); - - it('clears whether the network supports EIP-1559 from state along with any other network details', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, - networkDetails: { - EIPS: {}, - other: 'details', - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: true, - }, - other: 'details', - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: undefined, - }, - }); - }, - ); - }); - - it('does not emit infuraIsBlocked', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, - }, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - - const promiseForNoInfuraIsBlockedEvents = waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.InfuraIsBlocked, - count: 0, - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(await promiseForNoInfuraIsBlockedEvents).toBeTruthy(); - }, - ); - }); - - it('emits infuraIsUnblocked', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, - }, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await withoutCallingLookupNetwork({ - controller, - operation: async () => { - await controller.initializeProvider(); - }, - }); - - const infuraIsUnblocked = await waitForPublishedEvents({ - messenger, - eventType: NetworkControllerEventType.InfuraIsUnblocked, - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(infuraIsUnblocked).toBeTruthy(); - }, - ); - }); - }); - describe('if the network was switched after the net_version request started but before it completed', () => { it('stores the network status of the second network, not the first', async () => { await withController( @@ -4240,6 +2467,18 @@ describe('NetworkController', () => { ); }); }); + + lookupNetworkTests({ + expectedProviderConfig: buildProviderConfig({ + type: NETWORK_TYPES.RPC, + }), + initialState: { + providerConfig: buildProviderConfig({ type: NETWORK_TYPES.RPC }), + }, + operation: async (controller) => { + await controller.lookupNetwork(); + }, + }); }); }); @@ -8060,6 +6299,1169 @@ function mockCreateNetworkClient() { }); } +/** + * Test an operation that performs a `lookupNetwork` call with the given + * provider configuration. All effects of the `lookupNetwork` call should be + * covered by these tests. + * + * @param args - Arguments. + * @param args.expectedProviderConfig - The provider configuration that the + * operation is expected to set. + * @param args.initialState - The initial state of the network controller. + * @param args.operation - The operation to test. + */ +function lookupNetworkTests({ + expectedProviderConfig, + initialState, + operation, +}: { + expectedProviderConfig: ProviderConfiguration; + initialState?: Partial; + operation: (controller: NetworkController) => Promise; +}) { + describe('if the network ID and network details requests resolve successfully', () => { + describe('if the current network is different from the network in state', () => { + it('updates the network in state to match', async () => { + await withController( + { + state: initialState, + }, + async ({ controller }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + response: { result: '12345' }, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + await operation(controller); + + expect(controller.store.getState().networkId).toBe('12345'); + }, + ); + }); + }); + + describe('if the version of the current network is the same as that in state', () => { + it('does not change network ID in state', async () => { + await withController( + { + state: initialState, + }, + async ({ controller }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + response: { result: '12345' }, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + await operation(controller); + + await expect(controller.store.getState().networkId).toBe('12345'); + }, + ); + }); + }); + + describe('if the network details of the current network are different from the network details in state', () => { + it('updates the network in state to match', async () => { + await withController( + { + state: { + ...initialState, + networkDetails: { + EIPS: { + 1559: false, + }, + }, + }, + }, + async ({ controller }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + response: { + result: { + baseFeePerGas: '0x1', + }, + }, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + await operation(controller); + + expect(controller.store.getState().networkDetails).toStrictEqual({ + EIPS: { + 1559: true, + }, + }); + }, + ); + }); + }); + + describe('if the network details of the current network are the same as the network details in state', () => { + it('does not change network details in state', async () => { + await withController( + { + state: { + ...initialState, + networkDetails: { + EIPS: { + 1559: true, + }, + }, + }, + }, + async ({ controller }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + response: { + result: { + baseFeePerGas: '0x1', + }, + }, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + await operation(controller); + + expect(controller.store.getState().networkDetails).toStrictEqual({ + EIPS: { + 1559: true, + }, + }); + }, + ); + }); + }); + + it('emits infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + + it('does not emit infuraIsBlocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsBlocked, + count: 0, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + }); + + describe('if an RPC error is encountered while retrieving the version of the current network', () => { + it('updates the network in state to "unavailable"', async () => { + await withController( + { + state: initialState, + }, + async ({ controller }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + error: ethErrors.rpc.limitExceeded('some error'), + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + await operation(controller); + + expect(controller.store.getState().networkStatus).toBe('unavailable'); + }, + ); + }); + + if (expectedProviderConfig.type === NETWORK_TYPES.RPC) { + it('emits infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + error: ethErrors.rpc.limitExceeded('some error'), + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + } else { + it('does not emit infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + error: ethErrors.rpc.limitExceeded('some error'), + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + count: 0, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + } + + it('does not emit infuraIsBlocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + error: ethErrors.rpc.limitExceeded('some error'), + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsBlocked, + count: 0, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + }); + + describe('if a country blocked error is encountered while retrieving the version of the current network', () => { + if (expectedProviderConfig.type === NETWORK_TYPES.RPC) { + it('updates the network in state to "unknown"', async () => { + await withController( + { + state: initialState, + }, + async ({ controller }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + await operation(controller); + + expect(controller.store.getState().networkStatus).toBe('unknown'); + }, + ); + }); + + it('emits infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + + it('does not emit infuraIsBlocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsBlocked, + count: 0, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + } else { + it('updates the network in state to "blocked"', async () => { + await withController( + { + state: initialState, + }, + async ({ controller }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + await operation(controller); + + expect(controller.store.getState().networkStatus).toBe('blocked'); + }, + ); + }); + + it('does not emit infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + count: 0, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + + it('emits infuraIsBlocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsBlocked, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + } + }); + + describe('if an internal error is encountered while retrieving the version of the current network', () => { + it('updates the network in state to "unknown"', async () => { + await withController( + { + state: initialState, + }, + async ({ controller }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + error: ethErrors.rpc.internal('some error'), + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + await operation(controller); + + expect(controller.store.getState().networkStatus).toBe('unknown'); + }, + ); + }); + + if (expectedProviderConfig.type === NETWORK_TYPES.RPC) { + it('emits infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + error: ethErrors.rpc.internal('some error'), + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + } else { + it('does not emit infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + error: ethErrors.rpc.internal('some error'), + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + count: 0, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + } + + it('does not emit infuraIsBlocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + error: ethErrors.rpc.internal('some error'), + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsBlocked, + count: 0, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + }); + + describe('if an invalid network ID is returned', () => { + it('updates the network in state to "unknown"', async () => { + await withController( + { + state: initialState, + }, + async ({ controller }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + response: { result: 'invalid' }, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + await operation(controller); + + expect(controller.store.getState().networkStatus).toBe('unknown'); + }, + ); + }); + + if (expectedProviderConfig.type === NETWORK_TYPES.RPC) { + it('emits infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + response: { result: 'invalid' }, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + } else { + it('does not emit infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + response: { result: 'invalid' }, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + count: 0, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + } + + it('does not emit infuraIsBlocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { method: 'net_version' }, + response: { result: 'invalid' }, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsBlocked, + count: 0, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + }); + + describe('if an RPC error is encountered while retrieving the network details of the current network', () => { + it('updates the network in state to "unavailable"', async () => { + await withController( + { + state: initialState, + }, + async ({ controller }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + error: ethErrors.rpc.limitExceeded('some error'), + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + await operation(controller); + + expect(controller.store.getState().networkStatus).toBe('unavailable'); + }, + ); + }); + + if (expectedProviderConfig.type === NETWORK_TYPES.RPC) { + it('emits infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + error: ethErrors.rpc.limitExceeded('some error'), + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + } else { + it('does not emit infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + error: ethErrors.rpc.limitExceeded('some error'), + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + count: 0, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + } + + it('does not emit infuraIsBlocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + error: ethErrors.rpc.limitExceeded('some error'), + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsBlocked, + count: 0, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + }); + + describe('if a country blocked error is encountered while retrieving the network details of the current network', () => { + if (expectedProviderConfig.type === NETWORK_TYPES.RPC) { + it('updates the network in state to "unknown"', async () => { + await withController( + { + state: initialState, + }, + async ({ controller }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + await operation(controller); + + expect(controller.store.getState().networkStatus).toBe('unknown'); + }, + ); + }); + + it('emits infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + + it('does not emit infuraIsBlocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsBlocked, + count: 0, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + } else { + it('updates the network in state to "blocked"', async () => { + await withController( + { + state: initialState, + }, + async ({ controller }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + await operation(controller); + + expect(controller.store.getState().networkStatus).toBe('blocked'); + }, + ); + }); + + it('does not emit infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + count: 0, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + + it('emits infuraIsBlocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsBlocked, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + } + }); + + describe('if an internal error is encountered while retrieving the network details of the current network', () => { + it('updates the network in state to "unknown"', async () => { + await withController( + { + state: initialState, + }, + async ({ controller }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + error: ethErrors.rpc.internal('some error'), + }, + ], + }); + + await operation(controller); + + expect(controller.store.getState().networkStatus).toBe('unknown'); + }, + ); + }); + + if (expectedProviderConfig.type === NETWORK_TYPES.RPC) { + it('emits infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + error: ethErrors.rpc.internal('some error'), + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + } else { + it('does not emit infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + error: ethErrors.rpc.internal('some error'), + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsUnblocked, + count: 0, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + } + + it('does not emit infuraIsBlocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + error: ethErrors.rpc.internal('some error'), + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const payloads = await waitForPublishedEvents({ + messenger, + eventType: NetworkControllerEventType.InfuraIsBlocked, + count: 0, + operation: async () => { + await operation(controller); + }, + }); + + expect(payloads).toBeTruthy(); + }, + ); + }); + }); +} + /** * Builds a controller messenger. * @@ -8146,6 +7548,41 @@ async function withController( } } +/** + * Builds a complete ProviderConfig object, filling in values that are not + * provided with defaults. + * + * @param config - An incomplete ProviderConfig object. + * @returns The complete ProviderConfig object. + */ +function buildProviderConfig( + config: Partial = {}, +): ProviderConfiguration { + if (config.type && config.type !== NETWORK_TYPES.RPC) { + const networkConfig = INFURA_NETWORKS.find( + ({ networkType }) => networkType === config.type, + ); + if (!networkConfig) { + throw new Error(`Invalid type: ${config.type}`); + } + return { + ...networkConfig, + // This is redundant with the spread operation below, but this was + // required for TypeScript to understand that this property was set to an + // Infura type. + type: config.type, + ...config, + }; + } + return { + type: NETWORK_TYPES.RPC, + chainId: '0x42', + nickname: undefined, + rpcUrl: 'http://doesntmatter.com', + ...config, + }; +} + /** * Builds an object that `createInfuraProvider` or `createJsonRpcClient` returns. * @@ -8191,6 +7628,53 @@ function buildFakeProvider(stubs: FakeProviderStub[] = []) { return new FakeProvider({ stubs: completeStubs }); } +/** + * Asks the controller to set the provider in the simplest way, stubbing the + * provider appropriately so as not to cause any errors to be thrown. This is + * useful in tests where it doesn't matter how the provider gets set, just that + * it does. Canned responses may be optionally provided for certain RPC methods + * on the provider. + * + * @param controller - The network controller. + * @param options - Additional options. + * @param options.stubs - The set of RPC methods you want to stub on the + * provider along with their responses. + * @param options.stubLookupNetworkWhileSetting - Whether to stub the call to + * `lookupNetwork` that happens when the provider is set. This option is useful + * in tests that need a provider to get set but also call `lookupNetwork` on + * their own. In this case, since the `providerConfig` setter already calls + * `lookupNetwork` once, and since `lookupNetwork` is called out of band, the + * test may run with unexpected results. By stubbing `lookupNetwork` before + * setting the provider, the test is free to explicitly call it. + * @returns The set provider. + */ +async function setFakeProvider( + controller: NetworkController, + { + stubs = [], + stubLookupNetworkWhileSetting = false, + }: { + stubs?: FakeProviderStub[]; + stubLookupNetworkWhileSetting?: boolean; + } = {}, +): Promise { + const fakeProvider = buildFakeProvider(stubs); + const fakeNetworkClient = buildFakeClient(fakeProvider); + createNetworkClientMock.mockReturnValue(fakeNetworkClient); + const lookupNetworkMock = jest.spyOn(controller, 'lookupNetwork'); + + if (stubLookupNetworkWhileSetting) { + lookupNetworkMock.mockResolvedValue(undefined); + } + + await controller.initializeProvider(); + assert(controller.getProviderAndBlockTracker().provider); + + if (stubLookupNetworkWhileSetting) { + lookupNetworkMock.mockRestore(); + } +} + /** * For each kind of way that the provider can be set, `lookupNetwork` is always * called. This can cause difficulty when testing the behavior of