test(stack/proxy): introduce tests

This commit is contained in:
Pascal Precht 2020-03-05 14:46:35 +01:00 committed by Pascal Precht
parent 0e32cc09b5
commit 2170753dfd
6 changed files with 340 additions and 14 deletions

View File

@ -37,14 +37,22 @@
"ci": "npm run qa",
"clean": "npm run reset",
"lint": "npm-run-all lint:*",
"lint:js": "eslint src/",
"lint:js": "eslint src/ test/",
"lint:ts": "tslint -c tslint.json \"src/**/*.ts\"",
"qa": "npm-run-all lint _typecheck _build",
"reset": "npx rimraf dist embark-*.tgz package",
"solo": "embark-solo"
"solo": "embark-solo",
"test": "jest"
},
"eslintConfig": {
"extends": "../../../.eslintrc.json"
"extends": [
"../../../.eslintrc.json",
"plugin:jest/recommended",
"plugin:jest/style"
],
"rules": {
"jest/expect-expect": "off"
}
},
"dependencies": {
"@babel/runtime-corejs3": "7.8.4",
@ -63,10 +71,15 @@
"web3-providers-ws": "1.2.6"
},
"devDependencies": {
"@babel/core": "7.8.3",
"babel-jest": "25.1.0",
"embark-solo": "^5.2.3",
"embark-testing": "^5.3.0-nightly.12",
"eslint": "6.8.0",
"eslint-plugin-jest": "22.5.1",
"npm-run-all": "4.1.5",
"rimraf": "3.0.0",
"sinon": "7.4.2",
"tslint": "5.20.1",
"typescript": "3.7.2"
},
@ -74,5 +87,20 @@
"node": ">=10.17.0",
"npm": ">=6.11.3",
"yarn": ">=1.19.1"
},
"jest": {
"collectCoverage": true,
"testEnvironment": "node",
"testMatch": [
"**/test/**/*.js"
],
"transform": {
"\\.(js|ts)$": [
"babel-jest",
{
"rootMode": "upward"
}
]
}
}
}

View File

@ -20,11 +20,13 @@ export default class ProxyManager {
private isWs = false;
private _endpoint: string = "";
private inited: boolean = false;
private requestManager: any = null;
constructor(private embark: Embark, options: any) {
this.logger = embark.logger;
this.events = embark.events;
this.plugins = options.plugins;
this.requestManager = options.requestManager;
this.host = "localhost";
@ -139,10 +141,10 @@ export default class ProxyManager {
events: this.events,
isWs: false,
logger: this.logger,
plugins: this.plugins
plugins: this.plugins,
requestManager: this.requestManager
});
this.httpProxy.serve(this.host, this.rpcPort);
await this.httpProxy.serve(this.host, this.rpcPort);
this.logger.info(`HTTP Proxy for node endpoint ${endpoint} listening on ${buildUrl("http", this.host, this.rpcPort, "rpc")}`);
if (this.isWs) {
@ -150,10 +152,11 @@ export default class ProxyManager {
events: this.events,
isWs: true,
logger: this.logger,
plugins: this.plugins
plugins: this.plugins,
requestManager: this.requestManager
});
this.wsProxy.serve(this.host, this.wsPort);
await this.wsProxy.serve(this.host, this.wsPort);
this.logger.info(`WS Proxy for node endpoint ${endpoint} listening on ${buildUrl("ws", this.host, this.wsPort, "ws")}`);
}
}

View File

