diff --git a/CHANGELOG.md b/CHANGELOG.md index dcc44b85de7..f7f4eb11fa2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -580,6 +580,7 @@ Released with 1.0.0-beta.37 code base. - Add optional hex formatting parameter for getTransactionrReceipt (#5153) - Fix transactionRoot -> transactionsRoot in BlockHeader (#5083) - Fix Promise in Accounts.signTransaction() throwing errors that cannot be caught (#4724) +- Fixed unit tests & removed dead code for web3-providers-http (#5228) ### Security - Updated `got` lib version and fixed other libs using npm audit fix (#5178) (#5254) diff --git a/package.json b/package.json index d189281243d..d1fe647f5b9 100644 --- a/package.json +++ b/package.json @@ -109,6 +109,7 @@ "buffer": "^4.9.2", "bundlesize": "^0.18.0", "chai": "^4.2.0", + "chai-as-promised": "^7.1.1", "clean-webpack-plugin": "^3.0.0", "core-js": "^3.6.5", "crypto-js": "^3.3.0", @@ -116,6 +117,7 @@ "dependency-check": "^4.1.0", "ethereumjs-util": "^7.1.0", "ethers": "^5.4.4", + "fetch-mock": "^9.11.0", "ganache-cli": "^6.12.0", "jshint": "^2.12.0", "karma": "^6.3.19", diff --git a/packages/web3-providers-http/src/index.js b/packages/web3-providers-http/src/index.js index 1daab8c2be4..4169ed62a38 100644 --- a/packages/web3-providers-http/src/index.js +++ b/packages/web3-providers-http/src/index.js @@ -24,11 +24,11 @@ */ var errors = require('web3-core-helpers').errors; -var fetch = require('cross-fetch'); var http = require('http'); var https = require('https'); // Apply missing polyfill for IE +require('cross-fetch/polyfill'); require('es6-promise').polyfill(); require('abortcontroller-polyfill/dist/polyfill-patch-fetch'); @@ -65,21 +65,22 @@ var HttpProvider = function HttpProvider(host, options) { */ HttpProvider.prototype.send = function (payload, callback) { var options = { - method: 'POST', - body: JSON.stringify(payload) + method: 'POST', + body: JSON.stringify(payload) }; var headers = {}; var controller; if (typeof AbortController !== 'undefined') { controller = new AbortController(); - } else if (typeof AbortController === 'undefined' && typeof window !== 'undefined' && typeof window.AbortController !== 'undefined') { + } else if (typeof window !== 'undefined' && typeof window.AbortController !== 'undefined') { // Some chrome version doesn't recognize new AbortController(); so we are using it from window instead // https://stackoverflow.com/questions/55718778/why-abortcontroller-is-not-defined controller = new window.AbortController(); - } else { - // Disable user defined timeout - this.timeout = 0; + } + + if (typeof controller !== 'undefined') { + options.signal = controller.signal; } // the current runtime is node @@ -99,8 +100,8 @@ HttpProvider.prototype.send = function (payload, callback) { } } - if(this.headers) { - this.headers.forEach(function(header) { + if (this.headers) { + this.headers.forEach(function (header) { headers[header.name] = header.value; }); } @@ -112,55 +113,43 @@ HttpProvider.prototype.send = function (payload, callback) { // As the Fetch API supports the credentials as following options 'include', 'omit', 'same-origin' // https://developer.mozilla.org/en-US/docs/Web/API/fetch#credentials - // To avoid breaking change in 1.x we override this value based on boolean option. + // To avoid breaking change in 1.x we override this value based on boolean option. if(this.withCredentials) { options.credentials = 'include'; } else { options.credentials = 'omit'; } - + options.headers = headers; - if (this.timeout > 0) { - this.timeoutId = setTimeout(function() { + if (this.timeout > 0 && typeof controller !== 'undefined') { + this.timeoutId = setTimeout(function () { controller.abort(); }, this.timeout); } - // Prevent global leak of connected - var _this = this; - - var success = function(response) { + var success = function (response) { if (this.timeoutId !== undefined) { clearTimeout(this.timeoutId); } - var result = response; - var error = null; - - try { - // Response is a stream data so should be awaited for json response - result.json().then(function(data) { - result = data; - _this.connected = true; - callback(error, result); - }); - } catch (e) { - _this.connected = false; - callback(errors.InvalidResponse(result)); - } + + // Response is a stream data so should be awaited for json response + response.json().then(function (data) { + callback(null, data); + }).catch(function (error) { + callback(errors.InvalidResponse(response)); + }); }; - var failed = function(error) { + var failed = function (error) { if (this.timeoutId !== undefined) { clearTimeout(this.timeoutId); } if (error.name === 'AbortError') { - _this.connected = false; callback(errors.ConnectionTimeout(this.timeout)); } - _this.connected = false; callback(errors.InvalidConnection(this.host)); } diff --git a/test/httpprovider.js b/test/httpprovider.js index 5f3d23802e0..162e294a9d6 100644 --- a/test/httpprovider.js +++ b/test/httpprovider.js @@ -1,50 +1,52 @@ var chai = require('chai'); +var chaiAsPromised = require('chai-as-promised'); +chai.use(chaiAsPromised); var assert = chai.assert; +var expect = chai.expect; var http = require('http'); var https = require('https'); -var SandboxedModule = require('sandboxed-module'); +var Web3 = require('../packages/web3'); +var HttpProvider = require('../packages/web3-providers-http'); +var fetchMock = require('fetch-mock'); -SandboxedModule.registerBuiltInSourceTransformer('istanbul'); +function isObject(object) { + return object != null && typeof object === 'object'; +} -class Response { - constructor(url, headers) { - this.url = url; - var header = new Map(); - headers.map(function(h) { - header.set(`${h[0].toLowerCase()}`, `${h[1]}`); - }); - this.headers = header; - this.ok = true; - this.status = 200; - this.statusText = 'OK'; - } -}; - -function Mock(url, options) { - const response = new Response(url, Object.entries(options.headers)); - return new Promise(function(resolve, reject) { - resolve(response); - }); -}; - -var HttpProvider = SandboxedModule.require('../packages/web3-providers-http', { - requires: { - 'cross-fetch': Mock, - }, - singleOnly: true -}); +function deepEqual(object1, object2) { + const keys1 = Object.keys(object1); + const keys2 = Object.keys(object2); + if (keys1.length !== keys2.length) { + return false; + } + for (const key of keys1) { + const val1 = object1[key]; + const val2 = object2[key]; + const areObjects = isObject(val1) && isObject(val2); + if (areObjects && !deepEqual(val1, val2) || !areObjects && val1 !== val2) { + return false; + } + } + return true; +} describe('web3-providers-http', function () { describe('prepareRequest', function () { - it('should set request header', async function () { + it('should set request header', function () { var options = {headers: [{name: 'Access-Control-Allow-Origin', value: '*'}]} var provider = new HttpProvider('http://localhost:8545', options); - var origin = 'Access-Control-Allow-Origin'; assert.equal(provider.headers, options.headers); + assert.equal(provider.httpAgent instanceof http.Agent, true); + }); + + it('should have https agent', function () { + var provider = new HttpProvider('https://localhost'); + + assert.equal(provider.httpsAgent instanceof https.Agent, true); }); - it('should use the passed custom http agent', async function () { + it('should use the passed custom http agent', function () { var agent = new http.Agent(); var options = {agent: {http: agent}}; var provider = new HttpProvider('http://localhost:8545', options); @@ -55,7 +57,7 @@ describe('web3-providers-http', function () { assert.equal(provider.agent, options.agent); }); - it('should use the passed custom https agent', async function () { + it('should use the passed custom https agent', function () { var agent = new https.Agent(); var options = {agent: {https: agent}}; var provider = new HttpProvider('http://localhost:8545', options); @@ -65,34 +67,85 @@ describe('web3-providers-http', function () { assert.equal(provider.httpsAgent, undefined); assert.equal(provider.agent, options.agent); }); + }); - /** - Deprecated with xhr2-cookies since this is non-standard + describe('send', function () { + it('should fail with invalid remote node connection', async function () { + var provider = new HttpProvider('http://localhost:8545'); + var web3 = new Web3(provider); - it('should use the passed baseUrl', function () { - agent = new https.Agent(); - options = {agent: {https: agent, baseUrl: 'base'}}; - provider = new HttpProvider('http://localhost:8545', options); - result = provider._prepareRequest(); + await expect(web3.eth.getChainId()).to.be.rejectedWith(Error, "CONNECTION ERROR: Couldn't connect to node http://localhost:8545."); + }); - assert.equal(typeof result, 'object'); - assert.equal(result.agents.httpsAgent, agent); - assert.equal(result.agents.baseUrl, 'base'); - assert.equal(provider.httpAgent, undefined); - assert.equal(provider.httpsAgent, undefined); - assert.equal(provider.agent, options.agent); + it('should fail for non-json format response', async function () { + var provider = new HttpProvider('/fetchMock'); + var web3 = new Web3(provider); + + fetchMock.mock('/fetchMock', 'Testing non-json format response'); + + await expect(web3.eth.getChainId()).to.be.rejectedWith(Error, /Invalid JSON RPC response/); + fetchMock.restore(); }); - **/ - }); - describe('send', function () { - it('should send basic async request', function (done) { - var provider = new HttpProvider(); + it('should timeout by delayed response', async function () { + var provider = new HttpProvider('/fetchMock', { timeout: 500 }); + var web3 = new Web3(provider); + + fetchMock.mock('/fetchMock', 'Testing non-json format response', { delay: 1000 }); + + await expect(web3.eth.getChainId()).to.be.rejectedWith(Error, 'CONNECTION TIMEOUT: timeout of 500 ms achived'); + fetchMock.restore(); + }); + + it('should send basic async request', async function () { + var provider = new HttpProvider('/fetchMock'); + + var reqObject = { + 'jsonrpc': '2.0', + 'id': 0, + 'method': 'eth_chainId', + 'params': [] + }; + + var resObject = { + 'jsonrpc': '2.0', + 'id': 0, + 'result': '0x1' + }; + + fetchMock.mock((url, opts) => { + const reqCount = JSON.parse(opts.body).id; + reqObject = JSON.stringify((() => { + const obj = reqObject; + obj.id = reqCount; + return obj; + })()); + resObject = (() => { + const obj = resObject; + obj.id = reqCount; + return obj; + })(); + const matcher = { + url: '/fetchMock', + method: 'POST', + credentials: 'omit', + headers: { + 'Content-Type': 'application/json' + }, + body: reqObject + }; + return url === matcher.url + && opts.method === matcher.method + && opts.credentials === matcher.credentials + && deepEqual(opts.headers, matcher.headers) + && opts.body === matcher.body; + }, resObject); + + var web3 = new Web3(provider); - provider.send({}, function (err, result) { - assert.equal(typeof result, 'undefined'); - done(); - }); + var chainId = await web3.eth.getChainId(); + assert.equal(chainId, 1); + fetchMock.restore(); }); }); });