From bb06fbc3d0bb17fb625738b2a6f47acb992acdeb Mon Sep 17 00:00:00 2001 From: "nkl199@yahoo.co.uk" Date: Fri, 24 Aug 2018 14:37:29 +0100 Subject: [PATCH] [FAB-11059] unit test for peer - add Peer unit test with 100% code coverage - edit Peer.js to remove linting errors - remove tape unit test for Peer Change-Id: I4af0b13fa5eeffadc402ba39e3b5d77df3512399 Signed-off-by: nkl199@yahoo.co.uk --- fabric-client/lib/Peer.js | 34 ++- fabric-client/test/Peer.js | 479 +++++++++++++++++++++++++++++++++++++ test/unit/peer.js | 237 ------------------ 3 files changed, 500 insertions(+), 250 deletions(-) create mode 100644 fabric-client/test/Peer.js delete mode 100644 test/unit/peer.js diff --git a/fabric-client/lib/Peer.js b/fabric-client/lib/Peer.js index 8197395d7b..1db3781082 100644 --- a/fabric-client/lib/Peer.js +++ b/fabric-client/lib/Peer.js @@ -1,9 +1,16 @@ /* - Copyright 2016, 2018 IBM All Rights Reserved. - - SPDX-License-Identifier: Apache-2.0 - -*/ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ 'use strict'; @@ -25,7 +32,7 @@ const logger = utils.getLogger('Peer.js'); * @class * @extends Remote */ -var Peer = class extends Remote { +const Peer = class extends Remote { /** * Construct a Peer object with the given url and opts. A peer object @@ -88,13 +95,14 @@ var Peer = class extends Remote { } return this.waitForReady(this._endorserClient).then(() => { - return new Promise(function(resolve, reject) { - const send_timeout = setTimeout(function(){ + return new Promise((resolve, reject) => { + const send_timeout = setTimeout(() => { + clearTimeout(send_timeout); logger.error('%s - timed out after:%s', method, rto); return reject(new Error('REQUEST_TIMEOUT')); }, rto); - self._endorserClient.processProposal(proposal, function(err, proposalResponse) { + self._endorserClient.processProposal(proposal, (err, proposalResponse) => { clearTimeout(send_timeout); if (err) { logger.debug('%s - Received proposal response from: %s status: %s', method, self._url, err); @@ -106,7 +114,7 @@ var Peer = class extends Remote { } } else { if (proposalResponse) { - logger.debug('%s - Received proposal response from peer "%s": status - %s', method, self._url, proposalResponse.response.status); + logger.debug('%s - Received proposal response from peer "%s": status - %s', method, self._url, (proposalResponse.response && proposalResponse.response.status) ? proposalResponse.response.status : 'undefined'); // 400 is the error threshold level, anything below that the endorser will endorse it. if (proposalResponse.response && proposalResponse.response.status < 400) { resolve(proposalResponse); @@ -150,13 +158,13 @@ var Peer = class extends Remote { } return this.waitForReady(this._discoveryClient).then(() => { - return new Promise(function(resolve, reject) { - const send_timeout = setTimeout(function(){ + return new Promise((resolve, reject) => { + const send_timeout = setTimeout(() =>{ logger.error('%s - timed out after:%s', method, rto); return reject(new Error('REQUEST_TIMEOUT')); }, rto); - self._discoveryClient.discover(request, function(err, response) { + self._discoveryClient.discover(request, (err, response) => { clearTimeout(send_timeout); if (err) { logger.debug('%s - Received discovery response from: %s status: %s', method, self._url, err); diff --git a/fabric-client/test/Peer.js b/fabric-client/test/Peer.js new file mode 100644 index 0000000000..57321ca61d --- /dev/null +++ b/fabric-client/test/Peer.js @@ -0,0 +1,479 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +const rewire = require('rewire'); +const PeerRewire = rewire('../lib/Peer'); +const Peer = require('../lib/Peer'); + +const chai = require('chai'); +const chaiAsPromised = require('chai-as-promised'); +chai.use(chaiAsPromised); +const sinon = require('sinon'); + +describe('Peer', () => { + + describe('#constructor', () => { + + it('should not permit creation with a non-valid url', () => { + (() => { + new Peer('xxx'); + }).should.throw(/Invalid protocol/); + }); + + it('should not permit creation without an url', () => { + (() => { + new Peer(); + }).should.throw(/Parameter "url" must be a string, not undefined/); + }); + }); + + describe('#close', () => { + it('should call close on the endorser client if it exists', () => { + const obj = new Peer('grpc://host:2700'); + + const mockClose = sinon.stub(); + const mockPC = sinon.stub(); + mockPC.close = mockClose; + + // replace with the mock item + obj._endorserClient = mockPC; + + // call + obj.close(); + + //assert + sinon.assert.called(mockClose); + }); + + it('should call close on the discovery client if it exists', () => { + const obj = new Peer('grpc://host:2700'); + + const mockClose = sinon.stub(); + const mockPC = sinon.stub(); + mockPC.close = mockClose; + + // replace with the mock item + obj._discoveryClient = mockPC; + + // call + obj.close(); + + //assert + sinon.assert.called(mockClose); + }); + }); + + describe('#sendProposal', () => { + + const sandbox = sinon.createSandbox(); + + afterEach(() => { + sandbox.restore(); + }); + + it('should log function entry', () => { + const FakeLogger = { + debug : () => {}, + error: () => {} + }; + + const debugStub = sandbox.stub(FakeLogger, 'debug'); + + PeerRewire.__set__('logger', FakeLogger); + + const obj = new PeerRewire('grpc://host:2700'); + + // this will throw, but we can still check method entry + obj.sendProposal() + .then(() => { + chai.fail(); + }) + .catch(() => { + sinon.assert.called(debugStub); + debugStub.getCall(1).args.should.deep.equal(['%s - Start ----%s %s', 'sendProposal', 'host:2700', 'grpc://host:2700']); + }); + }); + + it('should reject if no proposal', async () => { + const obj = new Peer('grpc://host:2700'); + await obj.sendProposal().should.be.rejectedWith(/Missing proposal to send to peer/); + }); + + it('should reject on timeout', async () => { + PeerRewire.__set__('Peer.prototype.waitForReady', sinon.stub().resolves()); + + function Fake(params, callback) { + setTimeout(() => { callback.call(null,'timeout not honoured'); }, 10); + } + + const endorserClient = sinon.stub(); + endorserClient.processProposal = sinon.stub().callsFake(Fake); + + const obj = new PeerRewire('grpc://host:2700'); + obj._endorserClient = endorserClient; + + await obj.sendProposal('deliver', 0).should.be.rejectedWith(/REQUEST_TIMEOUT/); + }); + + it('should log and reject Error object on proposal repsonse error string', async () => { + + const FakeLogger = { + debug : () => {}, + error: () => {} + }; + + const debugStub = sandbox.stub(FakeLogger, 'debug'); + + PeerRewire.__set__('logger', FakeLogger); + + PeerRewire.__set__('Peer.prototype.waitForReady', sinon.stub().resolves()); + + const endorserClient = sinon.stub(); + + function Fake(params, callback) { + callback.call(null,'i_am_an_error'); + } + + endorserClient.processProposal = sinon.stub().callsFake(Fake); + + const obj = new PeerRewire('grpc://host:2700'); + obj._endorserClient = endorserClient; + + await obj.sendProposal('deliver').should.be.rejectedWith(/i_am_an_error/); + sinon.assert.calledWith(debugStub,'%s - Received proposal response from: %s status: %s'); + }); + + it('should reject Error object on proposal repsonse error object', async () => { + const FakeLogger = { + debug : () => {}, + error: () => {} + }; + + const debugStub = sandbox.stub(FakeLogger, 'debug'); + + PeerRewire.__set__('logger', FakeLogger); + + PeerRewire.__set__('Peer.prototype.waitForReady', sinon.stub().resolves()); + + const endorserClient = sinon.stub(); + + function Fake(params, callback) { + callback.call(null, new Error('FORCED_ERROR')); + } + + endorserClient.processProposal = sinon.stub().callsFake(Fake); + + const obj = new PeerRewire('grpc://host:2700'); + obj._endorserClient = endorserClient; + + await obj.sendProposal('deliver').should.be.rejectedWith(/FORCED_ERROR/); + sinon.assert.calledWith(debugStub,'%s - Received proposal response from: %s status: %s'); + }); + + it('should log and reject on undefined proposal repsonse', async () => { + const FakeLogger = { + debug : () => {}, + error: () => {} + }; + + const errorStub = sandbox.stub(FakeLogger, 'error'); + + PeerRewire.__set__('logger', FakeLogger); + + PeerRewire.__set__('Peer.prototype.waitForReady', sinon.stub().resolves()); + + const endorserClient = sinon.stub(); + + function Fake(params, callback) { + callback.call(null, null, null); + } + + endorserClient.processProposal = sinon.stub().callsFake(Fake); + + const obj = new PeerRewire('grpc://host:2700'); + obj._endorserClient = endorserClient; + + await obj.sendProposal('deliver').should.be.rejectedWith(/GRPC client got a null or undefined response from the peer/); + sinon.assert.calledWith(errorStub,'GRPC client got a null or undefined response from the peer "%s".'); + }); + + it('should log and reject on invalid proposal repsonse', async () => { + const FakeLogger = { + debug : () => {}, + error: () => {} + }; + + const errorStub = sandbox.stub(FakeLogger, 'error'); + const debugStub = sandbox.stub(FakeLogger, 'debug'); + + PeerRewire.__set__('logger', FakeLogger); + + PeerRewire.__set__('Peer.prototype.waitForReady', sinon.stub().resolves()); + + const endorserClient = sinon.stub(); + + function Fake(params, callback) { + callback.call(null, null, {data: 'invalid'}); + } + + endorserClient.processProposal = sinon.stub().callsFake(Fake); + + const obj = new PeerRewire('grpc://host:2700'); + obj._endorserClient = endorserClient; + + await obj.sendProposal('deliver').should.be.rejectedWith(/GRPC client failed to get a proper response from the peer/); + sinon.assert.calledWith(debugStub,'%s - Received proposal response from peer "%s": status - %s'); + sinon.assert.calledWith(errorStub,'GRPC client failed to get a proper response from the peer "%s".'); + }); + + it('should log and reject on proposal repsonse error status greater than or equal to 400', async () => { + const FakeLogger = { + debug : () => {}, + error: () => {} + }; + + const debugStub = sandbox.stub(FakeLogger, 'debug'); + + PeerRewire.__set__('logger', FakeLogger); + + PeerRewire.__set__('Peer.prototype.waitForReady', sinon.stub().resolves()); + + const endorserClient = sinon.stub(); + + function Fake(params, callback) { + callback.call(null, null, {response: {status: 400, message: 'fail_string'}}); + } + + endorserClient.processProposal = sinon.stub().callsFake(Fake); + + const obj = new PeerRewire('grpc://host:2700'); + obj._endorserClient = endorserClient; + + await obj.sendProposal('deliver').should.be.rejectedWith(/fail_string/); + sinon.assert.calledWith(debugStub,'%s - Received proposal response from peer "%s": status - %s'); + }); + + it('should resolve on valid proposal repsonse', async () => { + const FakeLogger = { + debug : () => {}, + error: () => {} + }; + + const debugStub = sandbox.stub(FakeLogger, 'debug'); + + PeerRewire.__set__('logger', FakeLogger); + + PeerRewire.__set__('Peer.prototype.waitForReady', sinon.stub().resolves()); + + const endorserClient = sinon.stub(); + + const myRepsonse = {response: {status: 399, message: 'passed_values'}}; + function Fake(params, callback) { + callback.call(null, null, myRepsonse); + } + + endorserClient.processProposal = sinon.stub().callsFake(Fake); + + const obj = new PeerRewire('grpc://host:2700'); + obj._endorserClient = endorserClient; + + const response = await obj.sendProposal('deliver'); + response.should.deep.equal(myRepsonse); + sinon.assert.calledWith(debugStub,'%s - Received proposal response from peer "%s": status - %s'); + }); + + }); + + describe('#sendDiscovery', () => { + const sandbox = sinon.createSandbox(); + + afterEach(() => { + sandbox.restore(); + }); + + it('should log on entry', async () => { + const FakeLogger = { + debug : () => {}, + error: () => {} + }; + + const debugStub = sandbox.stub(FakeLogger, 'debug'); + + PeerRewire.__set__('logger', FakeLogger); + + const obj = new PeerRewire('grpc://host:2700'); + + // this will throw, but we can still check method entry + obj.sendDiscovery() + .then(() => { + chai.fail(); + }) + .catch(() => { + sinon.assert.called(debugStub); + debugStub.getCall(1).args.should.deep.equal(['%s - Start', 'sendDiscovery']); + }); + }); + + it('should reject if no request to send', async () => { + const obj = new Peer('grpc://host:2700'); + await obj.sendDiscovery().should.be.rejectedWith(/Missing request to send to peer discovery service/); + }); + + it('should log and reject on timeout', async () => { + const FakeLogger = { + debug : () => {}, + error: () => {} + }; + + const errorStub = sandbox.stub(FakeLogger, 'error'); + + PeerRewire.__set__('logger', FakeLogger); + + PeerRewire.__set__('Peer.prototype.waitForReady', sinon.stub().resolves()); + + function Fake(params, callback) { + setTimeout(() => { callback.call(null,'timeout not honoured'); }, 10); + } + + const discoveryClient = sinon.stub(); + discoveryClient.discover = sinon.stub().callsFake(Fake); + + const obj = new PeerRewire('grpc://host:2700'); + obj._discoveryClient = discoveryClient; + + await obj.sendDiscovery('deliver', 0).should.be.rejectedWith(/REQUEST_TIMEOUT/); + sinon.assert.calledWith(errorStub,'%s - timed out after:%s'); + }); + + it('should log and reject Error object on discover repsonse error string', async () => { + + const FakeLogger = { + debug : () => {}, + error: () => {} + }; + + const debugStub = sandbox.stub(FakeLogger, 'debug'); + + PeerRewire.__set__('logger', FakeLogger); + + PeerRewire.__set__('Peer.prototype.waitForReady', sinon.stub().resolves()); + + function Fake(params, callback) { + callback.call(null,'i_am_an_error'); + } + + const discoveryClient = sinon.stub(); + discoveryClient.discover = sinon.stub().callsFake(Fake); + + const obj = new PeerRewire('grpc://host:2700'); + obj._discoveryClient = discoveryClient; + + await obj.sendDiscovery('deliver').should.be.rejectedWith(/i_am_an_error/); + sinon.assert.calledWith(debugStub,'%s - Received discovery response from: %s status: %s'); + }); + + it('should log and reject Error object on discover repsonse error object', async () => { + + const FakeLogger = { + debug : () => {}, + error: () => {} + }; + + const debugStub = sandbox.stub(FakeLogger, 'debug'); + + PeerRewire.__set__('logger', FakeLogger); + + PeerRewire.__set__('Peer.prototype.waitForReady', sinon.stub().resolves()); + + function Fake(params, callback) { + callback.call(null, new Error('FORCED_ERROR')); + } + + const discoveryClient = sinon.stub(); + discoveryClient.discover = sinon.stub().callsFake(Fake); + + const obj = new PeerRewire('grpc://host:2700'); + obj._discoveryClient = discoveryClient; + + await obj.sendDiscovery('deliver').should.be.rejectedWith(/FORCED_ERROR/); + sinon.assert.calledWith(debugStub,'%s - Received discovery response from: %s status: %s'); + }); + + it('should log and reject Error object on null response from discover', async () => { + + const FakeLogger = { + debug : () => {}, + error: () => {} + }; + + const errorStub = sandbox.stub(FakeLogger, 'error'); + + PeerRewire.__set__('logger', FakeLogger); + + PeerRewire.__set__('Peer.prototype.waitForReady', sinon.stub().resolves()); + + function Fake(params, callback) { + callback.call(null, null, null); + } + + const discoveryClient = sinon.stub(); + discoveryClient.discover = sinon.stub().callsFake(Fake); + + const obj = new PeerRewire('grpc://host:2700'); + obj._discoveryClient = discoveryClient; + + await obj.sendDiscovery('deliver').should.be.rejectedWith(/GRPC client failed to get a proper response from the peer/); + sinon.assert.calledWith(errorStub,'GRPC client failed to get a proper response from the peer "%s".'); + }); + + it('should log and resolve on good response from discover', async () => { + + const FakeLogger = { + debug : () => {}, + error: () => {} + }; + + const debugStub = sandbox.stub(FakeLogger, 'debug'); + + PeerRewire.__set__('logger', FakeLogger); + PeerRewire.__set__('Peer.prototype.waitForReady', sinon.stub().resolves()); + + const myResponse = {me: 'valid'}; + function Fake(params, callback) { + callback.call(null, null, myResponse); + } + + const discoveryClient = sinon.stub(); + discoveryClient.discover = sinon.stub().callsFake(Fake); + + const obj = new PeerRewire('grpc://host:2700'); + obj._discoveryClient = discoveryClient; + + const repsonse = await obj.sendDiscovery('deliver'); + repsonse.should.deep.equal(myResponse); + sinon.assert.calledWith(debugStub,'%s - Received discovery response from peer "%s"'); + }); + + }); + + describe('#toString', () => { + + it('should return a string representation of the object', () => { + const obj = new Peer('grpc://host:2700'); + obj.toString().should.equal('Peer:{url:grpc://host:2700}'); + }); + }); +}); \ No newline at end of file diff --git a/test/unit/peer.js b/test/unit/peer.js deleted file mode 100644 index 360d46048d..0000000000 --- a/test/unit/peer.js +++ /dev/null @@ -1,237 +0,0 @@ -/** - * Copyright 2017 IBM All Rights Reserved. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -const tape = require('tape'); -const _test = require('tape-promise').default; -const test = _test(tape); - -const Client = require('fabric-client'); -const testUtil = require('./util.js'); - -const Peer = require('fabric-client/lib/Peer.js'); - -test('Peer test', (t) => { - const peer = new Peer('grpc://127.0.0.1:5005'); - - t.doesNotThrow( - () => { - peer.setName('name'); - peer.close(); - }, - 'checking the peer setName() and close()' - ); - t.equals('name', peer.getName(), 'checking getName on Peer'); - - t.end(); -}); - -// -// Peer happy path test are implemented as part of the end-to-end tests only -// because the Peer no longer accepts random data but requires all the payload -// header structure, making it impractical to carry out a happy path test outside -// of a proposal-transaction flow - -// -// Peer bad address test -// -// Attempt to initialize an Peer with a bad URL address. An invalid protocol -// error is expected in this case. -// - -test('Peer bad address test', (t) => { - testUtil.resetDefaults(); - - try { - new Peer('xxxxx'); - t.fail('Peer allowed setting a bad URL.'); - } - catch(err) { - t.pass('peer did not allow setting bad URL.'); - } - t.end(); -}); - -// -// Peer missing address test -// -// Attempt to initialize an Peer with a missing URL address. A TypeError -// indicating that the URL must be a "string" is expected in this case. -// - -test('Peer missing address test', (t) => { - try { - new Peer(); - t.fail('Peer allowed setting a missing address.'); - } - catch(err) { - t.pass('Peer did not allow setting a missing address.'); - } - t.end(); -}); - -// -// Peer missing data test -// -// Send an empty broadcast message to an Peer. An error indicating that no -// data was sent is expected in this case. -// - -test('Peer missing data test', (t) => { - const peer = new Peer('grpc://127.0.0.1:5005'); - peer.sendProposal() - .then( - () => { - t.fail('Should have noticed missing data.'); - }, - (err) => { - t.pass('Successfully found missing data: ' + err); - peer.close(); - } - ).catch((err) => { - t.fail('Caught Error: should not be here if we defined promise error function: ' + err); - }); - peer.sendDiscovery() - .then( - () => { - t.fail('Should have noticed missing discovery data.'); - t.end(); - }, - (err) => { - t.pass('Successfully found missing discovery data: ' + err); - peer.close(); - t.end(); - } - ).catch((err) => { - t.fail('Caught Error: should not be here if we defined discovery promise error function: ' + err); - t.end(); - }); -}); - -// -// Peer unknown address test -// -// Send a message to a bad Peer address. An error indicating -// a connection failure is expected in this case. -// - -test('Peer unknown address test', (t) => { - const peer = new Peer('grpc://127.0.0.1:51006'); - - peer.sendProposal('some data') - .then( - () => { - t.fail('Should have noticed a bad address.'); - }, - (err) => { - t.equal(err.message, 'Failed to connect before the deadline', - 'sendProposal to unreachable peer should response connection failed'); - t.pass('Successfully found bad address!' + err); - } - ).catch((err) => { - t.fail('Caught Error: should not be here if we defined promise error function: ' - + err); - }); - - peer.sendDiscovery('some data') - .then( - () => { - t.fail('Should have noticed a bad address.'); - t.end(); - }, - (err) => { - t.equal(err.message, 'Failed to connect before the deadline', - 'sendProposal to unreachable peer should response connection failed'); - t.pass('Successfully found bad address!' + err); - t.end(); - } - ).catch((err) => { - t.fail('Caught Error: should not be here if we defined promise error function: ' - + err); - t.end(); - }); -}); - -// -//Peer timeout test -// -//Send a message to a running Peer address. An error indicating -//a timeout failure is expected in this case. -// - -test('Peer timeout test', (t) => { - // this does require a running network. This test does not really - // test the timeout, but does show that it does not cause any issues - let peer = new Peer('grpc://localhost:7051'); - - peer.sendProposal('some data', 1) - .then( - () => { - t.fail('Should have noticed a timeout.'); - }, - (err) => { - t.pass('Successfully got the timeout' + err); - } - ).catch((err) => { - t.fail('Caught Error: should not be here if we defined promise error function: ' - + err); - }); - peer.sendDiscovery('some data', 1) - .then( - () => { - t.fail('Should have noticed a discovery timeout.'); - }, - (err) => { - t.pass('Successfully got the discovery timeout' + err); - } - ).catch((err) => { - t.fail('Caught Error: should not be here if we defined discovery promise error function: ' - + err); - }); - - const backup = Client.getConfigSetting('request-timeout'); - Client.setConfigSetting('request-timeout', 1); - peer = new Peer('grpc://localhost:7051'); - - peer.sendProposal('some data') - .then( - () => { - t.fail('Should have noticed a timeout.'); - }, - (err) => { - t.pass('Successfully got the timeout' + err); - } - ).catch((err) => { - t.fail('Caught Error: should not be here if we defined promise error function: ' - + err); - }); - - peer.sendDiscovery('some data') - .then( - () => { - t.fail('Should have noticed a discovery timeout.'); - t.end(); - }, - (err) => { - t.pass('Successfully got the discovery timeout' + err); - t.end(); - } - ).catch((err) => { - t.fail('Caught Error: should not be here if we defined discovery promise error function: ' - + err); - t.end(); - }); - - // put back the setting - Client.setConfigSetting('request-timeout', backup); -}); - -test('Peer clientCert est', (t) => { - const peer = new Peer('grpc://127.0.0.1:5005', {clientCert: 'some cert'}); - - t.equals('some cert', peer.clientCert, 'checking client certificate on peer'); - - t.end(); -});