@ -16,7 +16,7 @@ export class Proxy {
this.events = options.events;
this.isWs = options.isWs;
this.nodeSubscriptions = {};
this._requestManager = null;
this._requestManager = options.requestManager || null;
this.events.setCommandHandler("proxy:websocket:subscribe", this.handleSubscribe.bind(this));
this.events.setCommandHandler("proxy:websocket:unsubscribe", this.handleUnsubscribe.bind(this));
@ -60,9 +60,7 @@ export class Proxy {
}
async serve(localHost, localPort) {
await this.nodeReady();
this.app = express();
if (this.isWs) {
expressWs(this.app);
@ -132,7 +130,6 @@ export class Proxy {
// Send the possibly modified request to the Node
const response = { jsonrpc: "2.0", id: modifiedRequest.request.id };
if (modifiedRequest.sendToNode !== false) {
try {
response.result = await this.forwardRequestToNode(modifiedRequest.request);
} catch (fwdReqErr) {
@ -278,7 +275,7 @@ export class Proxy {
return new Promise((resolve, reject) => {
let calledBack = false;
const data = { request, isWs: this.isWs, transport };
setTimeout(() => {
const timeoutId = setTimeout(() => {
if (calledBack) {
return;
}
@ -303,6 +300,7 @@ export class Proxy {
return reject(err);
}
calledBack = true;
clearTimeout(timeoutId);
resolve(result);
});
});
@ -312,7 +310,7 @@ export class Proxy {
return new Promise((resolve, reject) => {
const data = { originalRequest, request, response, isWs: this.isWs, transport };
let calledBack = false;
setTimeout(() => {
const timeoutId = setTimeout(() => {
if (calledBack) {
return;
}
@ -338,6 +336,7 @@ export class Proxy {
return reject(err);
}
calledBack = true;
clearTimeout(timeoutId);
resolve(result);
});
});

View File

@ -0,0 +1,87 @@
import sinon from 'sinon';
import assert from 'assert';
import { fakeEmbark } from 'embark-testing';
import ProxyManager from '../src';
import { Proxy } from '../src/proxy';
const mockRequestManager = {
send: (request, cb) => {
return new Promise(resolve => {
if (cb) {
cb(null, {});
}
resolve();
});
}
};
describe('stack/proxy', () => {
let proxyManager, embark;
beforeEach(() => {
const testBed = fakeEmbark({
blockchainConfig: {
proxy: {}
}
});
embark = testBed.embark;
proxyManager = new ProxyManager(embark, {
plugins: testBed.embark,
requestManager: mockRequestManager
});
});
afterEach(async () => {
await proxyManager.stopProxy();
embark.teardown();
sinon.restore();
});
describe('instantiation', () => {
it('should register proxy:endpoint:get command handler', () => {
embark.events.assert.commandHandlerRegistered('proxy:endpoint:get');
});
});
it('should return default proxy endpoint', async () => {
const endpoint = await embark.events.request2('proxy:endpoint:get');
assert.equal(endpoint, 'ws://localhost:8556');
});
it('should initialize', async () => {
await proxyManager.init();
assert(proxyManager.inited);
assert.equal(proxyManager.rpcPort, 8555);
assert.equal(proxyManager.wsPort, 8556);
assert(proxyManager.isWs);
});
it('should setup proxy', async () => {
embark.events.setCommandHandler('blockchain:node:provider', (cb) => {
cb({});
});
await proxyManager.setupProxy();
assert(proxyManager.httpProxy instanceof Proxy);
assert(proxyManager.wsProxy instanceof Proxy);
});
it('should stop proxy', async () => {
const stopSpy = sinon.spy(cb => cb());
proxyManager.wsProxy = {
stop: stopSpy
};
proxyManager.httpProxy = {
stop: stopSpy
};
await proxyManager.stopProxy();
assert(stopSpy.calledTwice);
assert.equal(proxyManager.wsProxy, null);
assert.equal(proxyManager.httpProxy, null);
});
});

View File

@ -0,0 +1,206 @@
import sinon from 'sinon';
import assert from 'assert';
import { fakeEmbark } from 'embark-testing';
import { Proxy } from '../src/proxy';
describe('stack/proxy', () => {
let proxy, embark, mockWs;
beforeEach(() => {
const testBed = fakeEmbark();
embark = testBed.embark;
proxy = new Proxy({
plugins: embark.plugins,
logger: embark.logger,
events: embark.events,
isWs: true
});
mockWs = {
OPEN: 1,
readyState: 1,
on: (ev, cb) => cb(),
send: sinon.spy(() => {})
};
});
afterEach(async () => {
return new Promise(resolve => {
embark.teardown();
sinon.restore();
proxy.stop(resolve);
});
});
describe('instantiation', () => {
it('should register proxy:websocket:subscribe command handler', () => {
embark.events.assert.commandHandlerRegistered('proxy:websocket:subscribe');
});
it('should register proxy:websocket:unsubscribe command handler', () => {
embark.events.assert.commandHandlerRegistered('proxy:websocket:unsubscribe');
});
});
it('should get notified when the connecting node is ready', async () => {
const providerSendSpy = sinon.spy(() => Promise.resolve());
embark.events.setCommandHandler('blockchain:node:provider', (cb) => {
cb(null, {
send: providerSendSpy
});
});
await proxy.nodeReady();
assert(providerSendSpy.calledOnce);
});
it('should emit actions for proxy requests', async () => {
const mockRequest = {
method: 'POST',
body: {
id: 4,
jsonrpc: '2.0',
method: 'test_method'
}
};
const requestAction = sinon.spy((params, cb) => {
params.somethingCustom = true;
cb(null, params);
});
embark.plugins.registerActionForEvent('blockchain:proxy:request', requestAction);
const modifiedRequest = await proxy.emitActionsForRequest(mockRequest, mockWs);
assert(modifiedRequest.isWs);
assert(modifiedRequest.transport);
assert(modifiedRequest.somethingCustom);
assert.equal(modifiedRequest.request, mockRequest);
});
it('should emit actions for proxy responses', async () => {
const mockRequest = {
method: 'POST',
body: {
id: 4,
jsonrpc: '2.0',
method: 'test_method'
}
};
const mockResponse = {
"id": 4,
"jsonrpc": "2.0",
"result": {
"response": "ok"
}
};
const responseAction = sinon.spy((params, cb) => {
params.somethingCustom = true;
cb(null, params);
});
embark.plugins.registerActionForEvent('blockchain:proxy:response', responseAction);
const modifiedResponse = await proxy.emitActionsForResponse(mockRequest, mockResponse, mockWs);
assert(modifiedResponse.isWs);
assert(modifiedResponse.transport);
assert.equal(modifiedResponse.transport, mockWs);
assert(modifiedResponse.somethingCustom);
});
it('should process request and run request and reponse actions', async () => {
const mockRequest = {
method: 'POST',
body: {
id: 2,
jsonrpc: '2.0',
method: 'test_method'
}
};
const mockRPCResponse = {
"id": 2,
"jsonrpc": "2.0",
"result": {
"response": "ok"
}
};
const mockRequestManager = { send: sinon.spy((options, cb) => cb(null, mockRPCResponse)) };
embark.events.setCommandHandler('blockchain:node:provider', (cb) => cb(null, mockRequestManager));
const requestAction = sinon.spy((params, cb) => {
params.sendToNode = false;
cb(null, params);
});
const responseAction = sinon.spy((params, cb) => cb(null, params));
embark.plugins.registerActionForEvent('blockchain:proxy:request', requestAction);
embark.plugins.registerActionForEvent('blockchain:proxy:response', responseAction);
await proxy.processRequest(mockRequest, mockWs);
assert(requestAction.calledOnce);
assert(responseAction.calledOnce);
assert(mockWs.send.calledOnce);
});
it('should forward request to node', async () => {
const mockRequest = {
method: 'POST',
body: {
id: 3,
jsonrpc: '2.0',
method: 'test_method'
}
};
const mockRPCResponse = {
"id": 3,
"jsonrpc": "2.0",
"result": {
"response": "ok"
}
};
const forwardSpy = sinon.spy((options, cb) => cb(null, mockRPCResponse));
const mockRequestManager = { send: forwardSpy };
embark.events.setCommandHandler('blockchain:node:provider', (cb) => cb(null, mockRequestManager));
const requestAction = sinon.spy((params, cb) => {
params.sendToNode = true;
cb(null, params);
});
embark.plugins.registerActionForEvent('blockchain:proxy:request', requestAction);
await proxy.processRequest(mockRequest, mockWs);
assert(forwardSpy.calledOnce);
});
it('should stop the proxy server', () => {
const closeSpy = sinon.spy(cb => cb());
proxy.server = {
close: closeSpy
};
proxy.stop(() => {
assert(true);
});
assert(closeSpy.calledOnce);
assert.equal(proxy.server, null);
});
});

View File

@ -21,6 +21,9 @@
},
{
"path": "../../core/utils"
},
{
"path": "../../utils/testing"
}
]
}