From 4b6b3b175f676d29e410bfc55fe50297d73413ca Mon Sep 17 00:00:00 2001 From: Bret Harrison Date: Sun, 3 Feb 2019 21:47:43 -0500 Subject: [PATCH] FAB-1117 NodeSDK event hub connect Allow the connect to have a start block and also be able to take a new peer target. Add the reconnect to allow the application to restart the connectioin and request the start block to be the last block this event hub has seen. Restructure the testing to isolate all parts to avoid conflicts between the different tools. Change-Id: I53bac25dd5c5e295b7cb94898a9a7f504cfd6fd2 Signed-off-by: Bret Harrison --- build/tasks/test.js | 42 ++- fabric-client/lib/Channel.js | 15 +- fabric-client/lib/ChannelEventHub.js | 294 ++++++++++----- fabric-client/test/ChannelEventHub.js | 495 +++++++++++++++++++++----- package.json | 2 +- scripts/Jenkins_Scripts/CI_Script.sh | 13 +- test/integration/channel-event-hub.js | 385 +++++++++++++++++++- test/unit/commit-handler.js | 35 +- 8 files changed, 1072 insertions(+), 209 deletions(-) diff --git a/build/tasks/test.js b/build/tasks/test.js index 61c3368158..a5a1ceb5fc 100644 --- a/build/tasks/test.js +++ b/build/tasks/test.js @@ -125,18 +125,23 @@ gulp.task('compile', shell.task([ // - Use nyc instead of gulp-istanbul to generate coverage report // - Cannot use gulp-istabul because it throws "unexpected identifier" for async/await functions -// Main test to run all tests -gulp.task('test', shell.task('npx nyc gulp run-test')); - // Test to run all unit tests -gulp.task('test-headless', shell.task('npx nyc gulp run-test-headless')); +gulp.task('test-headless', shell.task('npx nyc --check-coverage --lines 50 --statements 50 --functions 50 --branches 50 gulp run-test-headless')); + +// Test to run only tape integration test +gulp.task('test-integration', shell.task('npx nyc --check-coverage --lines 50 --statements 50 --functions 50 --branches 50 gulp run-test-integration')); + +// Test to run only tape integration test +gulp.task('test-cucumber', shell.task('npx nyc gulp run-test-cucumber')); + +// Test to run only logger test +gulp.task('test-logger', shell.task('npx nyc gulp run-test-logger')); + +// ------------------------------------------------------------------------- // Only run Mocha unit tests gulp.task('test-mocha', shell.task('npx nyc gulp run-test-mocha')); -// Only run scenario tests -gulp.task('test-cucumber', shell.task('npx nyc npm run test:cucumber')); - // Definition of Mocha (unit) test suites gulp.task('run-test-mocha', (done) => { const tasks = ['mocha-fabric-ca-client', 'mocha-fabric-client', 'mocha-fabric-network']; @@ -164,9 +169,6 @@ gulp.task('mocha-fabric-network', } ); -// Test to run all unit tests -gulp.task('test-tape', shell.task('npx nyc gulp run-tape-unit')); - // Definition of Cucumber (scenario) test suite gulp.task('run-test-cucumber', shell.task( 'export HFC_LOGGING=""; npm run test:cucumber' @@ -175,13 +177,27 @@ gulp.task('run-test-cucumber', shell.task( // Main test method to run all test suites // - lint, unit first, then FV, then scenario gulp.task('run-test', (done) => { - const tasks = ['clean-up', 'docker-clean', 'pre-test', 'ca', 'compile', 'lint', 'run-test-mocha', 'run-tape-unit', 'run-tape-e2e', 'run-logger-unit', 'docker-clean', 'run-test-cucumber']; + const tasks = ['clean-up', 'docker-clean', 'pre-test', 'ca', 'compile', 'lint', 'run-test-mocha', 'run-tape-unit', 'run-tape-e2e', 'run-test-logger', 'docker-clean', 'run-test-cucumber']; + runSequence(...tasks, done); +}); + +// Main test method to run all test suites +// - lint, unit first, then FV, then scenario +gulp.task('run-test-integration', (done) => { + const tasks = ['clean-up', 'docker-clean', 'pre-test', 'ca', 'lint', 'run-tape-e2e']; + runSequence(...tasks, done); +}); + +// Main test method to run all test suites +// - lint, unit first, then FV, then scenario +gulp.task('test-cucumber', (done) => { + const tasks = ['clean-up', 'docker-clean', 'run-test-cucumber']; runSequence(...tasks, done); }); // Run all non-integration tests gulp.task('run-test-headless', (done) => { - const tasks = ['clean-up', 'pre-test', 'ca', 'lint', 'run-test-mocha', 'run-tape-unit', 'run-logger-unit']; + const tasks = ['clean-up', 'pre-test', 'ca', 'lint', 'compile', 'run-test-mocha', 'run-tape-unit']; runSequence(...tasks, done); }); @@ -203,7 +219,7 @@ gulp.task('run-tape-unit', }); // Run logger in isolation -gulp.task('run-logger-unit', +gulp.task('run-test-logger', () => { // this is needed to avoid a problem in tape-promise with adding // too many listeners to the "unhandledRejection" event diff --git a/fabric-client/lib/Channel.js b/fabric-client/lib/Channel.js index ae36888f8c..7059f57cdf 100755 --- a/fabric-client/lib/Channel.js +++ b/fabric-client/lib/Channel.js @@ -949,14 +949,19 @@ const Channel = class { * This method will create a new ChannelEventHub and not save a reference. * Use the {getChannelEventHub} to reuse a ChannelEventHub. * - * @param {Peer | string} peer A Peer instance or the name of a peer that has - * been assigned to the channel. + * @param {Peer | string} peer Optional. A Peer instance or the name of a + * peer that has been assigned to the channel. If not provided the + * ChannelEventHub must be connected with a "target" peer. * @returns {ChannelEventHub} The ChannelEventHub instance */ newChannelEventHub(peer) { - // Will always return one or throw - const peers = this._getTargets(peer, Constants.NetworkConfig.EVENT_SOURCE_ROLE, true); - const channel_event_hub = new ChannelEventHub(this, peers[0]); + let _peer = null; + if (peer) { + const peers = this._getTargets(peer, Constants.NetworkConfig.EVENT_SOURCE_ROLE, true); + _peer = peers[0]; + } + const channel_event_hub = new ChannelEventHub(this, _peer); + return channel_event_hub; } diff --git a/fabric-client/lib/ChannelEventHub.js b/fabric-client/lib/ChannelEventHub.js index 207026eb75..e7edd91502 100644 --- a/fabric-client/lib/ChannelEventHub.js +++ b/fabric-client/lib/ChannelEventHub.js @@ -16,6 +16,7 @@ const Long = require('long'); const utils = require('./utils.js'); const clientUtils = require('./client-utils.js'); +const Constants = require('./Constants.js'); const logger = utils.getLogger('ChannelEventHub.js'); const {Identity} = require('./msp/identity'); const TransactionID = require('./TransactionID'); @@ -56,7 +57,9 @@ const five_minutes_ms = 5 * 60 * 1000; const ALL = 'all'; // Special value for block numbers -const NEWEST = 'newest'; +const NEWEST = 'newest'; // what fabric peer sees as newest on the ledger at time of connect +const OLDEST = 'oldest'; // what fabric peer sees as oldest on the ledger at time of connect +const LAST_SEEN = 'last_seen'; // what this event hub sees as the last block received /** * Transaction processing in fabric v1.1 is a long operation spanning multiple @@ -114,7 +117,7 @@ const NEWEST = 'newest'; * // is an error callback ready to process an error in case the * // connect() call fails * eh.registerTxEvent( - * 'all', // this listener will be notificed of all transactions + * 'all', // this listener will be notified of all transactions * (tx, status, block_num) => { * record(tx, status, block_num); * console.log(util.format('Transaction %s has completed', tx)); @@ -139,7 +142,7 @@ class ChannelEventHub { * * @param {Channel} channel - An instance of the Channel class * were this ChannelEventHub will receive blocks - * @param {Peer} peer An instance of the Peer class this ChannelEventHub connects. + * @param {Peer} peer Optional. An instance of the Peer class this ChannelEventHub connects. * @returns {ChannelEventHub} An instance of this class */ @@ -184,9 +187,6 @@ class ChannelEventHub { this._channel = channel; // peer node to connect // reference to the peer instance holding end point information - if (!peer) { - throw new Error('Missing required argument: peer'); - } this._peer = peer; } @@ -225,11 +225,10 @@ class ChannelEventHub { /* * internal method to check if this ChannelEventHub is allowing new event listeners * If this ChannelEventHub has been configured for a startBlock/endBlock of events then - * only one event listener is allowed. Once the connect has been called no - * new event listener will be allowed. + * only one event listener is allowed. */ _checkAllowRegistrations() { - if (!this._allowRegistration) { + if (this._start_stop_registration) { throw new Error('This ChannelEventHub is not open to event listener registrations'); } } @@ -250,7 +249,7 @@ class ChannelEventHub { /** * @typedef {Object} ConnectOptions - * @property {boolean} full_block - Optional. to indicated that the connection + * @property {boolean} full_block - Optional. To indicate that the connection * with the peer will be sending full blocks or filtered blocks to this * ChannelEventHub. * The default will be to establish a connection using filtered blocks. @@ -261,36 +260,69 @@ class ChannelEventHub { * receive full blocks. * Registering a block listener on a filtered block connection may not * provide sufficient information. - * @property {SignedEvent} signedEvent - Optional. the signed event to be sent + * @property {Number | string} startBlock - Optional. This will have the connection + * setup to start sending blocks back to the event hub at the block + * with this number. If connecting with a + * a startBlock then event listeners may not be registered with a + * startBlock or endBlock. + * If the event hub should start with the last block it has seen + * use the string 'last_seen'. + * If the event hub should start with the oldest block on the + * ledger use the string 'oldest'. + * If the event hub should start with the latest block on the ledger, + * use the string 'latest' or do use a startBlock. + * Default is to start with the latest block on the ledger. + * @property {Number | string} endBlock - Optional. This will have the connection + * setup to end sending blocks back to the event hub at the block + * with this number. If connecting with a + * a endBlock then event listeners may not be registered with a + * startBlock or endBlock. + * If the event hub should end with the last block it has seen + * use the string 'last_seen'. + * If the event hub should end with the current block on the + * ledger use the string 'newest'. + * Default is to not stop sending. + * @property {SignedEvent} signedEvent - Optional. The signed event to be sent * to the peer. This option is useful when the fabric-client application * does not have the user's privateKey and can not sign requests to the * fabric network. + * @property {Peer | string} target - Optional. The peer that provides the + * fabric event service. When using a string, the {@link Channel} + * must have a peer assigned with that name. This peer will replace + * the current peer endpoint of this channel event hub. */ /** * Establishes a connection with the fabric peer service. * * The connection will be established asynchronously. If the connection fails to - * get established, the application will be notified via the error callbacks - * from the registerXXXEvent() methods. It is recommended that an application always - * register at least one event listener with an error callback, by calling any one of the + * get established, the application will be notified via the 'connectCallback' + * provided. Additionally the error callbacks from the registerXXXEvent() methods + * will be notified if provided. + * It is recommended that an application relay on 'connectCallback' to determine + * connect status and relay on the 'errCallback' of the event listeners for + * runtime connection issues. + * Register event listeners and the error callbacks by calling any one of the * [registerBlockEvent]{@link ChannelEventHub#registerBlockEvent}, * [registerTxEvent]{@link ChannelEventHub#registerTxEvent} or * [registerChaincodeEvent]{@link ChannelEventHub#registerChaincodeEvent} - * methods, before calling connect(). + * methods, after calling connect(). * * @param {ConnectOptions | boolean} options - Optional. If of type boolean * then it will be assumed to how to connect to receive full (true) * or filtered (false) blocks. - * @param {functon} connectCallback - Optional. This callback will report + * @param {function} connectCallback - Optional. This callback will report * completion of the connection to the peer or will report * any errors encountered during connection to the peer. When there * is an error, this ChannelEventHub will be shutdown (disconnected). * Callback function should take two parameters as (error, value). */ connect(options, connectCallback) { + const method = 'connect'; + logger.debug('%s - start', method); let signedEvent = null; let full_block = null; + const connect_request = {}; // the following supports the users using the boolean parameter to control // how to connect to the fabric service for full or filtered blocks @@ -300,29 +332,71 @@ class ChannelEventHub { if (typeof options === 'object' && options !== null) { signedEvent = options.signedEvent || null; full_block = options.full_block || null; + + if (typeof options.force === 'boolean') { + connect_request.force = options.force; + } + if (typeof options.startBlock === 'undefined' || options.startBlock === null) { + logger.debug('%s - options do not include startBlock', method); + } else { + connect_request.startBlock = options.startBlock; + logger.debug('%s - options include startBlock of %s', method, options.startBlock); + } + if (typeof options.endBlock === 'undefined' || options.endBlock === null) { + logger.debug('%s - options do not include endBlock', method); + } else { + connect_request.endBlock = options.endBlock; + logger.debug('%s - options include endBlock of %s', method, options.endBlock); + } + if (!options.target) { + logger.debug('%s - options do not include a target', method); + } else { + this._assignPeer(options.target); + logger.debug('%s - options include a target', method); + } } if (signedEvent) { - signedEvent = this._validateSignedEvent(signedEvent); + connect_request.signedEvent = this._validateSignedEvent(signedEvent); } if (connectCallback) { + logger.debug('%s - using a connect callback', method); this.connectCallback = connectCallback; } - logger.debug('connect - start peerAddr:%s', this.getPeerAddr()); + logger.debug('%s - start peerAddr:%s', method, this.getPeerAddr()); if (!this._clientContext._userContext && !this._clientContext._adminSigningIdentity && !signedEvent) { throw new Error('Error connect the ChannelEventhub to peer, either the clientContext has not been properly initialized, missing userContext or admin identity or missing signedEvent'); } if (typeof full_block === 'boolean') { this._filtered_stream = !full_block; - logger.debug('connect - filtered block stream set to:%s', !full_block); + logger.debug('%s - filtered block stream set to:%s', method, !full_block); } else { - logger.debug('connect - using a filtered block stream by default'); + logger.debug('%s - using a filtered block stream by default', method); } - logger.debug('connect - signed event:%s', !!signedEvent); - this._connect({signedEvent}); - logger.debug('connect - end %s', this.getPeerAddr()); + logger.debug('%s - signed event:%s', method, !!signedEvent); + this._connect(connect_request); + logger.debug('%s - end %s', method, this.getPeerAddr()); + } + + /** + * Reestablishes a connection with the fabric peer service. + + * + * @param {ConnectOptions} options - Optional. + * @param {function} connectCallback - Optional. This callback will report + * completion of the connection to the peer or will report + * any errors encountered during connection to the peer. When there + * is an error, this ChannelEventHub will be shutdown (disconnected). + * Callback function should take two parameters as (error, value). + */ + reconnect(options, connectCallback) { + const method = 'reconnect'; + logger.debug('%s - start', method); + const re_options = Object.assign({force: true}, options); + + this.connect(re_options, connectCallback); } /* @@ -332,7 +406,6 @@ class ChannelEventHub { * @property {SignedEvent} signedEvent - Optional. the signed event to be send to peer */ - /* * Internal use only * Establishes a connection with the fabric peer service @@ -361,13 +434,23 @@ class ChannelEventHub { // clean up this._shutdown(); + if (this._start_stop_connect) { + logger.debug('_connect - reset the start stop settings'); + this._start_stop_connect = false; + this._starting_block_number = null; + this._ending_block_number = null; + this._ending_block_newest = false; + this._ending_block_seen = false; + } + + this._checkReplay(request, true); this._connect_running = true; this._current_stream++; const stream_id = this._current_stream; logger.debug('_connect - start stream:', stream_id); const self = this; // for callback context - const connecton_setup_timeout = setTimeout(() => { + const connection_setup_timeout = setTimeout(() => { logger.error('_connect - timed out after:%s', self._peer._request_timeout); self._connect_running = false; self._disconnect(new Error('Unable to connect to the fabric peer service')); @@ -392,7 +475,7 @@ class ChannelEventHub { this._stream.on('data', (deliverResponse) => { if (self._connect_running) { self._connect_running = false; - clearTimeout(connecton_setup_timeout); + clearTimeout(connection_setup_timeout); } logger.debug('on.data - block stream:%s _current_stream:%s peer:%s', stream_id, self._current_stream, self.getPeerAddr()); @@ -409,6 +492,7 @@ class ChannelEventHub { logger.debug('on.data - first block received , this ChannelEventHub now registered'); self._connected = true; if (this.connectCallback) { + logger.debug('_connect - call the connection callback'); this.connectCallback(null, this); // return this instance, user will be able check with isconnected() this.connectCallback = null; // clean up so not called again } @@ -465,7 +549,7 @@ class ChannelEventHub { this._stream.on('end', () => { self._connect_running = false; - clearTimeout(connecton_setup_timeout); + clearTimeout(connection_setup_timeout); logger.debug('on.end - event stream:%s _current_stream:%s peer:%s', stream_id, self._current_stream, self.getPeerAddr()); if (stream_id !== self._current_stream) { logger.debug('on.end - incoming message was from a canceled stream'); @@ -478,10 +562,10 @@ class ChannelEventHub { this._stream.on('error', (err) => { self._connect_running = false; - clearTimeout(connecton_setup_timeout); + clearTimeout(connection_setup_timeout); logger.debug('on.error - block stream:%s _current_stream:%s peer:%s', stream_id, self._current_stream, self.getPeerAddr()); if (stream_id !== self._current_stream) { - logger.debug('on.error - incoming message was from a canceled stream'); + logger.debug('on.error - incoming message was from a cancelled stream'); logger.debug('on.error - %s %s', new Date(), err); return; } @@ -536,7 +620,6 @@ class ChannelEventHub { */ _disconnect(err) { logger.debug('_disconnect - start -- called due to:: %s, peer:%s', err.message, this.getPeerAddr()); - this._connected = false; this._connect_running = false; this._closeAllCallbacks(err); this._shutdown(); @@ -565,6 +648,7 @@ class ChannelEventHub { if (this._event_client) { this._event_client.close(); } + this._connected = false; } /* @@ -685,23 +769,32 @@ class ChannelEventHub { // The behavior when a missing block is encountered let behavior = _abProto.SeekInfo.SeekBehavior.BLOCK_UNTIL_READY; + // build start const seekStart = new _abProto.SeekPosition(); - if (this._starting_block_number) { + if (!this._starting_block_number || this._starting_block_number === NEWEST) { + const seekNewest = new _abProto.SeekNewest(); + seekStart.setNewest(seekNewest); + } else if (this._starting_block_number === OLDEST) { + const seekOldest = new _abProto.SeekOldest(); + seekStart.setOldest(seekOldest); + } else if (this._starting_block_number) { const seekSpecifiedStart = new _abProto.SeekSpecified(); seekSpecifiedStart.setNumber(this._starting_block_number); seekStart.setSpecified(seekSpecifiedStart); - } else { - const seekNewest = new _abProto.SeekNewest(); - seekStart.setNewest(seekNewest); } // build stop const seekStop = new _abProto.SeekPosition(); - if (this._ending_block_newest) { + if (this._ending_block_number === NEWEST) { + this._ending_block_newest = true; const seekNewest = new _abProto.SeekNewest(); seekStop.setNewest(seekNewest); behavior = _abProto.SeekInfo.SeekBehavior.FAIL_IF_NOT_READY; + } else if (this._ending_block_number === OLDEST) { + const seekOldest = new _abProto.SeekOldest(); + seekStop.setOldest(seekOldest); + behavior = _abProto.SeekInfo.SeekBehavior.FAIL_IF_NOT_READY; } else { const seekSpecifiedStop = new _abProto.SeekSpecified(); if (this._ending_block_number) { @@ -794,67 +887,109 @@ class ChannelEventHub { logger.debug('%s - end', method); } + _assignPeer(peer) { + const method = 'assignPeer'; + logger.debug('%s - start', method); + + const peers = this._channel._getTargets(peer, Constants.NetworkConfig.EVENT_SOURCE_ROLE, true); + this._peer = peers[0]; + + logger.debug('%s - reassigning new peer for this channel event hub %s,', method, this.getPeerAddr()); + } + + _checkBlockNum(block_num) { + let _block_num = null; + if (typeof block_num === 'string') { + if (block_num.toLowerCase() === LAST_SEEN) { + // set to last seen even if last seen is null + _block_num = this._last_block_seen; + } else if (block_num.toLowerCase() === OLDEST) { + _block_num = OLDEST; + } else if (block_num.toLowerCase() === NEWEST) { + _block_num = NEWEST; + } else { + // maybe it is a string number + _block_num = utils.convertToLong(block_num); + } + } else { + if (typeof block_num !== 'undefined' && block_num !== null) { + _block_num = utils.convertToLong(block_num); + } + } + + return _block_num; + } + + _checkEndBlock(endBlock, startBlock) { + const _endBlock = this._checkBlockNum(endBlock); + + if (_endBlock instanceof Long && startBlock instanceof Long) { + if (startBlock.greaterThan(_endBlock)) { + throw new Error(util.format('"startBlock" (%s) must not be greater than "endBlock" (%s)', startBlock.toInt(), _endBlock.toInt())); + } + } + + return _endBlock; + } /* * Internal method * checks the startBlock/endBlock options * checks that only one registration when using startBlock/endBlock + * checks that startBlock has been set during connect, then not allow + * registration with startBlock/endBlock * @returns enum of how the endBlock and startBlock have been set */ - _checkReplay(options) { + _checkReplay(options, fromConnect) { logger.debug('_checkReplay - start'); let result = NO_START_STOP; - let have_start_block = false; - let have_end_block = false; - const converted_options = {}; - if (options && typeof options.startBlock !== 'undefined') { - try { - converted_options.start_block = utils.convertToLong(options.startBlock); - have_start_block = true; - } catch (error) { - throw new Error('Problem with the startBlock parameter ::' + error); - } - } - if (options && typeof options.endBlock !== 'undefined') { - try { - let end_block = options.endBlock; - if (typeof end_block === 'string') { - if (end_block.toLowerCase() === NEWEST) { - end_block = Long.MAX_VALUE; - this._ending_block_newest = true; - } - } - converted_options.end_block = utils.convertToLong(end_block); - have_end_block = true; - } catch (error) { - throw new Error('Problem with the endBlock parameter ::' + error); - } - } + let _start_block = null; + let _end_block = null; - if ((have_start_block || have_end_block) && this._haveRegistrations()) { - logger.error('This ChannelEventHub is already registered with active listeners. Not able options of startBlock:%s endBlock:%s', options.startBlock, options.endBlock); - throw new Error('Only one event registration is allowed when startBlock or endBlock are used'); + if (options) { + _start_block = this._checkBlockNum(options.startBlock); + _end_block = this._checkEndBlock(options.endBlock, _start_block); } - if ((have_start_block || have_end_block) && (this._connected || this._connect_running)) { - logger.error('This ChannelEventHub has already been connected to start receiving blocks. Not able to use options of startBlock:%s endBlock:%s', options.startBlock, options.endBlock); - throw new Error('Event listeners that use startBlock or endBlock must be registered before connecting to the peer channel-based service'); + if (_start_block || _end_block) { + if (fromConnect) { + if (this._start_stop_registration) { + logger.error('This ChannelEventHub has a registered listener that has options of startBlock or endBlock'); + throw new Error('Not able to connect with startBlock or endBlock when a registered listener has those options.'); + } + } else { + if (this._haveRegistrations()) { + logger.error('This ChannelEventHub is already registered with active listeners.'); + throw new Error('Only one event registration is allowed when startBlock or endBlock are used'); + } + if (this._start_stop_connect) { + logger.error('This ChannelEventHub has been connected with a startBlock or endBlock'); + throw new Error('Registrations with startBlock or endBlock are not allowed if this ChannelEventHub is connected with a startBlock or endBlock'); + } + if (this._connected || this._connect_running) { + logger.error('This ChannelEventHub has already been connected to start receiving blocks.'); + throw new Error('Event listeners that use startBlock or endBlock must be registered before connecting to the peer channel-based service'); + } + } } - if (have_end_block) { - if (have_start_block && converted_options.start_block.greaterThan(converted_options.end_block)) { - throw new Error(`"startBlock" (${converted_options.start_block}) must not be larger than "endBlock" (${converted_options.end_block}})`); + if (_end_block) { + this._ending_block_number = _end_block; + if (fromConnect) { + logger.debug('_checkReplay - connect will end receiving blocks from %s', this._ending_block_number); + this._start_stop_connect = true; } - this._ending_block_number = converted_options.end_block; - this._allowRegistration = false; result = END_ONLY; - logger.debug('_checkReplay - Event listening will end at block %s', converted_options.end_block); + logger.debug('_checkReplay - Event listening will end at block %s', this._ending_block_number); } - if (have_start_block) { - this._starting_block_number = converted_options.start_block; - this._allowRegistration = false; + if (_start_block) { + this._starting_block_number = _start_block; + if (fromConnect) { + logger.debug('_checkReplay - connect will start receiving blocks from %s', this._starting_block_number); + this._start_stop_connect = true; + } result++; // will move result to START_ONLY or START_AND_END - logger.debug('_checkReplay - Event listening will start at block %s', converted_options.start_block); + logger.debug('_checkReplay - Event listening will start at block %s', this._starting_block_number); } logger.debug('_checkReplay - end'); @@ -1459,9 +1594,8 @@ class ChannelEventHub { this._ending_block_number = null; this._ending_block_seen = false; this._ending_block_newest = false; - // allow this hub to to registar new listeners - this._allowRegistration = true; this._start_stop_registration = null; + this._start_stop_connect = false; } } diff --git a/fabric-client/test/ChannelEventHub.js b/fabric-client/test/ChannelEventHub.js index 4c7334d29c..bc2d4301f9 100644 --- a/fabric-client/test/ChannelEventHub.js +++ b/fabric-client/test/ChannelEventHub.js @@ -17,12 +17,15 @@ const rewire = require('rewire'); const ChannelEventHub = rewire('../lib/ChannelEventHub'); +const Peer = rewire('../lib/Peer'); +const Channel = rewire('../lib/Channel'); const chai = require('chai'); const chaiAsPromised = require('chai-as-promised'); chai.use(chaiAsPromised); const should = chai.should(); const sinon = require('sinon'); +const Long = require('long'); describe('ChannelEventHub', () => { let revert; @@ -75,10 +78,9 @@ describe('ChannelEventHub', () => { }).should.throw(/Missing required argument: channel/); }); - it('should throw if peer argument isnt given', () => { - (() => { - new ChannelEventHub({}); - }).should.throw(/Missing required argument: peer/); + it('should create even if peer argument isnt given', () => { + const hub = new ChannelEventHub({}); + should.equal(hub._peer, undefined); }); }); @@ -108,25 +110,166 @@ describe('ChannelEventHub', () => { }); }); + describe('#_assignPeer', () => { + let hub, peer, channel; + + beforeEach(() => { + peer = new Peer('grpc://host.com:9999', {name: 'mypeer'}); + channel = new Channel('mychannel', {}); + hub = new ChannelEventHub(channel, 'peer'); + }); + + it('should reassign peer to channel event hub', () => { + hub._assignPeer(peer); + should.equal(hub.getPeerAddr(), 'host.com:9999'); + }); + + it('should reassign peer to channel event hub', () => { + channel.addPeer(peer); + hub._assignPeer('mypeer'); + should.equal(hub.getPeerAddr(), 'host.com:9999'); + }); + + it('should throw an error if a peer name not found', () => { + (() => { + hub._assignPeer('bad'); + }).should.throw('Peer with name "bad" not assigned to this channel'); + }); + }); + describe('#lastBlockNumber', () => { + let hub; + beforeEach(() => { + hub = new ChannelEventHub('channel', 'peer'); + }); + it('should throw an error if a block has not been seen', () => { - const hub = new ChannelEventHub('channel', 'peer'); (() => { hub.lastBlockNumber(); }).should.throw(/This ChannelEventHub has not seen a block from the peer/); }); it('should return the last block seen', () => { - const hub = new ChannelEventHub('channel', 'peer'); hub._last_block_seen = 10; hub.lastBlockNumber().should.equal(10); }); }); + describe('#_checkBlockNum', () => { + let hub; + beforeEach(() => { + hub = new ChannelEventHub('channel', 'peer'); + }); + + it('should return the last block seen', () => { + hub._last_block_seen = 2; + const result = hub._checkBlockNum('last_seen'); + result.should.equal(2); + }); + + it('should return newest when newest', () => { + const result = hub._checkBlockNum('newest'); + result.should.equal('newest'); + }); + + it('should return oldest when oldest', () => { + const result = hub._checkBlockNum('oldest'); + result.should.equal('oldest'); + }); + + it('should return Long 12 when "12"', () => { + const result = hub._checkBlockNum('12'); + result.toInt().should.equal(12); + }); + + it('should return Long 12 when int 12', () => { + const result = hub._checkBlockNum(12); + result.toInt().should.equal(12); + }); + + it('should return Long 12 when Long 12', () => { + const result = hub._checkBlockNum(Long.fromInt(12)); + result.toInt().should.equal(12); + }); + + it('should return null when null', () => { + const result = hub._checkBlockNum(null); + should.equal(result, null); + }); + + it('should throw an error if a block number is bad', () => { + (() => { + hub._checkBlockNum('bad'); + }).should.throw(/value:bad is not a valid number /); + }); + + }); + + describe('#_checkEndBlock', () => { + let hub; + beforeEach(() => { + hub = new ChannelEventHub('channel', 'peer'); + }); + + it('should return the last block seen', () => { + hub._last_block_seen = 2; + const result = hub._checkEndBlock('last_seen'); + result.should.equal(2); + }); + + it('should return newest when newest', () => { + const result = hub._checkEndBlock('newest'); + result.should.equal('newest'); + }); + + it('should return oldest when oldest', () => { + const result = hub._checkEndBlock('oldest'); + result.should.equal('oldest'); + }); + + it('should return Long 12 when "12"', () => { + const result = hub._checkEndBlock('12'); + result.toInt().should.equal(12); + }); + + it('should return Long 12 when int 12', () => { + const result = hub._checkEndBlock(12); + result.toInt().should.equal(12); + }); + + it('should return Long 12 when Long 12', () => { + const result = hub._checkEndBlock(Long.fromInt(12)); + result.toInt().should.equal(12); + }); + + it('should return null when null', () => { + const result = hub._checkEndBlock(null); + should.equal(result, null); + }); + + it('should return Long 12 when int 12 and startBlock Long 12', () => { + const result = hub._checkEndBlock(12, Long.fromInt(12)); + result.toInt().should.equal(12); + }); + + it('should throw an error when int 12 and startBlock Long 14', () => { + (() => { + hub._checkEndBlock(12, Long.fromInt(14)); + }).should.throw('"startBlock" (14) must not be greater than "endBlock" (12)'); + }); + + it('should throw an error if a block number is bad', () => { + (() => { + hub._checkEndBlock('bad'); + }).should.throw(/value:bad is not a valid number /); + }); + + }); + describe('#_checkAllowRegistrations', () => { it ('should throw an error if registration is not allowed', () => { const hub = new ChannelEventHub('channel', 'peer'); - hub._allowRegistration = false; + hub._start_stop_registration = true; (() => { hub._checkAllowRegistrations(); }).should.throw(/This ChannelEventHub is not open to event listener registrations/); @@ -149,6 +292,20 @@ describe('ChannelEventHub', () => { }); }); + describe('#reconnect', () => { + let hub; + let connectStub; + it('should add in the force options', () => { + connectStub = sandbox.stub(); + hub = new ChannelEventHub('channel', 'peer'); + hub.connect = connectStub; + + hub.reconnect(); + + sinon.assert.calledWith(connectStub, {'force': true}); + }); + }); + describe('#connect', () => { let _validateSignedEventStub; let getPeerAddrStub; @@ -177,10 +334,10 @@ describe('ChannelEventHub', () => { getPeerAddrStub.returns('peer'); hub._clientContext._userContext = {}; hub.connect(true); - sinon.assert.calledWith(FakeLogger.debug, 'connect - start peerAddr:%s', 'peer'); - sinon.assert.calledWith(FakeLogger.debug, 'connect - filtered block stream set to:%s', false); - sinon.assert.calledWith(FakeLogger.debug, 'connect - signed event:%s', false); - sinon.assert.calledWith(FakeLogger.debug, 'connect - end %s', 'peer'); + sinon.assert.calledWith(FakeLogger.debug, '%s - start peerAddr:%s', 'connect', 'peer'); + sinon.assert.calledWith(FakeLogger.debug, '%s - filtered block stream set to:%s', 'connect', false); + sinon.assert.calledWith(FakeLogger.debug, '%s - signed event:%s', 'connect', false); + sinon.assert.calledWith(FakeLogger.debug, '%s - end %s', 'connect', 'peer'); }); it('should validate a signed event', () => { @@ -201,21 +358,55 @@ describe('ChannelEventHub', () => { getPeerAddrStub.returns('peer'); hub._clientContext._userContext = {}; hub.connect(null); - sinon.assert.calledWith(FakeLogger.debug, 'connect - using a filtered block stream by default'); + sinon.assert.calledWith(FakeLogger.debug, '%s - using a filtered block stream by default', 'connect'); }); it('should log when options are undefined', () => { getPeerAddrStub.returns('peer'); hub._clientContext._userContext = {}; hub.connect(); - sinon.assert.calledWith(FakeLogger.debug, 'connect - using a filtered block stream by default'); + sinon.assert.calledWith(FakeLogger.debug, '%s - using a filtered block stream by default', 'connect'); }); it('should call _connect', () => { getPeerAddrStub.returns('peer'); hub._clientContext._userContext = {}; hub.connect(); - sinon.assert.calledWith(_connectStub, {signedEvent: null}); + sinon.assert.calledWith(_connectStub, {}); + }); + + it('should call the correct logs on startBlock', () => { + hub._clientContext._userContext = {}; + hub.connect({startBlock: 1}); + sinon.assert.calledWith(FakeLogger.debug, '%s - options include startBlock of %s'); + sinon.assert.calledWith(FakeLogger.debug, '%s - options do not include endBlock'); + sinon.assert.calledWith(FakeLogger.debug, '%s - options do not include a target'); + }); + + it('should call the correct logs on endBlock', () => { + hub._clientContext._userContext = {}; + hub.connect({endBlock: 1}); + sinon.assert.calledWith(FakeLogger.debug, '%s - options include endBlock of %s'); + sinon.assert.calledWith(FakeLogger.debug, '%s - options do not include startBlock'); + sinon.assert.calledWith(FakeLogger.debug, '%s - options do not include a target'); + }); + + it('should call the correct logs on startBlock and endBlock', () => { + hub._clientContext._userContext = {}; + hub.connect({startBlock: 1, endBlock: 2}); + sinon.assert.calledWith(FakeLogger.debug, '%s - options include startBlock of %s'); + sinon.assert.calledWith(FakeLogger.debug, '%s - options include endBlock of %s'); + sinon.assert.calledWith(FakeLogger.debug, '%s - options do not include a target'); + }); + + it('should call the correct logs on target', () => { + hub._clientContext._userContext = {}; + hub._channel = {}; + hub._channel._getTargets = sandbox.stub().returns({}); + hub.connect({target: {}}); + sinon.assert.calledWith(FakeLogger.debug, '%s - options include a target'); + sinon.assert.calledWith(FakeLogger.debug, '%s - options do not include startBlock'); + sinon.assert.calledWith(FakeLogger.debug, '%s - options do not include endBlock'); }); }); @@ -666,10 +857,12 @@ describe('ChannelEventHub', () => { let TransactionIDStub; let SeekPositionStub; let setNewestStub; + let setOldestStub; let setSpecifiedStub; let SeekSpecifiedStub; let setNumberStub; let SeekNewestStub; + let SeekOldestStub; let setStartStub; let setStopStub; let setBehaviorStub; @@ -691,14 +884,17 @@ describe('ChannelEventHub', () => { TransactionIDStub = sandbox.stub(); revert.push(ChannelEventHub.__set__('TransactionID', TransactionIDStub)); setNewestStub = sandbox.stub(); + setOldestStub = sandbox.stub(); setSpecifiedStub = sandbox.stub(); - SeekPositionStub = sandbox.stub().returns({setNewest: setNewestStub, setSpecified: setSpecifiedStub}); + SeekPositionStub = sandbox.stub().returns({setNewest: setNewestStub, setOldest: setOldestStub, setSpecified: setSpecifiedStub}); revert.push(ChannelEventHub.__set__('_abProto.SeekPosition', SeekPositionStub)); setNumberStub = sandbox.stub(); SeekSpecifiedStub = sandbox.stub().returns({setNumber: setNumberStub}); revert.push(ChannelEventHub.__set__('_abProto.SeekSpecified', SeekSpecifiedStub)); SeekNewestStub = sandbox.stub(); revert.push(ChannelEventHub.__set__('_abProto.SeekNewest', SeekNewestStub)); + SeekOldestStub = sandbox.stub(); + revert.push(ChannelEventHub.__set__('_abProto.SeekOldest', SeekOldestStub)); setStartStub = sandbox.stub(); setStopStub = sandbox.stub(); setBehaviorStub = sandbox.stub(); @@ -774,12 +970,10 @@ describe('ChannelEventHub', () => { sinon.assert.calledWith(TransactionIDStub, new IdentityStub(), false); }); - it('should return the seek payload with more than zero blocks', () => { - hub._starting_block_number = 1; - hub._ending_block_newest = 1; + it('should run with startBlock at newest', () => { + hub._starting_block_number = 'newest'; hub.generateUnsignedRegistration({mspId: 'mspId', certificate: 'certificate'}); sinon.assert.calledWith(SeekSpecifiedStub); - sinon.assert.calledWith(setNumberStub, 1); sinon.assert.calledWith(setSpecifiedStub, new SeekSpecifiedStub()); sinon.assert.called(SeekPositionStub); sinon.assert.called(SeekNewestStub); @@ -787,6 +981,52 @@ describe('ChannelEventHub', () => { sinon.assert.called(SeekInfoStub); sinon.assert.calledWith(setStartStub, new SeekPositionStub()); sinon.assert.calledWith(setStopStub, new SeekPositionStub()); + sinon.assert.calledWith(setBehaviorStub, 'BLOCK_UNTIL_READY'); + sinon.assert.called(buildChannelHeaderStub); + sinon.assert.called(getTransactionIDStub); + sinon.assert.called(getClientCertHashStub); + sinon.assert.called(getNonceStub); + sinon.assert.called(buildHeaderStub); + sinon.assert.called(buildHeaderStub); + sinon.assert.called(setHeaderStub); + sinon.assert.called(setDataStub); + sinon.assert.calledTwice(toBufferStub); + }); + + it('should run with startBlock at oldest', () => { + hub._starting_block_number = 'oldest'; + hub.generateUnsignedRegistration({mspId: 'mspId', certificate: 'certificate'}); + sinon.assert.calledWith(SeekSpecifiedStub); + sinon.assert.calledWith(setSpecifiedStub, new SeekSpecifiedStub()); + sinon.assert.called(SeekPositionStub); + sinon.assert.called(SeekOldestStub); + sinon.assert.calledWith(setOldestStub, new SeekOldestStub()); + sinon.assert.called(SeekInfoStub); + sinon.assert.calledWith(setStartStub, new SeekPositionStub()); + sinon.assert.calledWith(setStopStub, new SeekPositionStub()); + sinon.assert.calledWith(setBehaviorStub, 'BLOCK_UNTIL_READY'); + sinon.assert.called(buildChannelHeaderStub); + sinon.assert.called(getTransactionIDStub); + sinon.assert.called(getClientCertHashStub); + sinon.assert.called(getNonceStub); + sinon.assert.called(buildHeaderStub); + sinon.assert.called(buildHeaderStub); + sinon.assert.called(setHeaderStub); + sinon.assert.called(setDataStub); + sinon.assert.calledTwice(toBufferStub); + }); + + it('should run with startBlock:1 and endBlock:1', () => { + hub._starting_block_number = 1; + hub._ending_block_number = 1; + hub.generateUnsignedRegistration({mspId: 'mspId', certificate: 'certificate'}); + sinon.assert.calledWith(SeekSpecifiedStub); + sinon.assert.calledWith(setNumberStub, 1); + sinon.assert.calledWith(setSpecifiedStub, new SeekSpecifiedStub()); + sinon.assert.called(SeekPositionStub); + sinon.assert.called(SeekInfoStub); + sinon.assert.calledWith(setStartStub, new SeekPositionStub()); + sinon.assert.calledWith(setStopStub, new SeekPositionStub()); sinon.assert.calledWith(setBehaviorStub, 'FAIL_IF_NOT_READY'); sinon.assert.called(buildChannelHeaderStub); sinon.assert.called(getTransactionIDStub); @@ -799,16 +1039,14 @@ describe('ChannelEventHub', () => { sinon.assert.calledTwice(toBufferStub); }); - it('should return the seek payload with more than zero blocks given identity and txId', () => { + it('should run with startBlock:1 and endBlock:1 given identity and txId', () => { hub._starting_block_number = 1; - hub._ending_block_newest = 1; + hub._ending_block_number = 1; hub.generateUnsignedRegistration({identity: 'identity', txId: new TransactionIDStub()}); sinon.assert.calledWith(SeekSpecifiedStub); sinon.assert.calledWith(setNumberStub, 1); sinon.assert.calledWith(setSpecifiedStub, new SeekSpecifiedStub()); sinon.assert.called(SeekPositionStub); - sinon.assert.called(SeekNewestStub); - sinon.assert.calledWith(setNewestStub, new SeekNewestStub()); sinon.assert.called(SeekInfoStub); sinon.assert.calledWith(setStartStub, new SeekPositionStub()); sinon.assert.calledWith(setStopStub, new SeekPositionStub()); @@ -824,16 +1062,16 @@ describe('ChannelEventHub', () => { sinon.assert.calledTwice(toBufferStub); }); - it('should return the seek payload with more than zero blocks given identity and txId', () => { + it('should run with startBlock:1 and endBlock:newest given identity and txId', () => { hub._starting_block_number = 1; - hub._ending_block_newest = 1; + hub._ending_block_number = 'newest'; hub.generateUnsignedRegistration({identity: 'identity', txId: new TransactionIDStub(), mspId: 'mspId', certificate: 'certificate'}); sinon.assert.calledWith(SeekSpecifiedStub); sinon.assert.calledWith(setNumberStub, 1); sinon.assert.calledWith(setSpecifiedStub, new SeekSpecifiedStub()); - sinon.assert.called(SeekPositionStub); sinon.assert.called(SeekNewestStub); sinon.assert.calledWith(setNewestStub, new SeekNewestStub()); + sinon.assert.called(SeekPositionStub); sinon.assert.called(SeekInfoStub); sinon.assert.calledWith(setStartStub, new SeekPositionStub()); sinon.assert.calledWith(setStopStub, new SeekPositionStub()); @@ -849,16 +1087,14 @@ describe('ChannelEventHub', () => { sinon.assert.calledTwice(toBufferStub); }); - it('should return the seek payload with more than zero blocks given identity and txId', () => { + it('should run with startBlock:1 and endBlock:1 given identity and txId', () => { hub._starting_block_number = 1; - hub._ending_block_newest = 1; + hub._ending_block_number = 1; hub.generateUnsignedRegistration({identity: 'identity', txId: new TransactionIDStub(), mspId: 'mspId'}); sinon.assert.calledWith(SeekSpecifiedStub); sinon.assert.calledWith(setNumberStub, 1); sinon.assert.calledWith(setSpecifiedStub, new SeekSpecifiedStub()); sinon.assert.called(SeekPositionStub); - sinon.assert.called(SeekNewestStub); - sinon.assert.calledWith(setNewestStub, new SeekNewestStub()); sinon.assert.called(SeekInfoStub); sinon.assert.calledWith(setStartStub, new SeekPositionStub()); sinon.assert.calledWith(setStopStub, new SeekPositionStub()); @@ -980,103 +1216,199 @@ describe('ChannelEventHub', () => { }); describe('#_checkReplay', () => { - let convertToLongStub; - let _haveRegistrationsStub; - let hub; beforeEach(() => { - _haveRegistrationsStub = sandbox.stub(); - convertToLongStub = sandbox.stub(); - revert.push(ChannelEventHub.__set__('utils.convertToLong', convertToLongStub)); hub = new ChannelEventHub('channel', 'peer'); - hub._haveRegistrations = _haveRegistrationsStub; }); - it('should log on entry and exit', () => { - hub._checkReplay({}); + it('should return NO_START_STOP and log on entry and exit', () => { + const result = hub._checkReplay({}); + result.should.equal(0); sinon.assert.calledWith(FakeLogger.debug, '_checkReplay - start'); sinon.assert.calledWith(FakeLogger.debug, '_checkReplay - end'); }); - it('should throw if there is a problem with the startBlock parameter', () => { - convertToLongStub.throws(new Error()); - (() => { - hub._checkReplay({startBlock: 1}); - }).should.throw(Error, 'Problem with the startBlock parameter ::Error'); + it('should return START_ONLY with startBlock parameter of 1', () => { + const result = hub._checkReplay({startBlock: 1}); + result.should.equal(1); + hub._starting_block_number.toInt().should.equal(1); + sinon.assert.calledWith(FakeLogger.debug, '_checkReplay - Event listening will start at block %s', Long.fromInt(1)); }); - it('should return 1 when startBlock is set', () => { - convertToLongStub.returns('1'); - const result = hub._checkReplay({startBlock: 1}); - sinon.assert.calledWith(FakeLogger.debug, '_checkReplay - Event listening will start at block %s', '1'); - hub._starting_block_number.should.equal('1'); + it('should return START_ONLY with startBlock parameter of string 1', () => { + const result = hub._checkReplay({startBlock: '1'}); result.should.equal(1); + hub._starting_block_number.toInt().should.equal(1); + sinon.assert.calledWith(FakeLogger.debug, '_checkReplay - Event listening will start at block %s', Long.fromInt(1)); }); - it('should throw an error if there is a problem with the endBlock parameter', () => { - convertToLongStub.throws(new Error()); - (() => { - hub._checkReplay({endBlock: 1}); - }).should.throw(Error, 'Problem with the endBlock parameter ::Error'); + it('should return START_ONLY with startBlock parameter of Long 1', () => { + const result = hub._checkReplay({startBlock: Long.fromInt(1)}); + result.should.equal(1); + hub._starting_block_number.toInt().should.equal(1); + sinon.assert.calledWith(FakeLogger.debug, '_checkReplay - Event listening will start at block %s', Long.fromInt(1)); + }); + + it('should return START_ONLY with startBlock parameter of newest', () => { + const result = hub._checkReplay({startBlock: 'newest'}); + result.should.equal(1); + hub._starting_block_number.should.equal('newest'); + sinon.assert.calledWith(FakeLogger.debug, '_checkReplay - Event listening will start at block %s', 'newest'); + }); + + it('should return START_ONLY with startBlock parameter of oldest', () => { + const result = hub._checkReplay({startBlock: 'oldest'}); + result.should.equal(1); + hub._starting_block_number.should.equal('oldest'); + sinon.assert.calledWith(FakeLogger.debug, '_checkReplay - Event listening will start at block %s', 'oldest'); + }); + + it('should return START_ONLY with startBlock parameter of last_seen', () => { + hub._last_block_seen = 1; + const result = hub._checkReplay({startBlock: 'last_seen'}); + result.should.equal(1); + hub._starting_block_number.should.equal(1); + sinon.assert.calledWith(FakeLogger.debug, '_checkReplay - Event listening will start at block %s', 1); + }); + + it('should return END_ONLY with endBlock parameter of 1', () => { + const result = hub._checkReplay({endBlock: 1}); + result.should.equal(2); + hub._ending_block_number.toInt().should.equal(1); + sinon.assert.calledWith(FakeLogger.debug, '_checkReplay - Event listening will end at block %s', Long.fromInt(1)); + }); + + it('should return END_ONLY with endBlock parameter of string 1', () => { + const result = hub._checkReplay({endBlock: '1'}); + result.should.equal(2); + hub._ending_block_number.toInt().should.equal(1); + sinon.assert.calledWith(FakeLogger.debug, '_checkReplay - Event listening will end at block %s', Long.fromInt(1)); + }); + + it('should return END_ONLY with endBlock parameter of Long 1', () => { + const result = hub._checkReplay({endBlock: Long.fromInt(1)}); + result.should.equal(2); + hub._ending_block_number.toInt().should.equal(1); + sinon.assert.calledWith(FakeLogger.debug, '_checkReplay - Event listening will end at block %s', Long.fromInt(1)); + }); + + it('should return END_ONLY with endBlock parameter of newest', () => { + const result = hub._checkReplay({endBlock: 'newest'}); + result.should.equal(2); + hub._ending_block_number.should.equal('newest'); + sinon.assert.calledWith(FakeLogger.debug, '_checkReplay - Event listening will end at block %s', 'newest'); + }); + + it('should return END_ONLY with endBlock parameter of oldest', () => { + const result = hub._checkReplay({endBlock: 'oldest'}); + result.should.equal(2); + hub._ending_block_number.should.equal('oldest'); + sinon.assert.calledWith(FakeLogger.debug, '_checkReplay - Event listening will end at block %s', 'oldest'); + }); + + it('should return END_ONLY with endBlock parameter of last_seen', () => { + hub._last_block_seen = 1; + const result = hub._checkReplay({endBlock: 'last_seen'}); + result.should.equal(2); + hub._ending_block_number.should.equal(1); + sinon.assert.calledWith(FakeLogger.debug, '_checkReplay - Event listening will end at block %s', 1); }); it('should throw an error if startBlock is greater than endBlock', () => { - convertToLongStub.onCall(0).returns({greaterThan: () => true}); - convertToLongStub.onCall(1).returns(100); - revert.push(ChannelEventHub.__set__('Long.MAX_VALUE', 100)); (() => { - hub._checkReplay({startBlock: 1, endBlock: '1'}); - }).should.throw('"startBlock" ([object Object]) must not be larger than "endBlock" (100})'); + hub._checkReplay({startBlock: 2, endBlock: '1'}); + }).should.throw('"startBlock" (2) must not be greater than "endBlock" (1)'); }); - it('should throw an error if startBlock given and _haveRegistrations is true', () => { - _haveRegistrationsStub.returns(true); - convertToLongStub.returns(1); + it('should throw an error if startBlock given and have _start_stop_registration', () => { + hub._start_stop_registration = {}; + (() => { + hub._checkReplay({startBlock: 1}, true); + }).should.throw('Not able to connect with startBlock or endBlock when a registered listener has those options.'); + sinon.assert.calledWith(FakeLogger.error, 'This ChannelEventHub has a registered listener that has options of startBlock or endBlock'); + }); + + it('should throw an error if endBlock given and have _start_stop_registration', () => { + hub._start_stop_registration = {}; + (() => { + hub._checkReplay({endBlock: 1}, true); + }).should.throw('Not able to connect with startBlock or endBlock when a registered listener has those options.'); + sinon.assert.calledWith(FakeLogger.error, 'This ChannelEventHub has a registered listener that has options of startBlock or endBlock'); + }); + + it('should throw an error if startBlock and have registered listeners', () => { + hub._haveRegistrations = sandbox.stub(); + hub._haveRegistrations.returns(true); (() => { hub._checkReplay({startBlock: 1}); }).should.throw('Only one event registration is allowed when startBlock or endBlock are used'); - sinon.assert.calledWith(FakeLogger.error, 'This ChannelEventHub is already registered with active listeners. Not able options of startBlock:%s endBlock:%s', 1, undefined); + sinon.assert.calledWith(FakeLogger.error, 'This ChannelEventHub is already registered with active listeners.'); }); - it('should throw an error if endBlock given and _haveRegistrations is true', () => { - _haveRegistrationsStub.returns(true); - convertToLongStub.returns(1); + it('should throw an error if endBlock and have registered listeners', () => { + hub._haveRegistrations = sandbox.stub(); + hub._haveRegistrations.returns(true); (() => { hub._checkReplay({endBlock: 1}); }).should.throw('Only one event registration is allowed when startBlock or endBlock are used'); - sinon.assert.calledWith(FakeLogger.error, 'This ChannelEventHub is already registered with active listeners. Not able options of startBlock:%s endBlock:%s', undefined, 1); + sinon.assert.calledWith(FakeLogger.error, 'This ChannelEventHub is already registered with active listeners.'); + }); + + it('should throw an error if startBlock and endBlock and have registered listeners', () => { + hub._haveRegistrations = sandbox.stub(); + hub._haveRegistrations.returns(true); + (() => { + hub._checkReplay({startBlock: 1, endBlock: 2}); + }).should.throw('Only one event registration is allowed when startBlock or endBlock are used'); + sinon.assert.calledWith(FakeLogger.error, 'This ChannelEventHub is already registered with active listeners.'); }); it('should throw an error if startBlock and _connected', () => { hub._connected = true; - convertToLongStub.returns(1); (() => { hub._checkReplay({startBlock: 1}); }).should.throw('Event listeners that use startBlock or endBlock must be registered before connecting to the peer channel-based service'); - sinon.assert.calledWith(FakeLogger.error, 'This ChannelEventHub has already been connected to start receiving blocks. Not able to use options of startBlock:%s endBlock:%s', 1, undefined); + sinon.assert.calledWith(FakeLogger.error, 'This ChannelEventHub has already been connected to start receiving blocks.'); }); it('should throw an error if endBlock and _connected', () => { hub._connected = true; - convertToLongStub.returns(1); (() => { hub._checkReplay({endBlock: 1}); }).should.throw('Event listeners that use startBlock or endBlock must be registered before connecting to the peer channel-based service'); - sinon.assert.calledWith(FakeLogger.error, 'This ChannelEventHub has already been connected to start receiving blocks. Not able to use options of startBlock:%s endBlock:%s', undefined, 1); + sinon.assert.calledWith(FakeLogger.error, 'This ChannelEventHub has already been connected to start receiving blocks.'); }); - it('should return 2 if endBlock is set', () => { - convertToLongStub.returns(1); - const result = hub._checkReplay({endBlock: 1}); - sinon.assert.calledWith(FakeLogger.debug, '_checkReplay - Event listening will end at block %s', 1); - result.should.equal(2); + it('should throw an error if startBlock and endBlock and _connected', () => { + hub._connected = true; + (() => { + hub._checkReplay({startBlock: 1, endBlock: 2}); + }).should.throw('Event listeners that use startBlock or endBlock must be registered before connecting to the peer channel-based service'); + sinon.assert.calledWith(FakeLogger.error, 'This ChannelEventHub has already been connected to start receiving blocks.'); }); - it('should return 2 if endBlock is set to newest', () => { - convertToLongStub.returns(1); - const result = hub._checkReplay({endBlock: 'newest'}); - sinon.assert.calledWith(FakeLogger.debug, '_checkReplay - Event listening will end at block %s', 1); - result.should.equal(2); + it('should throw an error if startBlock and _start_stop_connect', () => { + hub._start_stop_connect = true; + (() => { + hub._checkReplay({startBlock: 1}); + }).should.throw('Registrations with startBlock or endBlock are not allowed if this ChannelEventHub is connected with a startBlock or endBlock'); + sinon.assert.calledWith(FakeLogger.error, 'This ChannelEventHub has been connected with a startBlock or endBlock'); + }); + + it('should throw an error if endBlock and _start_stop_connect', () => { + hub._start_stop_connect = true; + (() => { + hub._checkReplay({endBlock: 1}); + }).should.throw('Registrations with startBlock or endBlock are not allowed if this ChannelEventHub is connected with a startBlock or endBlock'); + sinon.assert.calledWith(FakeLogger.error, 'This ChannelEventHub has been connected with a startBlock or endBlock'); + }); + + it('should throw an error if startBlock and endBlock and _start_stop_connect', () => { + hub._start_stop_connect = true; + (() => { + hub._checkReplay({startBlock: 1, endBlock: 2}); + }).should.throw('Registrations with startBlock or endBlock are not allowed if this ChannelEventHub is connected with a startBlock or endBlock'); + sinon.assert.calledWith(FakeLogger.error, 'This ChannelEventHub has been connected with a startBlock or endBlock'); }); }); @@ -1981,7 +2313,6 @@ describe('ChannelEventHub', () => { should.equal(hub._ending_block_number, null); hub._ending_block_seen.should.be.false; hub._ending_block_newest.should.be.false; - hub._allowRegistration.should.be.true; should.equal(hub._start_stop_registration, null); }); }); diff --git a/package.json b/package.json index fe18dfad71..fa4f8d0511 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "tar-fs": "^1.13.0", "targz": "^1.0.1", "tslint": "^5.11.0", - "typescript": "2.8.3", + "typescript": "^3.3.3", "winston": "^2.2.0", "@ampretia/x509": "^0.4.0" }, diff --git a/scripts/Jenkins_Scripts/CI_Script.sh b/scripts/Jenkins_Scripts/CI_Script.sh index ea3bd7efbb..6a8d015c90 100755 --- a/scripts/Jenkins_Scripts/CI_Script.sh +++ b/scripts/Jenkins_Scripts/CI_Script.sh @@ -153,8 +153,17 @@ sdk_E2e_Tests() { gulp || err_Check "ERROR!!! gulp failed" gulp ca || err_Check "ERROR!!! gulp ca failed" - echo -e "\033[32m Execute Headless and Integration Tests" "\033[0m" - gulp test || err_Check "ERROR!!! gulp test failed" + echo -e "\033[32m Execute Headless Tests" "\033[0m" + gulp test-headless || err_Check "ERROR!!! gulp test failed" + + echo -e "\033[32m Execute Integration Tests" "\033[0m" + gulp test-integration || err_Check "ERROR!!! gulp test failed" + + echo -e "\033[32m Execute Just the Cucumber Tests" "\033[0m" + gulp run-test-cucumber || err_Check "ERROR!!! gulp test failed" + + echo -e "\033[32m Execute Just the logger Tests" "\033[0m" + gulp run-test-logger || err_Check "ERROR!!! gulp test failed" } # Publish npm modules after successful merge on amd64 diff --git a/test/integration/channel-event-hub.js b/test/integration/channel-event-hub.js index b6c573d64c..4da59c3777 100644 --- a/test/integration/channel-event-hub.js +++ b/test/integration/channel-event-hub.js @@ -23,7 +23,7 @@ const e2eUtils = require('./e2e/e2eUtils.js'); // When running this as a standalone test, be sure to create and join a channel called 'mychannel' test('***** Test channel events', async (t) => { - t.pass(' ======>>>>> CHANNEL EVENT INTEGRATION TEST START'); + t.pass('\n ======>>>>> CHANNEL EVENT INTEGRATION TEST START\n'); try { testUtil.resetDefaults(); @@ -79,6 +79,14 @@ test('***** Test channel events', async (t) => { channel.addPeer(peer); targets.push(peer); + data = fs.readFileSync(path.join(__dirname, 'e2e', '../../fixtures/channel/crypto-config/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tlscacerts/org2.example.com-cert.pem')); + const peer_org2 = client.newPeer('grpcs://localhost:8051', { + pem: Buffer.from(data).toString(), + 'ssl-target-name-override': 'peer0.org2.example.com' + }); + + t.pass('Successfully setup the fabric network'); + // get a transaction ID object based on the current user assigned to the client instance tx_id = client.newTransactionID(); req = { @@ -93,6 +101,7 @@ test('***** Test channel events', async (t) => { if (!checkResults(t, results[0])) { throw Error('Failed to install chaincode'); } + t.pass('Successfully installed chaincode'); // get a transaction ID object based on the current user assigned // to the client instance @@ -113,6 +122,9 @@ test('***** Test channel events', async (t) => { if (!checkResults(t, results[0])) { throw Error('Failed to instantiate chaincode'); } + // get the initialize chaincode response status + const init_response = results[0][0].response; + t.pass('The initialize response status:' + init_response.status); /* * Test @@ -570,7 +582,7 @@ test('***** Test channel events', async (t) => { if (error.toString().indexOf('Newest block received')) { // this error callback will be called to indicate that the listener is no longer listening // in this case it is OK as the message indicates that newest block was sent - t.pass('Message received inidicating newest block received ::' + error); + t.pass('Message received indicating newest block received ::' + error); resolve('newest block replayed'); } else { t.fail('Failed to replay all the block events'); @@ -586,8 +598,317 @@ test('***** Test channel events', async (t) => { results = await block_replay; t.equals(results, 'newest block replayed', 'Checking that newest block replayed'); + /* + * Test + * that we are able to connect with start block + * before any listeners (just a test, not a good way to use an eventhub) + */ + event_hub.disconnect(); // clean up + // check that we can connect with callbacks + connecter = new Promise((resolve, reject) => { + const handle = setTimeout(() => { + reject(new Error('timeout connecting to the event service')); + }, 15000); + // connect with the startBlock + // For testing only, not a good idea as that the eventHub will + // immediately start to receive blocks and no listener will be + // registered to see + event_hub.connect({full_block: false, startBlock: 0}, (error, connected_hub) => { + clearTimeout(handle); + if (error) { + reject(error); + } else { + if (connected_hub.isconnected()) { + t.pass('Successfully able to connect to the event service using a connect callback and have a start block'); + resolve(); + } else { + reject(new Error('Event Hub notified us that it was connected however the connect status was false')); + } + } + }); + }); + + try { + await connecter; + t.pass('Successfully checked for connect using a callback and start block'); + } catch (error) { + t.fail('Failed to connect to event service ::' + error.toString()); + } + + // must get a new transaction object for every transaction + tx_id = client.newTransactionID(); + txid = tx_id.getTransactionID(); // get the actual transaction id string + req = { + targets : targets, + chaincodeId: chaincode_id, + fcn: 'invoke', + args: ['invoke', 'BLOCK'], + txId: tx_id + }; + + results = await channel.sendTransactionProposal(req); + if (!checkResults(t, results[0])) { + throw Error('Failed to endorse invoke proposal with "BLOCK" arg'); + } + + event_monitor = new Promise((resolve, reject) => { + const handle = setTimeout(() => { + t.fail('Timeout - Failed to receive the event'); + reject('timeout'); + }, 15000); + + event_hub.registerTxEvent(txid, (txnid, code, block_num) => { + clearTimeout(handle); + t.pass('transaction status code:' + code + ' for transaction id ::' + txnid + ' block_num:' + block_num); + resolve(code); + }, (error) => { + clearTimeout(handle); + t.fail('Failed to receive event for start block testing ::' + error.toString()); + // send back error + reject(error); + }); + }); + + send_trans = channel.sendTransaction({proposalResponses: results[0], proposal: results[1]}); + + results = await Promise.all([event_monitor, send_trans]); + t.pass('Successfully got the transaction results'); + + // checking that the callback is able to tell the application something + t.equal(results[0], 'VALID', 'checking that the event says the transaction was valid'); + + + /* + * Test + * that we are able to connect with start block after + * we register a listener, see if able to replay + * the blocks and not submit a new one + */ + event_hub.disconnect(); // clean up + // check that we can connect with callbacks + connecter = new Promise((resolve, reject) => { + const handle = setTimeout(() => { + reject(new Error('timeout connecting to the event service')); + }, 15000); + // connect with the startBlock + // For testing only, not a good idea as that the eventHub will + // immediately start to receive blocks and no listener will be + // registered to see + event_hub.connect({full_block: false, startBlock: 0}, (error, connected_hub) => { + clearTimeout(handle); + if (error) { + reject(error); + } else { + if (connected_hub.isconnected()) { + t.pass('Successfully able to connect to the event service using a connect callback and have a start block'); + resolve(); + } else { + reject(new Error('Event Hub notified us that it was connected however the connect status was false')); + } + } + }); + }); + + event_monitor = new Promise((resolve, reject) => { + const handle = setTimeout(() => { + t.fail('Timeout - Failed to receive the event'); + reject('timeout'); + }, 15000); + + // register will a txid that should already be in block + event_hub.registerTxEvent(txid, (txnid, code, block_num) => { + clearTimeout(handle); + t.pass('transaction status code:' + code + ' for transaction id ::' + txnid + ' block_num:' + block_num); + resolve(code); + }, (error) => { + clearTimeout(handle); + t.fail('Failed to receive event for replay::' + error.toString()); + // send back error + reject(error); + }); + }); + + // notice that we are just registering a listener and connecting + // we are not invoking a new transaction + results = await Promise.all([event_monitor, connecter]); + t.pass('Successfully got the transaction replayed results'); + + // checking that the callback is able to tell the application something + t.equal(results[0], 'VALID', 'checking that the replayed event says the transaction was valid'); + + /* + * Test + * that we are able to reconnect with a start block after + * a timeout of not receiving an event + */ + event_hub.disconnect(); // clean up + + // check that we can connect with callbacks + connecter = new Promise((resolve, reject) => { + const handle = setTimeout(() => { + reject(new Error('timeout connecting to the event service')); + }, 15000); + + event_hub.connect({full_block: false}, (error, connected_hub) => { + clearTimeout(handle); + if (error) { + reject(error); + } else { + if (connected_hub.isconnected()) { + t.pass('Successfully able to connect to the event service using a connect callback'); + resolve(); + } else { + reject(new Error('Event Hub notified us that it was connected however the connect status was false')); + } + } + }); + }); + + try { + await connecter; + t.pass('Successfully checked for connect using a callback'); + } catch (error) { + t.fail('Failed to connect to event service ::' + error.toString()); + } + + // must get a new transaction object for every transaction + tx_id = client.newTransactionID(); + txid = tx_id.getTransactionID(); // get the actual transaction id string + req = { + targets : targets, + chaincodeId: chaincode_id, + fcn: 'invoke', + args: ['invoke', 'BLOCK'], + txId: tx_id + }; + + results = await channel.sendTransactionProposal(req); + if (!checkResults(t, results[0])) { + throw Error('Failed to endorse invoke proposal with "BLOCK" arg'); + } + + const tx_event_checker = new TxEventChecker(t); + + event_hub.registerTxEvent(txid, (txnid, code, block_num) => { + t.pass('transaction status code:' + code + ' for transaction id ::' + txnid + ' block_num:' + block_num); + tx_event_checker.check(txnid, code, block_num); + }, (error) => { + t.fail('Failed to receive event for replay on reconnect::' + error.toString()); + tx_event_checker.error(error); + }, { + unregister: false + // need to make sure the event hub holds this + // registration, we are faking out the processing + // and pretending the event hub did not see it + }); + + event_monitor = new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + // this will simulate that this event listener did not see the + // the event, so later when we reconnect, we should see it + t.pass('Timeout - Successfully received the timeout'); + resolve('TIMEOUT'); + }, 5000); + + // configure the listener to use this promise + tx_event_checker.setTimeout(timeout); + tx_event_checker.setResolve(resolve); + tx_event_checker.setReject(reject); + tx_event_checker.setTransactionId('bad'); // so we do not see it + }); + + send_trans = channel.sendTransaction({proposalResponses: results[0], proposal: results[1]}); + + results = await Promise.all([event_monitor, send_trans]); + t.equal(results[0], 'TIMEOUT', 'checking that the timeout occurred'); + + + // ----------------------------- + // check that we can reconnect + connecter = new Promise((resolve, reject) => { + const handle = setTimeout(() => { + reject(new Error('timeout reconnecting to the event service')); + }, 5000); + // reconnect with the startBlock + event_hub.reconnect({full_block: false, startBlock: 0}, (error, connected_hub) => { + clearTimeout(handle); + if (error) { + reject(error); + } else { + if (connected_hub.isconnected()) { + t.pass('Successfully able to reconnect to the event service using a reconnect callback and have a start block'); + resolve(); + } else { + reject(new Error('Event Hub notified us that it was connected however the connect status was false')); + } + } + }); + }); + + event_monitor = new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + t.fail('Timeout - Failed to receive the replay of the event after reconnecting'); + reject('timeout'); + }, 5000); + // configure the listener to use this promise + tx_event_checker.setTimeout(timeout); + tx_event_checker.setResolve(resolve); + tx_event_checker.setReject(reject); + tx_event_checker.setTransactionId(txid); + }); + + // notice that we are just changing the promise of the listener callback + // we are not invoking a new transaction, the reconnect should replay + results = await Promise.all([event_monitor, connecter]); + // checking that the callback is able to tell the application something + t.equal(results[0], 'VALID', 'checking that reconnecting will replay the event'); + t.equal(event_hub.getPeerAddr(), 'localhost:7051', 'checking received the replayed event from peer:localhost:7051'); - t.pass(' ======>>>>> CHANNEL EVENT INTEGRATION TEST END'); + // -------------------------------------------- + // check that we can reconnect with a new peer + connecter = new Promise((resolve, reject) => { + const handle = setTimeout(() => { + reject(new Error('timeout reconnecting to the event service with a new peer')); + }, 5000); + // reconnect with the startBlock + event_hub.reconnect({full_block: false, startBlock: 0, target: peer_org2}, (error, connected_hub) => { + clearTimeout(handle); + if (error) { + reject(error); + } else { + if (connected_hub.isconnected()) { + t.pass('Successfully able to reconnect to the event service using a reconnect callback and have a new peer'); + resolve(); + } else { + reject(new Error('Event Hub notified us that it was connected however the connect status was false')); + } + } + }); + }); + + event_monitor = new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + t.fail('Timeout - Failed to receive the replay of the event after reconnecting with a new peer'); + reject('timeout'); + }, 5000); + // configure the listener to use this promise + tx_event_checker.setTimeout(timeout); + tx_event_checker.setResolve(resolve); + tx_event_checker.setReject(reject); + tx_event_checker.setTransactionId(txid); + }); + + // notice that we are just changing the promise of the listener callback + // we are not invoking a new transaction, the reconnect should replay + results = await Promise.all([event_monitor, connecter]); + // checking that the callback is able to tell the application something + t.equal(results[0], 'VALID', 'checking that reconnecting will replay the events on a different peer'); + t.equal(event_hub.getPeerAddr(), 'localhost:8051', 'checking received the replayed event from peer:localhost:8051'); + + // clean up since we set it to not unregister when notified + event_hub.unregisterTxEvent(txid); + + t.pass(' \n======>>>>> CHANNEL EVENT INTEGRATION TEST END\n'); } catch (catch_err) { t.fail('Testing of channel events has failed with ' + catch_err); } @@ -595,6 +916,45 @@ test('***** Test channel events', async (t) => { t.end(); }); +class TxEventChecker { + constructor (t) { + this._resolve = null; + this._reject = null; + this._timeout = null; + this._tx_id = null; + this._t = t; + } + + setResolve(resolve) { + this._resolve = resolve; + } + + setReject(reject) { + this._reject = reject; + } + + setTimeout(timeout) { + this._timeout = timeout; + } + + setTransactionId(tx_id) { + this._tx_id = tx_id; + } + + check(tx_id, code, block_num) { + if (tx_id === this._tx_id) { + this._t.pass('Successfully received the transaction on block_num ' + block_num); + clearTimeout(this._timeout); + this._resolve(code); + } + } + + error(error) { + this._t.fail('This listener callback got an error :: ' + error); + clearTimeout(this._timeout); + this._reject(error); + } +} function createChaincodeRegistration(t, message, event_hub, chaincode_id, chaincode_eventname) { const event_monitor = new Promise((resolve, reject) => { @@ -633,20 +993,19 @@ function createChaincodeRegistration(t, message, event_hub, chaincode_id, chainc function checkResults(t, proposalResponses) { let all_good = true; - for (const i in proposalResponses) { + for (const proposalResponse of proposalResponses) { let one_good = false; - if (proposalResponses && - proposalResponses[i].response && - proposalResponses[i].response.status === 200) { + if (proposalResponse instanceof Error) { + t.fail(proposalResponse.toString()); + } else if (proposalResponse.response && proposalResponse.response.status === 200) { one_good = true; + } else if (proposalResponse.response) { + t.fail ('response:' + proposalResponse.response); + } else { + t.fail('Received unknown response ::' + proposalResponse); } all_good = all_good & one_good; } - if (!all_good) { - t.fail('Failed to endorse the proposal'); - return false; - } - t.pass('Successfully endorsed the proposal'); - return true; + return all_good; } diff --git a/test/unit/commit-handler.js b/test/unit/commit-handler.js index efe71df392..2fece029b1 100644 --- a/test/unit/commit-handler.js +++ b/test/unit/commit-handler.js @@ -32,6 +32,20 @@ test('\n\n ** BasicCommitHandler - test **\n\n', async (t) => { client.setStateStore(store); await TestUtil.setAdmin(client, 'org1'); const channel = client.newChannel('handlertest'); + + // checking that we can not get the handler + try { + await channel.initialize({commitHandler:'bad.js'}); + t.fail('Should not be here - commiHandler name is bad '); + } catch (error) { + if (error.toString().includes('find module')) { + t.pass('Successfully failed to initialize using the commitHandler file name ::' + error); + } else { + t.fail('Received an unknown error ::' + error); + } + } + + // get a good handler try { await channel.initialize(); } catch (error) { @@ -47,17 +61,6 @@ test('\n\n ** BasicCommitHandler - test **\n\n', async (t) => { return; } - try { - await channel.initialize({commitHandler:'bad.js'}); - t.fail('Should not be here - commiHandler name is bad '); - } catch (error) { - if (error.toString().includes('find module')) { - t.pass('Successfully failed to initialize using the commitHandler file name ::' + error); - } else { - t.fail('Received an unknown error ::' + error); - } - } - let parameters = null; await errorChecker(t, handler, parameters, 'Missing all'); parameters = {}; @@ -81,6 +84,8 @@ test('\n\n ** BasicCommitHandler - test **\n\n', async (t) => { } } + orderer.close(); + const request = {}; const envelope = {}; @@ -111,7 +116,7 @@ test('\n\n ** BasicCommitHandler - test **\n\n', async (t) => { t.fail('Should not be here - looking for connect deadline'); } catch (error) { if (error instanceof Error) { - if (error.toString().indexOf('Failed to connect before the deadline') > -1) { + if (error.toString().indexOf('Failed to connect before the deadline URL:grpc://somehost.com:6666') > -1) { t.pass('This should fail with ' + error.toString()); } else { t.fail('Did not get deadline error - got ' + error.toString()); @@ -121,11 +126,15 @@ test('\n\n ** BasicCommitHandler - test **\n\n', async (t) => { } } - t.pass('Completed the testing'); + // close all the connections + channel.close(); + if (temp) { Client.setConfigSetting('endorsement-handler-path', temp); } + + t.pass('Completed the testing'); t.end(); });