diff --git a/packages/composer-connector-hlfv1/lib/hlfconnection.js b/packages/composer-connector-hlfv1/lib/hlfconnection.js index 247de90617..efe6c632f4 100644 --- a/packages/composer-connector-hlfv1/lib/hlfconnection.js +++ b/packages/composer-connector-hlfv1/lib/hlfconnection.js @@ -19,7 +19,6 @@ const fs = require('fs-extra'); const HLFSecurityContext = require('./hlfsecuritycontext'); const HLFUtil = require('./hlfutil'); const HLFTxEventHandler = require('./hlftxeventhandler'); -const HLFQueryHandler = require('./hlfqueryhandler'); const Logger = require('composer-common').Logger; const path = require('path'); const temp = require('temp').track(); @@ -44,6 +43,8 @@ const installDependencies = { const chaincodePathSection = 'businessnetwork'; +let HLFQueryHandler; + /** * Class representing a connection to a business network running on Hyperledger * Fabric, using the hfc module. @@ -67,10 +68,18 @@ class HLFConnection extends Connection { /** * create a new Query Handler. * @param {HLFConnection} connection The connection to be used by the query handler. - * @return {HLFQueryManager} A new query manager. + * @param {string} queryHandlerImpl The query handler to require + * @return {HLFQueryHandler} A new query handler. */ - static createQueryHandler(connection) { - return new HLFQueryHandler(connection); + static createQueryHandler(connection, queryHandlerImpl) { + const method = 'createQueryHandler'; + if (typeof queryHandlerImpl === 'string') { + LOG.info(method, `attemping to load query handler module ${queryHandlerImpl}`); + HLFQueryHandler = require(queryHandlerImpl); + return new HLFQueryHandler(connection); + } else { + return new queryHandlerImpl(connection); + } } /** @@ -128,10 +137,18 @@ class HLFConnection extends Connection { this.caClient = caClient; this.initialized = false; this.commitTimeout = connectOptions['x-commitTimeout'] ? connectOptions['x-commitTimeout'] * 1000 : 300 * 1000; + LOG.debug(method, `commit timeout set to ${this.commitTimeout}`); + this.requiredEventHubs = isNaN(connectOptions['x-requiredEventHubs'] * 1) ? 1 : connectOptions['x-requiredEventHubs'] * 1; LOG.debug(method, `required event hubs set to ${this.requiredEventHubs}`); - this.queryHandler = HLFConnection.createQueryHandler(this); - LOG.debug(method, `commit timeout set to ${this.commitTimeout}`); + + let queryHandlerImpl = './hlfqueryhandler'; + if (process.env.COMPOSER_QUERY_HANDLER && process.env.COMPOSER_QUERY_HANDLER.length !== 0) { + queryHandlerImpl = process.env.COMPOSER_QUERY_HANDLER; + } else if (connectOptions.queryHandler && connectOptions.queryHandler.length !== 0) { + queryHandlerImpl = connectOptions.queryHandler; + } + this.queryHandler = HLFConnection.createQueryHandler(this, queryHandlerImpl); // We create promisified versions of these APIs. this.fs = thenifyAll(fs); @@ -1337,6 +1354,88 @@ class HLFConnection extends Connection { return txId; } + /** + * Send a query + * @param {Peer} peer The peer to query + * @param {TransactionID} txId the transaction id to use + * @param {string} functionName the function name of the query + * @param {array} args the arguments to ass + * @returns {Buffer} asynchronous response to query + */ + async querySinglePeer(peer, txId, functionName, args) { + const method = 'querySinglePeer'; + LOG.entry(method, peer.getName(), txId, functionName, args); + const request = { + targets: [peer], + chaincodeId: this.businessNetworkIdentifier, + txId: txId, + fcn: functionName, + args: args + }; + + const t0 = Date.now(); + let payloads = await this.queryByChaincode(request); + LOG.perf(method, `Total duration for queryByChaincode to ${functionName}: `, txId, t0); + LOG.debug(method, `Received ${payloads.length} payloads(s) from querying the composer runtime chaincode`); + if (!payloads.length) { + LOG.error(method, 'No payloads were returned from the query request:' + functionName); + throw new Error('No payloads were returned from the query request:' + functionName); + } + const payload = payloads[0]; + + // + // need to also handle the grpc error codes as before, but now need to handle the change in the + // node-sdk with a horrible match a string error, but would need a fix to node-sdk to resolve. + // A Fix is in 1.3 + if (payload instanceof Error && ( + (payload.code && (payload.code === 14 || payload.code === 1 || payload.code === 4)) || + (payload.message.match(/Failed to connect before the deadline/)) + )) { + throw payload; + } + + LOG.exit(method, payload); + return payload; + + } + + /** + * Perform a chaincode query and parse the responses. + * @param {object} request the proposal for a query + * @return {array} the responses + */ + async queryByChaincode(request) { + const method = 'queryByChaincode'; + LOG.entry(method, request); + try { + const results = await this.channel.sendTransactionProposal(request); + const responses = results[0]; + if (responses && Array.isArray(responses)) { + let results = []; + for (let i = 0; i < responses.length; i++) { + let response = responses[i]; + if (response instanceof Error) { + results.push(response); + } + else if (response.response && response.response.payload) { + results.push(response.response.payload); + } + else { + results.push(new Error(response)); + } + } + LOG.exit(method); + return results; + } + const err = new Error('Payload results are missing from the chaincode query'); + LOG.error(method, err); + throw err; + } catch(err) { + LOG.error(method, err); + throw err; + } + } + } module.exports = HLFConnection; diff --git a/packages/composer-connector-hlfv1/lib/hlfqueryhandler.js b/packages/composer-connector-hlfv1/lib/hlfqueryhandler.js index e5c2550059..a4178cdb81 100644 --- a/packages/composer-connector-hlfv1/lib/hlfqueryhandler.js +++ b/packages/composer-connector-hlfv1/lib/hlfqueryhandler.js @@ -73,7 +73,7 @@ class HLFQueryHandler { if (this.queryPeerIndex !== -1) { let peer = this.allQueryPeers[this.queryPeerIndex]; try { - payload = await this.querySinglePeer(peer, txId, functionName, args); + payload = await this.connection.querySinglePeer(peer, txId, functionName, args); success = true; } catch (error) { allErrors.push(error); @@ -93,7 +93,7 @@ class HLFQueryHandler { } let peer = this.allQueryPeers[i]; try { - payload = await this.querySinglePeer(peer, txId, functionName, args); + payload = await this.connection.querySinglePeer(peer, txId, functionName, args); this.queryPeerIndex = i; success = true; break; @@ -119,88 +119,6 @@ class HLFQueryHandler { return payload; } - - /** - * Send a query - * @param {Peer} peer The peer to query - * @param {TransactionID} txId the transaction id to use - * @param {string} functionName the function name of the query - * @param {array} args the arguments to ass - * @returns {Buffer} asynchronous response to query - */ - async querySinglePeer(peer, txId, functionName, args) { - const method = 'querySinglePeer'; - LOG.entry(method, peer.getName(), txId, functionName, args); - const request = { - targets: [peer], - chaincodeId: this.connection.businessNetworkIdentifier, - txId: txId, - fcn: functionName, - args: args - }; - - const t0 = Date.now(); - let payloads = await this.queryByChaincode(request); - LOG.perf(method, `Total duration for queryByChaincode to ${functionName}: `, txId, t0); - LOG.debug(method, `Received ${payloads.length} payloads(s) from querying the composer runtime chaincode`); - if (!payloads.length) { - LOG.error(method, 'No payloads were returned from the query request:' + functionName); - throw new Error('No payloads were returned from the query request:' + functionName); - } - const payload = payloads[0]; - - // - // need to also handle the grpc error codes as before, but now need to handle the change in the - // node-sdk with a horrible match a string error, but would need a fix to node-sdk to resolve. - // A Fix is in 1.3 - if (payload instanceof Error && ( - (payload.code && (payload.code === 14 || payload.code === 1 || payload.code === 4)) || - (payload.message.match(/Failed to connect before the deadline/)) - )) { - throw payload; - } - - LOG.exit(method, payload); - return payload; - - } - - /** - * Perform a chaincode query and parse the responses. - * @param {object} request the proposal for a query - * @return {array} the responses - */ - async queryByChaincode(request) { - const method = 'queryByChaincode'; - LOG.entry(method, request); - try { - const results = await this.connection.channel.sendTransactionProposal(request); - const responses = results[0]; - if (responses && Array.isArray(responses)) { - let results = []; - for (let i = 0; i < responses.length; i++) { - let response = responses[i]; - if (response instanceof Error) { - results.push(response); - } - else if (response.response && response.response.payload) { - results.push(response.response.payload); - } - else { - results.push(new Error(response)); - } - } - LOG.exit(method); - return results; - } - const err = new Error('Payload results are missing from the chaincode query'); - LOG.error(method, err); - throw err; - } catch(err) { - LOG.error(method, err); - throw err; - } - } } module.exports = HLFQueryHandler; diff --git a/packages/composer-connector-hlfv1/test/hlfconnection.js b/packages/composer-connector-hlfv1/test/hlfconnection.js index b34f2294e4..266d09334a 100644 --- a/packages/composer-connector-hlfv1/test/hlfconnection.js +++ b/packages/composer-connector-hlfv1/test/hlfconnection.js @@ -122,7 +122,7 @@ describe('HLFConnection', () => { describe('#createQueryHandler', () => { - it('should create a new query handler', () => { + it('should create a new query handler from a string definition', () => { // restore the createQueryHandler implementation as by default it is mocked out. sandbox.restore(); sandbox = sinon.sandbox.create(); @@ -130,7 +130,19 @@ describe('HLFConnection', () => { mockConnection.getChannelPeersInOrg.returns([]); mockConnection.channel = mockChannel; mockChannel.getPeers.returns([]); - let queryHandler = HLFConnection.createQueryHandler(mockConnection); + let queryHandler = HLFConnection.createQueryHandler(mockConnection, '../lib/hlfqueryhandler'); + queryHandler.should.be.an.instanceOf(HLFQueryHandler); + }); + + it('should create a new query handler from an already loaded module', () => { + // restore the createQueryHandler implementation as by default it is mocked out. + sandbox.restore(); + sandbox = sinon.sandbox.create(); + let mockConnection = sinon.createStubInstance(HLFConnection); + mockConnection.getChannelPeersInOrg.returns([]); + mockConnection.channel = mockChannel; + mockChannel.getPeers.returns([]); + let queryHandler = HLFConnection.createQueryHandler(mockConnection, HLFQueryHandler); queryHandler.should.be.an.instanceOf(HLFQueryHandler); }); @@ -206,6 +218,30 @@ describe('HLFConnection', () => { connection = new HLFConnection(mockConnectionManager, 'hlfabric1', 'test-business-network', {'x-requiredEventHubs': 'fred'}, mockClient, mockChannel, mockCAClient); sinon.assert.calledWith(logInfoSpy, 'constructor', sinon.match(/test-business-network/)); }); + + it('should default to the built in query handler if no override specified', () => { + connection = new HLFConnection(mockConnectionManager, 'hlfabric1', null, {}, mockClient, mockChannel, mockCAClient); + sinon.assert.calledWith(HLFConnection.createQueryHandler, connection, './hlfqueryhandler'); + }); + + it('should use the connection options for query handler to load if specified', () => { + connection = new HLFConnection(mockConnectionManager, 'hlfabric1', null, {queryHandler: 'myHandler'}, mockClient, mockChannel, mockCAClient); + sinon.assert.calledWith(HLFConnection.createQueryHandler, connection, 'myHandler'); + }); + + it('should use the environment variable for query handler if set', () => { + process.env.COMPOSER_QUERY_HANDLER = 'anotherHandler'; + connection = new HLFConnection(mockConnectionManager, 'hlfabric1', null, {}, mockClient, mockChannel, mockCAClient); + sinon.assert.calledWith(HLFConnection.createQueryHandler, connection, 'anotherHandler'); + delete process.env.COMPOSER_QUERY_HANDLER; + }); + + it('should prioritise env var defined query handler over code defined query handler', () => { + process.env.COMPOSER_QUERY_HANDLER = 'priorityHandler'; + connection = new HLFConnection(mockConnectionManager, 'hlfabric1', null, {queryHandler: 'myHandler'}, mockClient, mockChannel, mockCAClient); + sinon.assert.calledWith(HLFConnection.createQueryHandler, connection, 'priorityHandler'); + delete process.env.COMPOSER_QUERY_HANDLER; + }); }); describe('#_connectToEventHubs', () => { @@ -2714,4 +2750,195 @@ describe('HLFConnection', () => { }); + describe('#querySinglePeer', () => { + + it('should query a single peer', async () => { + const response = Buffer.from('hello world'); + sandbox.stub(connection, 'queryByChaincode').resolves([response]); + let result = await connection.querySinglePeer(mockPeer2, mockTransactionID, 'myfunc', ['arg1', 'arg2']); + sinon.assert.calledOnce(connection.queryByChaincode); + sinon.assert.calledWith(connection.queryByChaincode, { + chaincodeId: 'org-acme-biznet', + txId: mockTransactionID, + fcn: 'myfunc', + args: ['arg1', 'arg2'], + targets: [mockPeer2] + }); + result.equals(response).should.be.true; + + }); + + it('should throw if no responses are returned', () => { + sandbox.stub(connection, 'queryByChaincode').resolves([]); + return connection.querySinglePeer(mockPeer2, 'txid', 'myfunc', ['arg1', 'arg2']) + .should.be.rejectedWith(/No payloads were returned from the query request/); + }); + + it('should return any responses that are errors and not UNAVAILABLE', async () => { + const response = new Error('such error'); + sandbox.stub(connection, 'queryByChaincode').resolves([response]); + let result = await connection.querySinglePeer(mockPeer2, mockTransactionID, 'myfunc', ['arg1', 'arg2']); + sinon.assert.calledOnce(connection.queryByChaincode); + sinon.assert.calledWith(connection.queryByChaincode, { + chaincodeId: 'org-acme-biznet', + txId: mockTransactionID, + fcn: 'myfunc', + args: ['arg1', 'arg2'], + targets: [mockPeer2] + }); + result.should.be.instanceOf(Error); + result.message.should.equal('such error'); + }); + + it('should throw any responses that are errors and code 14 being unavailable.', () => { + const response = new Error('14 UNAVAILABLE: Connect Failed'); + response.code = 14; + sandbox.stub(connection, 'queryByChaincode').resolves([response]); + return connection.querySinglePeer(mockPeer2, 'txid', 'myfunc', ['arg1', 'arg2']) + .should.be.rejectedWith(/Connect Failed/); + }); + + it('should throw any responses that are errors and code 1 being unavailable.', () => { + const response = new Error('1 UNAVAILABLE: Connect Failed'); + response.code = 1; + sandbox.stub(connection, 'queryByChaincode').resolves([response]); + return connection.querySinglePeer(mockPeer2, 'txid', 'myfunc', ['arg1', 'arg2']) + .should.be.rejectedWith(/Connect Failed/); + }); + + it('should throw any responses that are errors and code 4 being unavailable.', () => { + const response = new Error('4 UNAVAILABLE: Connect Failed'); + response.code = 4; + sandbox.stub(connection, 'queryByChaincode').resolves([response]); + return connection.querySinglePeer(mockPeer2, 'txid', 'myfunc', ['arg1', 'arg2']) + .should.be.rejectedWith(/Connect Failed/); + }); + + it('should throw if query request fails', () => { + sandbox.stub(connection, 'queryByChaincode').rejects(new Error('Query Failed')); + return connection.querySinglePeer(mockPeer2, 'txid', 'myfunc', ['arg1', 'arg2']) + .should.be.rejectedWith(/Query Failed/); + }); + }); + + describe('#queryByChaincode', () => { + it('should handle single good response', async () => { + const request = { + id: 1 + }; + const results = [ + [{ + response: { + payload: 'some payload' + } + }] + ]; + mockChannel.sendTransactionProposal.resolves(results); + const responses = await connection.queryByChaincode(request); + sinon.assert.calledOnce(mockChannel.sendTransactionProposal); + sinon.assert.calledWith(mockChannel.sendTransactionProposal, request); + responses.length.should.equal(1); + responses[0].should.equal('some payload'); + }); + + it('should handle multiple good responses', async () => { + const request = { + id: 1 + }; + const results = [[ + { + response: { + payload: 'some payload' + } + }, + { + response: { + payload: 'another payload' + } + }, + { + response: { + payload: 'final payload' + } + } + ]]; + mockChannel.sendTransactionProposal.resolves(results); + const responses = await connection.queryByChaincode(request); + sinon.assert.calledOnce(mockChannel.sendTransactionProposal); + sinon.assert.calledWith(mockChannel.sendTransactionProposal, request); + responses.length.should.equal(3); + responses[0].should.equal('some payload'); + responses[1].should.equal('another payload'); + responses[2].should.equal('final payload'); + }); + + it('should handle single error response', async () => { + const request = { + id: 1 + }; + const results = [ + [ new Error('some error') ] + ]; + mockChannel.sendTransactionProposal.resolves(results); + const responses = await connection.queryByChaincode(request); + sinon.assert.calledOnce(mockChannel.sendTransactionProposal); + sinon.assert.calledWith(mockChannel.sendTransactionProposal, request); + responses.length.should.equal(1); + responses[0].should.be.instanceOf(Error); + responses[0].message.should.equal('some error'); + }); + + it('should handle multiple different response types', async () => { + const request = { + id: 1 + }; + const results = [[ + + new Error('some error'), + + { + response: { + payload: 'another payload' + } + }, + { + response: 'a strange error' + }, + { + data: 'I am not just an android' + } + ]]; + mockChannel.sendTransactionProposal.resolves(results); + const responses = await connection.queryByChaincode(request); + sinon.assert.calledOnce(mockChannel.sendTransactionProposal); + sinon.assert.calledWith(mockChannel.sendTransactionProposal, request); + responses.length.should.equal(4); + responses[0].should.be.instanceOf(Error); + responses[0].message.should.equal('some error'); + responses[1].should.equal('another payload'); + responses[2].should.be.instanceOf(Error); + responses[3].should.be.instanceOf(Error); + }); + + it('should handle no responses', async () => { + const request = { + id: 1 + }; + let results = []; + mockChannel.sendTransactionProposal.resolves(results); + await connection.queryByChaincode(request).should.be.rejectedWith(/Payload results are missing/); + results = ['not an array']; + mockChannel.sendTransactionProposal.resolves(results); + await connection.queryByChaincode(request).should.be.rejectedWith(/Payload results are missing/); + }); + + it('should handle error from sendTransactionProposal', async () => { + const request = { + id: 1 + }; + mockChannel.sendTransactionProposal.rejects(new Error('sendTxProp error')); + await connection.queryByChaincode(request).should.be.rejectedWith(/sendTxProp error/); + }); + }); + }); diff --git a/packages/composer-connector-hlfv1/test/hlfqueryhandler.js b/packages/composer-connector-hlfv1/test/hlfqueryhandler.js index 231d84fdb8..973ba3cca1 100644 --- a/packages/composer-connector-hlfv1/test/hlfqueryhandler.js +++ b/packages/composer-connector-hlfv1/test/hlfqueryhandler.js @@ -73,47 +73,45 @@ describe('HLFQueryHandler', () => { it('should not switch to another peer if peer returns a payload which is an error', async () => { const response = new Error('my chaincode error'); - sandbox.stub(queryHandler, 'queryByChaincode').resolves([response]); - let qspSpy = sinon.spy(queryHandler, 'querySinglePeer'); + mockConnection.querySinglePeer.resolves(response); try { await queryHandler.queryChaincode(mockTransactionID, 'myfunc', ['arg1', 'arg2']); should.fail('expected error to be thrown'); } catch(error) { error.message.should.equal('my chaincode error'); - sinon.assert.calledOnce(qspSpy); - sinon.assert.calledWith(qspSpy, mockPeer2, mockTransactionID, 'myfunc', ['arg1', 'arg2']); + sinon.assert.calledOnce(mockConnection.querySinglePeer); + sinon.assert.calledWith(mockConnection.querySinglePeer, mockPeer2, mockTransactionID, 'myfunc', ['arg1', 'arg2']); queryHandler.queryPeerIndex.should.equal(0); } }); - it('should choose a valid peer', async () => { const response = Buffer.from('hello world'); - sandbox.stub(queryHandler, 'querySinglePeer').resolves(response); + mockConnection.querySinglePeer.resolves(response); let result = await queryHandler.queryChaincode(mockTransactionID, 'myfunc', ['arg1', 'arg2']); - sinon.assert.calledOnce(queryHandler.querySinglePeer); - sinon.assert.calledWith(queryHandler.querySinglePeer, mockPeer2, mockTransactionID, 'myfunc', ['arg1', 'arg2']); + sinon.assert.calledOnce(mockConnection.querySinglePeer); + sinon.assert.calledWith(mockConnection.querySinglePeer, mockPeer2, mockTransactionID, 'myfunc', ['arg1', 'arg2']); queryHandler.queryPeerIndex.should.equal(0); result.equals(response).should.be.true; }); it('should cache a valid peer and reuse', async () => { const response = Buffer.from('hello world'); - sandbox.stub(queryHandler, 'querySinglePeer').resolves(response); + mockConnection.querySinglePeer.resolves(response); await queryHandler.queryChaincode(mockTransactionID, 'myfunc', ['arg1', 'arg2']); let result = await queryHandler.queryChaincode(mockTransactionID, 'myfunc', ['arg1', 'arg2']); - sinon.assert.calledTwice(queryHandler.querySinglePeer); - sinon.assert.alwaysCalledWith(queryHandler.querySinglePeer, mockPeer2, mockTransactionID, 'myfunc', ['arg1', 'arg2']); + sinon.assert.calledTwice(mockConnection.querySinglePeer); + sinon.assert.alwaysCalledWith(mockConnection.querySinglePeer, mockPeer2, mockTransactionID, 'myfunc', ['arg1', 'arg2']); queryHandler.queryPeerIndex.should.equal(0); result.equals(response).should.be.true; }); it('should choose a valid peer if any respond with an error', async () => { const response = Buffer.from('hello world'); - const qsp = sandbox.stub(queryHandler, 'querySinglePeer'); + const qsp = mockConnection.querySinglePeer; /* this didn't work as the mockPeers look the same qsp.withArgs(mockPeer2, 'aTxID', 'myfunc', ['arg1', 'arg2']).rejects(new Error('I failed')); @@ -133,7 +131,7 @@ describe('HLFQueryHandler', () => { it('should handle when the last successful peer fails', async () => { const response = Buffer.from('hello world'); - const qsp = sandbox.stub(queryHandler, 'querySinglePeer'); + const qsp = mockConnection.querySinglePeer; qsp.onFirstCall().resolves(response); qsp.onSecondCall().rejects(new Error('I failed')); qsp.onThirdCall().resolves(response); @@ -142,17 +140,17 @@ describe('HLFQueryHandler', () => { result.equals(response).should.be.true; result = await queryHandler.queryChaincode(mockTransactionID, 'myfunc', ['arg1', 'arg2']); result.equals(response).should.be.true; - sinon.assert.calledThrice(queryHandler.querySinglePeer); - sinon.assert.calledWith(queryHandler.querySinglePeer.firstCall, mockPeer2, mockTransactionID, 'myfunc', ['arg1', 'arg2']); - sinon.assert.calledWith(queryHandler.querySinglePeer.secondCall, mockPeer2, mockTransactionID, 'myfunc', ['arg1', 'arg2']); - sinon.assert.calledWith(queryHandler.querySinglePeer.thirdCall, mockPeer1, mockTransactionID, 'myfunc', ['arg1', 'arg2']); + sinon.assert.calledThrice(qsp); + sinon.assert.calledWith(qsp.firstCall, mockPeer2, mockTransactionID, 'myfunc', ['arg1', 'arg2']); + sinon.assert.calledWith(qsp.secondCall, mockPeer2, mockTransactionID, 'myfunc', ['arg1', 'arg2']); + sinon.assert.calledWith(qsp.thirdCall, mockPeer1, mockTransactionID, 'myfunc', ['arg1', 'arg2']); queryHandler.queryPeerIndex.should.equal(1); result.equals(response).should.be.true; }); it('should throw if all peers respond with errors', () => { - const qsp = sandbox.stub(queryHandler, 'querySinglePeer'); + const qsp = mockConnection.querySinglePeer; qsp.onFirstCall().rejects(new Error('I failed 1')); qsp.onSecondCall().rejects(new Error('I failed 2')); qsp.onThirdCall().rejects(new Error('I failed 3')); @@ -170,208 +168,4 @@ describe('HLFQueryHandler', () => { .should.be.rejectedWith(/No peers have been provided/); }); }); - - describe('#querySinglePeer', () => { - - it('should query a single peer', async () => { - const response = Buffer.from('hello world'); - sandbox.stub(queryHandler, 'queryByChaincode').resolves([response]); - mockConnection.businessNetworkIdentifier = 'org-acme-biznet'; - let result = await queryHandler.querySinglePeer(mockPeer2, mockTransactionID, 'myfunc', ['arg1', 'arg2']); - sinon.assert.calledOnce(queryHandler.queryByChaincode); - sinon.assert.calledWith(queryHandler.queryByChaincode, { - chaincodeId: 'org-acme-biznet', - txId: mockTransactionID, - fcn: 'myfunc', - args: ['arg1', 'arg2'], - targets: [mockPeer2] - }); - result.equals(response).should.be.true; - - }); - - it('should throw if no responses are returned', () => { - sandbox.stub(queryHandler, 'queryByChaincode').resolves([]); - return queryHandler.querySinglePeer(mockPeer2, 'txid', 'myfunc', ['arg1', 'arg2']) - .should.be.rejectedWith(/No payloads were returned from the query request/); - }); - - it('should return any responses that are errors and not UNAVAILABLE', async () => { - const response = new Error('such error'); - sandbox.stub(queryHandler, 'queryByChaincode').resolves([response]); - mockConnection.businessNetworkIdentifier = 'org-acme-biznet'; - let result = await queryHandler.querySinglePeer(mockPeer2, mockTransactionID, 'myfunc', ['arg1', 'arg2']); - sinon.assert.calledOnce(queryHandler.queryByChaincode); - sinon.assert.calledWith(queryHandler.queryByChaincode, { - chaincodeId: 'org-acme-biznet', - txId: mockTransactionID, - fcn: 'myfunc', - args: ['arg1', 'arg2'], - targets: [mockPeer2] - }); - result.should.be.instanceOf(Error); - result.message.should.equal('such error'); - }); - - it('should throw any responses that are errors and code 14 being unavailable.', () => { - const response = new Error('14 UNAVAILABLE: Connect Failed'); - response.code = 14; - sandbox.stub(queryHandler, 'queryByChaincode').resolves([response]); - mockConnection.businessNetworkIdentifier = 'org-acme-biznet'; - return queryHandler.querySinglePeer(mockPeer2, 'txid', 'myfunc', ['arg1', 'arg2']) - .should.be.rejectedWith(/Connect Failed/); - }); - - it('should throw any responses that are errors and code 1 being unavailable.', () => { - const response = new Error('1 UNAVAILABLE: Connect Failed'); - response.code = 1; - sandbox.stub(queryHandler, 'queryByChaincode').resolves([response]); - mockConnection.businessNetworkIdentifier = 'org-acme-biznet'; - return queryHandler.querySinglePeer(mockPeer2, 'txid', 'myfunc', ['arg1', 'arg2']) - .should.be.rejectedWith(/Connect Failed/); - }); - - it('should throw any responses that are errors and code 4 being unavailable.', () => { - const response = new Error('4 UNAVAILABLE: Connect Failed'); - response.code = 4; - sandbox.stub(queryHandler, 'queryByChaincode').resolves([response]); - mockConnection.businessNetworkIdentifier = 'org-acme-biznet'; - return queryHandler.querySinglePeer(mockPeer2, 'txid', 'myfunc', ['arg1', 'arg2']) - .should.be.rejectedWith(/Connect Failed/); - }); - - it('should throw any responses that are exceeded deadline responses.', () => { - const response = new Error('Failed to connect before the deadline'); - sandbox.stub(queryHandler, 'queryByChaincode').resolves([response]); - mockConnection.businessNetworkIdentifier = 'org-acme-biznet'; - return queryHandler.querySinglePeer(mockPeer2, 'txid', 'myfunc', ['arg1', 'arg2']) - .should.be.rejectedWith(/Failed to connect before the deadline/); - }); - - it('should throw if query request fails', () => { - sandbox.stub(queryHandler, 'queryByChaincode').rejects(new Error('Query Failed')); - return queryHandler.querySinglePeer(mockPeer2, 'txid', 'myfunc', ['arg1', 'arg2']) - .should.be.rejectedWith(/Query Failed/); - }); - }); - - describe('#queryByChaincode', () => { - it('should handle single good response', async () => { - const request = { - id: 1 - }; - const results = [ - [{ - response: { - payload: 'some payload' - } - }] - ]; - mockChannel.sendTransactionProposal.resolves(results); - const responses = await queryHandler.queryByChaincode(request); - sinon.assert.calledOnce(mockChannel.sendTransactionProposal); - sinon.assert.calledWith(mockChannel.sendTransactionProposal, request); - responses.length.should.equal(1); - responses[0].should.equal('some payload'); - }); - - it('should handle multiple good responses', async () => { - const request = { - id: 1 - }; - const results = [[ - { - response: { - payload: 'some payload' - } - }, - { - response: { - payload: 'another payload' - } - }, - { - response: { - payload: 'final payload' - } - } - ]]; - mockChannel.sendTransactionProposal.resolves(results); - const responses = await queryHandler.queryByChaincode(request); - sinon.assert.calledOnce(mockChannel.sendTransactionProposal); - sinon.assert.calledWith(mockChannel.sendTransactionProposal, request); - responses.length.should.equal(3); - responses[0].should.equal('some payload'); - responses[1].should.equal('another payload'); - responses[2].should.equal('final payload'); - }); - - it('should handle single error response', async () => { - const request = { - id: 1 - }; - const results = [ - [ new Error('some error') ] - ]; - mockChannel.sendTransactionProposal.resolves(results); - const responses = await queryHandler.queryByChaincode(request); - sinon.assert.calledOnce(mockChannel.sendTransactionProposal); - sinon.assert.calledWith(mockChannel.sendTransactionProposal, request); - responses.length.should.equal(1); - responses[0].should.be.instanceOf(Error); - responses[0].message.should.equal('some error'); - }); - - it('should handle multiple different response types', async () => { - const request = { - id: 1 - }; - const results = [[ - - new Error('some error'), - - { - response: { - payload: 'another payload' - } - }, - { - response: 'a strange error' - }, - { - data: 'I am not just an android' - } - ]]; - mockChannel.sendTransactionProposal.resolves(results); - const responses = await queryHandler.queryByChaincode(request); - sinon.assert.calledOnce(mockChannel.sendTransactionProposal); - sinon.assert.calledWith(mockChannel.sendTransactionProposal, request); - responses.length.should.equal(4); - responses[0].should.be.instanceOf(Error); - responses[0].message.should.equal('some error'); - responses[1].should.equal('another payload'); - responses[2].should.be.instanceOf(Error); - responses[3].should.be.instanceOf(Error); - }); - - it('should handle no responses', async () => { - const request = { - id: 1 - }; - let results = []; - mockChannel.sendTransactionProposal.resolves(results); - await queryHandler.queryByChaincode(request).should.be.rejectedWith(/Payload results are missing/); - results = ['not an array']; - mockChannel.sendTransactionProposal.resolves(results); - await queryHandler.queryByChaincode(request).should.be.rejectedWith(/Payload results are missing/); - }); - - it('should handle error from sendTransactionProposal', async () => { - const request = { - id: 1 - }; - mockChannel.sendTransactionProposal.rejects(new Error('sendTxProp error')); - await queryHandler.queryByChaincode(request).should.be.rejectedWith(/sendTxProp error/); - }); - }); });