Skip to content
This repository was archived by the owner on Mar 8, 2020. It is now read-only.

Commit a976290

Browse files
author
Dave Kelsey
authored
[Master] Allow txid to be passed to query requests (#4500)
Signed-off-by: Dave Kelsey <d_kelsey@uk.ibm.com>
1 parent 1f54125 commit a976290

File tree

2 files changed

+171
-16
lines changed

2 files changed

+171
-16
lines changed

packages/composer-connector-hlfv1/lib/hlfqueryhandler.js

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,8 @@ class HLFQueryHandler {
140140
};
141141

142142
const t0 = Date.now();
143-
let payloads = await this.connection.channel.queryByChaincode(request);
144-
LOG.perf(method, `Total duration for node-sdk queryByChaincode to ${functionName}: `, txId, t0);
143+
let payloads = await this.queryByChaincode(request);
144+
LOG.perf(method, `Total duration for queryByChaincode to ${functionName}: `, txId, t0);
145145
LOG.debug(method, `Received ${payloads.length} payloads(s) from querying the composer runtime chaincode`);
146146
if (!payloads.length) {
147147
LOG.error(method, 'No payloads were returned from the query request:' + functionName);
@@ -164,6 +164,43 @@ class HLFQueryHandler {
164164
return payload;
165165

166166
}
167+
168+
/**
169+
* Perform a chaincode query and parse the responses.
170+
* @param {object} request the proposal for a query
171+
* @return {array} the responses
172+
*/
173+
async queryByChaincode(request) {
174+
const method = 'queryByChaincode';
175+
LOG.entry(method, request);
176+
try {
177+
const results = await this.connection.channel.sendTransactionProposal(request);
178+
const responses = results[0];
179+
if (responses && Array.isArray(responses)) {
180+
let results = [];
181+
for (let i = 0; i < responses.length; i++) {
182+
let response = responses[i];
183+
if (response instanceof Error) {
184+
results.push(response);
185+
}
186+
else if (response.response && response.response.payload) {
187+
results.push(response.response.payload);
188+
}
189+
else {
190+
results.push(new Error(response));
191+
}
192+
}
193+
LOG.exit(method);
194+
return results;
195+
}
196+
const err = new Error('Payload results are missing from the chaincode query');
197+
LOG.error(method, err);
198+
throw err;
199+
} catch(err) {
200+
LOG.error(method, err);
201+
throw err;
202+
}
203+
}
167204
}
168205

169206
module.exports = HLFQueryHandler;

packages/composer-connector-hlfv1/test/hlfqueryhandler.js

Lines changed: 132 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ describe('HLFQueryHandler', () => {
7373

7474
it('should not switch to another peer if peer returns a payload which is an error', async () => {
7575
const response = new Error('my chaincode error');
76-
mockChannel.queryByChaincode.resolves([response]);
76+
sandbox.stub(queryHandler, 'queryByChaincode').resolves([response]);
7777
let qspSpy = sinon.spy(queryHandler, 'querySinglePeer');
7878
try {
7979
await queryHandler.queryChaincode(mockTransactionID, 'myfunc', ['arg1', 'arg2']);
@@ -175,11 +175,11 @@ describe('HLFQueryHandler', () => {
175175

176176
it('should query a single peer', async () => {
177177
const response = Buffer.from('hello world');
178-
mockChannel.queryByChaincode.resolves([response]);
178+
sandbox.stub(queryHandler, 'queryByChaincode').resolves([response]);
179179
mockConnection.businessNetworkIdentifier = 'org-acme-biznet';
180180
let result = await queryHandler.querySinglePeer(mockPeer2, mockTransactionID, 'myfunc', ['arg1', 'arg2']);
181-
sinon.assert.calledOnce(mockChannel.queryByChaincode);
182-
sinon.assert.calledWith(mockChannel.queryByChaincode, {
181+
sinon.assert.calledOnce(queryHandler.queryByChaincode);
182+
sinon.assert.calledWith(queryHandler.queryByChaincode, {
183183
chaincodeId: 'org-acme-biznet',
184184
txId: mockTransactionID,
185185
fcn: 'myfunc',
@@ -191,18 +191,18 @@ describe('HLFQueryHandler', () => {
191191
});
192192

193193
it('should throw if no responses are returned', () => {
194-
mockChannel.queryByChaincode.resolves([]);
194+
sandbox.stub(queryHandler, 'queryByChaincode').resolves([]);
195195
return queryHandler.querySinglePeer(mockPeer2, 'txid', 'myfunc', ['arg1', 'arg2'])
196196
.should.be.rejectedWith(/No payloads were returned from the query request/);
197197
});
198198

199199
it('should return any responses that are errors and not UNAVAILABLE', async () => {
200200
const response = new Error('such error');
201-
mockChannel.queryByChaincode.resolves([response]);
201+
sandbox.stub(queryHandler, 'queryByChaincode').resolves([response]);
202202
mockConnection.businessNetworkIdentifier = 'org-acme-biznet';
203203
let result = await queryHandler.querySinglePeer(mockPeer2, mockTransactionID, 'myfunc', ['arg1', 'arg2']);
204-
sinon.assert.calledOnce(mockChannel.queryByChaincode);
205-
sinon.assert.calledWith(mockChannel.queryByChaincode, {
204+
sinon.assert.calledOnce(queryHandler.queryByChaincode);
205+
sinon.assert.calledWith(queryHandler.queryByChaincode, {
206206
chaincodeId: 'org-acme-biznet',
207207
txId: mockTransactionID,
208208
fcn: 'myfunc',
@@ -216,7 +216,7 @@ describe('HLFQueryHandler', () => {
216216
it('should throw any responses that are errors and code 14 being unavailable.', () => {
217217
const response = new Error('14 UNAVAILABLE: Connect Failed');
218218
response.code = 14;
219-
mockChannel.queryByChaincode.resolves([response]);
219+
sandbox.stub(queryHandler, 'queryByChaincode').resolves([response]);
220220
mockConnection.businessNetworkIdentifier = 'org-acme-biznet';
221221
return queryHandler.querySinglePeer(mockPeer2, 'txid', 'myfunc', ['arg1', 'arg2'])
222222
.should.be.rejectedWith(/Connect Failed/);
@@ -225,7 +225,7 @@ describe('HLFQueryHandler', () => {
225225
it('should throw any responses that are errors and code 1 being unavailable.', () => {
226226
const response = new Error('1 UNAVAILABLE: Connect Failed');
227227
response.code = 1;
228-
mockChannel.queryByChaincode.resolves([response]);
228+
sandbox.stub(queryHandler, 'queryByChaincode').resolves([response]);
229229
mockConnection.businessNetworkIdentifier = 'org-acme-biznet';
230230
return queryHandler.querySinglePeer(mockPeer2, 'txid', 'myfunc', ['arg1', 'arg2'])
231231
.should.be.rejectedWith(/Connect Failed/);
@@ -234,26 +234,144 @@ describe('HLFQueryHandler', () => {
234234
it('should throw any responses that are errors and code 4 being unavailable.', () => {
235235
const response = new Error('4 UNAVAILABLE: Connect Failed');
236236
response.code = 4;
237-
mockChannel.queryByChaincode.resolves([response]);
237+
sandbox.stub(queryHandler, 'queryByChaincode').resolves([response]);
238238
mockConnection.businessNetworkIdentifier = 'org-acme-biznet';
239239
return queryHandler.querySinglePeer(mockPeer2, 'txid', 'myfunc', ['arg1', 'arg2'])
240240
.should.be.rejectedWith(/Connect Failed/);
241241
});
242242

243243
it('should throw any responses that are exceeded deadline responses.', () => {
244244
const response = new Error('Failed to connect before the deadline');
245-
mockChannel.queryByChaincode.resolves([response]);
245+
sandbox.stub(queryHandler, 'queryByChaincode').resolves([response]);
246246
mockConnection.businessNetworkIdentifier = 'org-acme-biznet';
247247
return queryHandler.querySinglePeer(mockPeer2, 'txid', 'myfunc', ['arg1', 'arg2'])
248248
.should.be.rejectedWith(/Failed to connect before the deadline/);
249249
});
250250

251-
252251
it('should throw if query request fails', () => {
253-
mockChannel.queryByChaincode.rejects(new Error('Query Failed'));
252+
sandbox.stub(queryHandler, 'queryByChaincode').rejects(new Error('Query Failed'));
254253
return queryHandler.querySinglePeer(mockPeer2, 'txid', 'myfunc', ['arg1', 'arg2'])
255254
.should.be.rejectedWith(/Query Failed/);
256255
});
257256
});
258257

258+
describe('#queryByChaincode', () => {
259+
it('should handle single good response', async () => {
260+
const request = {
261+
id: 1
262+
};
263+
const results = [
264+
[{
265+
response: {
266+
payload: 'some payload'
267+
}
268+
}]
269+
];
270+
mockChannel.sendTransactionProposal.resolves(results);
271+
const responses = await queryHandler.queryByChaincode(request);
272+
sinon.assert.calledOnce(mockChannel.sendTransactionProposal);
273+
sinon.assert.calledWith(mockChannel.sendTransactionProposal, request);
274+
responses.length.should.equal(1);
275+
responses[0].should.equal('some payload');
276+
});
277+
278+
it('should handle multiple good responses', async () => {
279+
const request = {
280+
id: 1
281+
};
282+
const results = [[
283+
{
284+
response: {
285+
payload: 'some payload'
286+
}
287+
},
288+
{
289+
response: {
290+
payload: 'another payload'
291+
}
292+
},
293+
{
294+
response: {
295+
payload: 'final payload'
296+
}
297+
}
298+
]];
299+
mockChannel.sendTransactionProposal.resolves(results);
300+
const responses = await queryHandler.queryByChaincode(request);
301+
sinon.assert.calledOnce(mockChannel.sendTransactionProposal);
302+
sinon.assert.calledWith(mockChannel.sendTransactionProposal, request);
303+
responses.length.should.equal(3);
304+
responses[0].should.equal('some payload');
305+
responses[1].should.equal('another payload');
306+
responses[2].should.equal('final payload');
307+
});
308+
309+
it('should handle single error response', async () => {
310+
const request = {
311+
id: 1
312+
};
313+
const results = [
314+
[ new Error('some error') ]
315+
];
316+
mockChannel.sendTransactionProposal.resolves(results);
317+
const responses = await queryHandler.queryByChaincode(request);
318+
sinon.assert.calledOnce(mockChannel.sendTransactionProposal);
319+
sinon.assert.calledWith(mockChannel.sendTransactionProposal, request);
320+
responses.length.should.equal(1);
321+
responses[0].should.be.instanceOf(Error);
322+
responses[0].message.should.equal('some error');
323+
});
324+
325+
it('should handle multiple different response types', async () => {
326+
const request = {
327+
id: 1
328+
};
329+
const results = [[
330+
331+
new Error('some error'),
332+
333+
{
334+
response: {
335+
payload: 'another payload'
336+
}
337+
},
338+
{
339+
response: 'a strange error'
340+
},
341+
{
342+
data: 'I am not just an android'
343+
}
344+
]];
345+
mockChannel.sendTransactionProposal.resolves(results);
346+
const responses = await queryHandler.queryByChaincode(request);
347+
sinon.assert.calledOnce(mockChannel.sendTransactionProposal);
348+
sinon.assert.calledWith(mockChannel.sendTransactionProposal, request);
349+
responses.length.should.equal(4);
350+
responses[0].should.be.instanceOf(Error);
351+
responses[0].message.should.equal('some error');
352+
responses[1].should.equal('another payload');
353+
responses[2].should.be.instanceOf(Error);
354+
responses[3].should.be.instanceOf(Error);
355+
});
356+
357+
it('should handle no responses', async () => {
358+
const request = {
359+
id: 1
360+
};
361+
let results = [];
362+
mockChannel.sendTransactionProposal.resolves(results);
363+
await queryHandler.queryByChaincode(request).should.be.rejectedWith(/Payload results are missing/);
364+
results = ['not an array'];
365+
mockChannel.sendTransactionProposal.resolves(results);
366+
await queryHandler.queryByChaincode(request).should.be.rejectedWith(/Payload results are missing/);
367+
});
368+
369+
it('should handle error from sendTransactionProposal', async () => {
370+
const request = {
371+
id: 1
372+
};
373+
mockChannel.sendTransactionProposal.rejects(new Error('sendTxProp error'));
374+
await queryHandler.queryByChaincode(request).should.be.rejectedWith(/sendTxProp error/);
375+
});
376+
});
259377
});

0 commit comments

Comments
 (0)