Skip to content

Commit

Permalink
Testability
Browse files Browse the repository at this point in the history
  • Loading branch information
BelfordZ committed Dec 5, 2022
1 parent b6a0952 commit 2a223d9
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 68 deletions.
2 changes: 2 additions & 0 deletions packages/network-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,12 @@
"@metamask/eth-json-rpc-infura": "^7.0.0",
"async-mutex": "^0.2.6",
"babel-runtime": "^6.26.0",
"eth-block-tracker": "^6.0.0",
"eth-json-rpc-infura": "^5.1.0",
"eth-json-rpc-middleware": "^9.0.1",
"eth-query": "^2.1.2",
"immer": "^9.0.6",
"json-rpc-engine": "^6.1.0",
"web3-provider-engine": "^16.0.3"
},
"devDependencies": {
Expand Down
141 changes: 104 additions & 37 deletions packages/network-controller/src/NetworkController.test.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,60 @@
import * as sinon from 'sinon';
import { ControllerMessenger } from '@metamask/base-controller';
import { NetworkType, NetworksChainId } from '@metamask/controller-utils';
import SafeEventEmitter from '@metamask/safe-event-emitter';
import nock from 'nock';
import {
NetworkController,
NetworkControllerMessenger,
NetworkControllerOptions,
ProviderConfig,
} from './NetworkController';
import SafeEventEmitter from '@metamask/safe-event-emitter';
import nock from 'nock';

const RPC_TARGET = 'http://foo';

type WithMockedBlockTrackerOptions = {
nextBlockNumber?: () => string
nextBlockNumber?: () => string;
};

const withMockedBlockTracker = (fn?: () => void, options: WithMockedBlockTrackerOptions = {}) => {
const nextBlockNumber = options.nextBlockNumber ? options.nextBlockNumber : () => '0x42';

nock(new RegExp('https://.*'))
.post(
new RegExp('.*'),
{
jsonrpc: '2.0',
id: new RegExp('.*'),
method: new RegExp('.*'),
params: []
}
)
.reply((uri, reqBody: any) => {
const withMockedBlockTracker = async (
options: WithMockedBlockTrackerOptions = {},
) => {
const nextBlockNumber = options.nextBlockNumber
? options.nextBlockNumber
: () => '0x42';

const urlRegex = /https:\/\/.*/u;
const anyRegex = /.*/u;
nock(urlRegex)
.post(anyRegex, {
jsonrpc: '2.0',
id: anyRegex,
method: "eth_blockNumber",
params: [],
})
.reply((_, reqBody: any) => {
console.log(reqBody);
return [
200,
{ jsonrpc: '2.0', id: reqBody.id, result: nextBlockNumber() },
];
})
.persist();

nock(urlRegex)
.post(anyRegex, {
jsonrpc: '2.0',
id: anyRegex,
method: "eth_getBlockByNumber",
params: ["0x42", false],
})
.reply((_, reqBody: any) => {
console.log(reqBody);
return [
200,
{ jsonrpc: '2.0', id: reqBody.id, result: nextBlockNumber() }
{ jsonrpc: '2.0', id: reqBody.id, result: {} },
];
}).persist();
})
.persist();
};

const setupController = (
Expand All @@ -55,7 +74,6 @@ const setupController = (
messenger,
};
const controller = new NetworkController(networkControllerOpts);
controller.setProviderType(controller.state.provider.type);
return controller;
};

Expand All @@ -72,12 +90,11 @@ describe('NetworkController', () => {

afterEach(() => {
sinon.restore();
// nock.restore();
// nock.cleanAll();
nock.restore();
nock.cleanAll();
});

it.only('should set default state', () => {
withMockedBlockTracker();
it('should set default state', () => {
const controller = new NetworkController({
messenger,
infuraProjectId: 'potate',
Expand All @@ -86,7 +103,9 @@ describe('NetworkController', () => {
expect(controller.state).toStrictEqual({
network: 'loading',
isCustomNetwork: false,
properties: {},
properties: {
isEIP1559Compatible: false
},
provider: {
type: 'mainnet',
chainId: '1',
Expand All @@ -100,21 +119,42 @@ describe('NetworkController', () => {
messenger,
};
const controller = new NetworkController(networkControllerOpts);
const setupInfuraProvider = jest.spyOn(NetworkController.prototype as any, 'setupInfuraProvider');
setupInfuraProvider.mockImplementationOnce(() => { });

controller.setProviderType(controller.state.provider.type);
expect(controller.provider).toBeInstanceOf(SafeEventEmitter);
expect(setupInfuraProvider).toHaveBeenCalled();
});

(
['kovan', 'rinkeby', 'ropsten', 'mainnet', 'localhost'] as NetworkType[]
['kovan', 'rinkeby', 'ropsten', 'mainnet'] as NetworkType[]
).forEach((n) => {
it(`should create a provider instance for ${n} infura network`, () => {
const networkController = setupController(n, messenger);
expect(networkController.provider).toBeInstanceOf(SafeEventEmitter);

const setupInfuraProvider = jest.spyOn(NetworkController.prototype as any, 'setupInfuraProvider');
setupInfuraProvider.mockImplementationOnce(() => { });
expect(networkController.state.isCustomNetwork).toBe(false);
networkController.setProviderType(n);
expect(setupInfuraProvider).toHaveBeenCalled();
});
});

it('should create a provider instance for optimism network', () => {
it(`should create a provider instance for localhost network`, () => {
const networkController = setupController('localhost', messenger);

const setupStandardProvider = jest.spyOn(
NetworkController.prototype as any,
'setupStandardProvider'
);
setupStandardProvider.mockImplementationOnce(() => { });

expect(networkController.state.isCustomNetwork).toBe(false);
networkController.setProviderType('localhost');
expect(setupStandardProvider).toHaveBeenCalled();
});

it.only('should create a provider instance for optimism network', () => {
const networkControllerOpts: NetworkControllerOptions = {
infuraProjectId: 'foo',
state: {
Expand All @@ -128,12 +168,21 @@ describe('NetworkController', () => {
},
messenger,
};

const controller = new NetworkController(networkControllerOpts);
expect(controller.provider).toBeInstanceOf(SafeEventEmitter);

const setupStandardProvider = jest.spyOn(
NetworkController.prototype as any,
'setupStandardProvider'
);
setupStandardProvider.mockImplementationOnce(() => { });

controller.setProviderType(controller.state.provider.type);
expect(controller.state.isCustomNetwork).toBe(true);
expect(setupStandardProvider).toHaveBeenCalled();
});

it('should create a provider instance for rpc network', () => {
it.only('should create a provider instance for rpc network', () => {
const networkControllerOpts: NetworkControllerOptions = {
infuraProjectId: 'foo',
state: {
Expand All @@ -147,20 +196,33 @@ describe('NetworkController', () => {
messenger,
};
const controller = new NetworkController(networkControllerOpts);

const setupStandardProvider = jest.spyOn(
NetworkController.prototype as any,
'setupStandardProvider'
);
setupStandardProvider.mockImplementationOnce(() => { });

controller.setProviderType(controller.state.provider.type);
expect(controller.provider).toBeInstanceOf(SafeEventEmitter);
expect(controller.state.isCustomNetwork).toBe(false);
expect(setupStandardProvider).toHaveBeenCalled();
});

it('should set new RPC target', () => {
const controller = new NetworkController({ messenger, infuraProjectId: 'potate' });
const controller = new NetworkController({
messenger,
infuraProjectId: 'potate',
});
controller.setRpcTarget(RPC_TARGET, NetworksChainId.rpc);
expect(controller.state.provider.rpcTarget).toBe(RPC_TARGET);
expect(controller.state.isCustomNetwork).toBe(false);
});

it('should set new provider type', () => {
const controller = new NetworkController({ messenger, infuraProjectId: 'potate' });
const controller = new NetworkController({
messenger,
infuraProjectId: 'potate',
});
controller.setProviderType('localhost');
expect(controller.state.provider.type).toBe('localhost');
expect(controller.state.isCustomNetwork).toBe(false);
Expand Down Expand Up @@ -207,7 +269,10 @@ describe('NetworkController', () => {
});

it('should throw when setting an unrecognized provider type', () => {
const controller = new NetworkController({ messenger, infuraProjectId: 'potate' });
const controller = new NetworkController({
messenger,
infuraProjectId: 'potate',
});
expect(() => controller.setProviderType('junk' as NetworkType)).toThrow(
"Unrecognized network type: 'junk'",
);
Expand All @@ -223,7 +288,9 @@ describe('NetworkController', () => {
});
controller.setProviderType(controller.state.provider.type);
controller.lookupNetwork = sinon.stub();
if (controller.provider === undefined) { throw new Error('provider is undefined'); }
if (controller.provider === undefined) {
throw new Error('provider is undefined');
}
controller.provider.emit('error', {});
expect((controller.lookupNetwork as any).called).toBe(true);
});
Expand Down
44 changes: 31 additions & 13 deletions packages/network-controller/src/NetworkController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,15 @@ import {
NetworksChainId,
NetworkType,
} from '@metamask/controller-utils';
import { JsonRpcEngine } from 'json-rpc-engine';
import createInfuraClient, { InfuraNetworkType } from './clients/createInfuraClient';
import { providerFromEngine, SafeEventEmitterProvider } from 'eth-json-rpc-middleware';
import { JsonRpcEngine, JsonRpcMiddleware } from 'json-rpc-engine';
import {
providerFromEngine,
SafeEventEmitterProvider,
} from 'eth-json-rpc-middleware';
import { PollingBlockTracker } from 'eth-block-tracker';
import createInfuraClient, {
InfuraNetworkType,
} from './clients/createInfuraClient';
import createJsonRpcClient from './clients/createJsonRpcClient';

/**
Expand Down Expand Up @@ -162,12 +168,14 @@ export class NetworkController extends BaseControllerV2<
private initializeProvider(
type: NetworkType,
rpcTarget?: string,
chainId?: string
chainId?: string,
) {
this.update((state) => {
state.isCustomNetwork = this.getIsCustomNetwork(chainId);
});

console.log("initialize provider:");
console.log(type);
console.log(chainId);
switch (type) {
case 'kovan':
case MAINNET:
Expand All @@ -180,8 +188,7 @@ export class NetworkController extends BaseControllerV2<
this.setupStandardProvider(LOCALHOST_RPC_URL);
break;
case RPC:
rpcTarget &&
this.setupStandardProvider(rpcTarget, chainId as string);
rpcTarget && this.setupStandardProvider(rpcTarget, chainId as string);
break;
default:
throw new Error(`Unrecognized network type: '${type}'`);
Expand All @@ -201,7 +208,9 @@ export class NetworkController extends BaseControllerV2<

private registerProvider() {
if (this.provider === undefined) {
throw new Error('dont call registerProvider when networkController.provider is unset');
throw new Error(
'dont call registerProvider when networkController.provider is unset',
);
}

this.provider.on('error', this.verifyNetwork.bind(this));
Expand All @@ -216,10 +225,7 @@ export class NetworkController extends BaseControllerV2<
this.updateProvider(networkMiddleware, blockTracker);
}

private setupStandardProvider(
rpcTarget: string,
chainId?: string,
) {
private setupStandardProvider(rpcTarget: string, chainId?: string) {
const { networkMiddleware, blockTracker } = createJsonRpcClient(
rpcTarget,
chainId,
Expand All @@ -241,14 +247,24 @@ export class NetworkController extends BaseControllerV2<

// extensions network controller saves a copy of the blockTracker.
// need to figure out if we migrate this functionality or not.
private updateProvider(networkMiddleware: any, blockTracker: any) {
private updateProvider(
networkMiddleware: JsonRpcMiddleware<unknown, unknown>,
blockTracker: PollingBlockTracker,
) {
this.safelyStopProvider(this.provider);

const engine = new JsonRpcEngine();
engine.push(networkMiddleware);
const provider = providerFromEngine(engine);

this.provider = provider;
this.blockTracker = blockTracker;

this.messagingSystem.publish(
`NetworkController:providerChange`,
this.state.provider,
);

this.registerProvider();
}

Expand All @@ -262,6 +278,8 @@ export class NetworkController extends BaseControllerV2<
this.state.network === 'loading' && this.lookupNetwork();
}

blockTracker: PollingBlockTracker | undefined;

/**
* Ethereum provider object for the current network
* todo: should never be undefined (definitely assigned in constructor)
Expand Down
Loading

0 comments on commit 2a223d9

Please sign in to comment.