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

Commit b9edd1f

Browse files
author
Simon Stone
authored
Add integration tests for transaction return types and commit flag (contributes to #4165, #4224) (#4228)
Signed-off-by: Simon Stone <[email protected]>
1 parent 4d73e16 commit b9edd1f

File tree

13 files changed

+687
-145
lines changed

13 files changed

+687
-145
lines changed

packages/composer-cli/lib/cmds/transaction/lib/submit.js

Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
'use strict';
1616

1717
const cmdUtil = require('../../utils/cmdutils');
18+
const { Typed } = require('composer-common');
19+
const Pretty = require('prettyjson');
1820

1921
/**
2022
* <p>
@@ -28,41 +30,52 @@ class Submit {
2830
/**
2931
* Command process for deploy command
3032
* @param {string} argv argument list from composer command
31-
* @return {Promise} promise when command complete
3233
*/
33-
static handler(argv) {
34-
let businessNetworkConnection;
35-
let cardName = argv.card;
34+
static async handler(argv) {
35+
const cardName = argv.card;
36+
const businessNetworkConnection = cmdUtil.createBusinessNetworkConnection();
37+
await businessNetworkConnection.connect(cardName);
3638

37-
businessNetworkConnection = cmdUtil.createBusinessNetworkConnection();
38-
return businessNetworkConnection.connect(cardName)
39+
let data = argv.data;
40+
if (typeof data === 'string') {
41+
try {
42+
data = JSON.parse(data);
43+
} catch(e) {
44+
throw new Error('JSON error. Have you quoted the JSON string?', e);
45+
}
46+
} else {
47+
throw new Error('Data must be a string');
48+
}
49+
50+
if (!data.$class) {
51+
throw new Error('$class attribute not supplied');
52+
}
3953

40-
.then(() => {
41-
let data = argv.data;
54+
const businessNetwork = businessNetworkConnection.getBusinessNetwork();
55+
const serializer = businessNetwork.getSerializer();
56+
const resource = serializer.fromJSON(data);
4257

43-
if (typeof data === 'string') {
44-
try {
45-
data = JSON.parse(data);
46-
} catch(e) {
47-
throw new Error('JSON error. Have you quoted the JSON string?', e);
58+
const result = await businessNetworkConnection.submitTransaction(resource);
59+
if (result) {
60+
const prettify = (result) => {
61+
if (result instanceof Typed) {
62+
return serializer.toJSON(result);
4863
}
64+
return result;
65+
};
66+
let prettyResult;
67+
if (Array.isArray(result)) {
68+
prettyResult = result.map(item => prettify(item));
4969
} else {
50-
throw new Error('Data must be a string');
51-
}
52-
53-
if (!data.$class) {
54-
throw new Error('$class attribute not supplied');
70+
prettyResult = prettify(result);
5571
}
56-
57-
let businessNetwork = businessNetworkConnection.getBusinessNetwork();
58-
let serializer = businessNetwork.getSerializer();
59-
let resource = serializer.fromJSON(data);
60-
61-
return businessNetworkConnection.submitTransaction(resource);
62-
})
63-
.then((submitted) => {
64-
cmdUtil.log('Transaction Submitted.');
65-
});
72+
cmdUtil.log(Pretty.render(prettyResult, {
73+
keysColor: 'blue',
74+
dashColor: 'blue',
75+
stringColor: 'white'
76+
}));
77+
}
78+
cmdUtil.log('Transaction Submitted.');
6679
}
6780

6881
}

packages/composer-cli/test/transaction/submit.js

Lines changed: 107 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -14,50 +14,50 @@
1414

1515
'use strict';
1616

17-
const Client = require('composer-client');
18-
const Admin = require('composer-admin');
19-
const Common = require('composer-common');
20-
const BusinessNetworkConnection = Client.BusinessNetworkConnection;
21-
const BusinessNetworkDefinition = Admin.BusinessNetworkDefinition;
22-
const Serializer = Common.Serializer;
23-
const Resource = Common.Resource;
24-
25-
const Submit = require('../../lib/cmds/transaction/submitCommand.js');
17+
const { BusinessNetworkConnection } = require('composer-client');
18+
const { BusinessNetworkDefinition } = require('composer-common');
2619
const CmdUtil = require('../../lib/cmds/utils/cmdutils.js');
20+
const Pretty = require('prettyjson');
21+
const Submit = require('../../lib/cmds/transaction/submitCommand.js');
2722

2823
const chai = require('chai');
29-
const sinon = require('sinon');
30-
3124
chai.should();
3225
chai.use(require('chai-things'));
3326
chai.use(require('chai-as-promised'));
27+
const sinon = require('sinon');
3428

35-
const NAMESPACE = 'net.biz.TestNetwork';
3629
const ENROLL_SECRET = 'SuccessKidWin';
3730

38-
39-
4031
describe('composer transaction submit CLI unit tests', () => {
4132
let sandbox;
4233
let mockBusinessNetworkConnection;
43-
let mockBusinessNetwork;
44-
let mockSerializer;
45-
let mockResource;
34+
let businessNetworkDefinition;
35+
let modelManager;
36+
let factory;
37+
let spyPretty;
4638

4739
beforeEach(() => {
4840
sandbox = sinon.sandbox.create();
41+
42+
businessNetworkDefinition = new BusinessNetworkDefinition('[email protected]');
43+
modelManager = businessNetworkDefinition.getModelManager();
44+
modelManager.addModelFile(`
45+
namespace org.acme
46+
concept MyConcept {
47+
o String value
48+
}
49+
transaction MyTransaction {
50+
o Boolean success
51+
}`);
52+
factory = businessNetworkDefinition.getFactory();
53+
4954
mockBusinessNetworkConnection = sinon.createStubInstance(BusinessNetworkConnection);
50-
mockBusinessNetwork = sinon.createStubInstance(BusinessNetworkDefinition);
51-
mockSerializer = sinon.createStubInstance(Serializer);
52-
mockResource = sinon.createStubInstance(Resource);
53-
mockBusinessNetworkConnection.getBusinessNetwork.returns(mockBusinessNetwork);
55+
mockBusinessNetworkConnection.getBusinessNetwork.returns(businessNetworkDefinition);
5456
mockBusinessNetworkConnection.connect.resolves();
55-
mockBusinessNetwork.getSerializer.returns(mockSerializer);
56-
mockSerializer.fromJSON.returns(mockResource);
57-
mockResource.getIdentifier.returns('SuccessKid');
5857

5958
sandbox.stub(CmdUtil, 'createBusinessNetworkConnection').returns(mockBusinessNetworkConnection);
6059
sandbox.stub(process, 'exit');
60+
spyPretty = sandbox.spy(Pretty, 'render');
6161
});
6262

6363
afterEach(() => {
@@ -66,71 +66,124 @@ describe('composer transaction submit CLI unit tests', () => {
6666

6767
describe('#hander', () => {
6868

69-
it('should not error when all requred params (card based) are specified', () => {
69+
it('should not error when all requred params (card based) are specified', async () => {
70+
sandbox.stub(CmdUtil, 'prompt').resolves(ENROLL_SECRET);
71+
72+
let argv = {
73+
card: 'cardname',
74+
data: '{"$class": "org.acme.MyTransaction", "success": true}'
75+
};
76+
77+
await Submit.handler(argv);
78+
sinon.assert.calledWith(mockBusinessNetworkConnection.connect,'cardname');
79+
});
80+
81+
it('should not error when the transaction returns a primitive value', async () => {
82+
mockBusinessNetworkConnection.submitTransaction.resolves('foobar');
83+
sandbox.stub(CmdUtil, 'prompt').resolves(ENROLL_SECRET);
84+
85+
let argv = {
86+
card: 'cardname',
87+
data: '{"$class": "org.acme.MyTransaction", "success": true}'
88+
};
89+
90+
await Submit.handler(argv);
91+
sinon.assert.calledWith(mockBusinessNetworkConnection.connect,'cardname');
92+
sinon.assert.calledOnce(spyPretty);
93+
sinon.assert.calledWith(spyPretty, 'foobar');
94+
});
95+
96+
it('should not error when the transaction returns an array of primitive values', async () => {
97+
mockBusinessNetworkConnection.submitTransaction.resolves(['foobar', 'doge', 'cat']);
7098
sandbox.stub(CmdUtil, 'prompt').resolves(ENROLL_SECRET);
7199

72100
let argv = {
73101
card: 'cardname',
74-
data: '{"$class": "'+NAMESPACE+'", "success": true}'
102+
data: '{"$class": "org.acme.MyTransaction", "success": true}'
75103
};
76104

77-
return Submit.handler(argv)
78-
.then((res) => {
79-
sinon.assert.calledWith(mockBusinessNetworkConnection.connect,'cardname');
105+
await Submit.handler(argv);
106+
sinon.assert.calledWith(mockBusinessNetworkConnection.connect,'cardname');
107+
sinon.assert.calledOnce(spyPretty);
108+
sinon.assert.calledWith(spyPretty, ['foobar', 'doge', 'cat']);
109+
});
110+
111+
it('should not error when the transaction returns a concept value', async () => {
112+
const concept = factory.newConcept('org.acme', 'MyConcept');
113+
concept.value = 'foobar';
114+
mockBusinessNetworkConnection.submitTransaction.resolves(concept);
115+
sandbox.stub(CmdUtil, 'prompt').resolves(ENROLL_SECRET);
116+
117+
let argv = {
118+
card: 'cardname',
119+
data: '{"$class": "org.acme.MyTransaction", "success": true}'
120+
};
80121

81-
});
122+
await Submit.handler(argv);
123+
sinon.assert.calledWith(mockBusinessNetworkConnection.connect,'cardname');
124+
sinon.assert.calledOnce(spyPretty);
125+
sinon.assert.calledWith(spyPretty, { $class: 'org.acme.MyConcept', value: 'foobar' });
82126
});
83127

84-
it('should error when can not parse the json (card based)', () => {
85-
sandbox.stub(JSON, 'parse').throws(new Error('failure'));
128+
it('should not error when the transaction returns an array of concept values', async () => {
129+
const concept1 = factory.newConcept('org.acme', 'MyConcept');
130+
concept1.value = 'foobar';
131+
const concept2 = factory.newConcept('org.acme', 'MyConcept');
132+
concept2.value = 'doge';
133+
const concept3 = factory.newConcept('org.acme', 'MyConcept');
134+
concept3.value = 'cat';
135+
mockBusinessNetworkConnection.submitTransaction.resolves([concept1, concept2, concept3]);
136+
sandbox.stub(CmdUtil, 'prompt').resolves(ENROLL_SECRET);
137+
138+
let argv = {
139+
card: 'cardname',
140+
data: '{"$class": "org.acme.MyTransaction", "success": true}'
141+
};
142+
143+
await Submit.handler(argv);
144+
sinon.assert.calledWith(mockBusinessNetworkConnection.connect,'cardname');
145+
sinon.assert.calledOnce(spyPretty);
146+
sinon.assert.calledWith(spyPretty, [{ $class: 'org.acme.MyConcept', value: 'foobar' }, { $class: 'org.acme.MyConcept', value: 'doge' }, { $class: 'org.acme.MyConcept', value: 'cat' }]);
147+
});
86148

149+
it('should error when can not parse the json (card based)', async () => {
87150
let argv = {
88151
card: 'cardname',
89-
data: '{"$class": "'+NAMESPACE+'", "success": true}'
152+
data: '{"$class": "org.acme.MyTransaction", "success": true'
90153
};
91154

92-
return Submit.handler(argv).should.be.rejectedWith(/JSON error/);
155+
await Submit.handler(argv).should.be.rejectedWith(/JSON error/);
93156
});
94157

95-
it('should error when the transaction fails to submit', () => {
158+
it('should error when the transaction fails to submit', async () => {
96159
let argv = {
97160
card: 'cardname',
98-
data: '{"$class": "'+NAMESPACE+'", "success": true}'
161+
data: '{"$class": "org.acme.MyTransaction", "success": true}'
99162
};
100163

101164
mockBusinessNetworkConnection.submitTransaction.rejects(new Error('some error'));
102-
return Submit.handler(argv)
103-
.then((res) => {
104-
// sinon.assert.calledWith(process.exit, 1);
105-
}).catch((error) => {
106-
error.toString().should.equal('Error: some error');
107-
});
165+
await Submit.handler(argv)
166+
.should.be.rejectedWith(/some error/);
108167
});
109168

110-
it('should error if data is not a string', () => {
169+
it('should error if data is not a string', async () => {
111170
let argv = {
112171
card: 'cardname',
113172
data: {}
114173
};
115174

116-
return Submit.handler(argv)
117-
.then((res) => {
118-
}).catch((error) => {
119-
error.toString().should.equal('Error: Data must be a string');
120-
});
175+
await Submit.handler(argv)
176+
.should.be.rejectedWith(/Data must be a string/);
121177
});
122178

123-
it('should error if data class is not supplied', () => {
179+
it('should error if data class is not supplied', async () => {
124180
let argv = {
125181
card: 'cardname',
126182
data: '{"success": true}'
127183
};
128184

129-
return Submit.handler(argv)
130-
.then((res) => {
131-
}).catch((error) => {
132-
error.toString().should.equal('Error: $class attribute not supplied');
133-
});
185+
await Submit.handler(argv)
186+
.should.be.rejectedWith(/\$class attribute not supplied/);
134187
});
135188
});
136189
});

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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -900,6 +900,14 @@ class HLFConnection extends Connection {
900900
const method = 'invokeChainCode';
901901
LOG.entry(method, securityContext, functionName, args, options);
902902

903+
// If commit has been set to false, we do not want to order the transaction or wait for any events.
904+
if (options.commit === false) {
905+
LOG.debug(method, 'Commit has been set to false, deferring to queryChainCode instead');
906+
const result = await this.queryChainCode(securityContext, functionName, args, options);
907+
LOG.exit(method, result);
908+
return result;
909+
}
910+
903911
if (!this.businessNetworkIdentifier) {
904912
throw new Error('No business network has been specified for this connection');
905913
}
@@ -961,13 +969,6 @@ class HLFConnection extends Connection {
961969
LOG.debug(method, 'Response does not include payload data');
962970
}
963971

964-
// If commit has been set to false, do not order the transaction or wait for any events.
965-
if (options.commit === false) {
966-
LOG.debug(method, 'Commit has been set to false, not ordering transaction or waiting for any events');
967-
LOG.exit(method, result);
968-
return result;
969-
}
970-
971972
// Submit the endorsed transaction to the primary orderers.
972973
const proposal = results[1];
973974
const header = results[2];

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

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2052,36 +2052,12 @@ describe('HLFConnection', () => {
20522052
});
20532053

20542054
it('should submit an invoke request to the chaincode and not order it if commit specified as false', () => {
2055-
const proposalResponses = [{
2056-
response: {
2057-
status: 200,
2058-
payload: 'hello world'
2059-
}
2060-
}];
2061-
const proposal = { proposal: 'i do' };
2062-
const header = { header: 'gooooal' };
2063-
mockChannel.sendTransactionProposal.resolves([ proposalResponses, proposal, header ]);
2064-
connection._validatePeerResponses.returns({ignoredErrors: 0, validResponses: proposalResponses});
2065-
// This is the commit proposal and response (from the orderer).
2066-
const response = {
2067-
status: 'SUCCESS'
2068-
};
2069-
mockChannel.sendTransaction.withArgs({ proposalResponses: proposalResponses, proposal: proposal, header: header }).resolves(response);
2070-
// This is the event hub response.
2071-
mockEventHub1.registerTxEvent.yields('00000000-0000-0000-0000-000000000000', 'VALID');
2055+
sandbox.stub(connection, 'queryChainCode').resolves('hello world');
20722056
return connection.invokeChainCode(mockSecurityContext, 'myfunc', ['arg1', 'arg2'], { commit: false })
20732057
.then((result) => {
20742058
result.should.equal('hello world');
2075-
sinon.assert.calledOnce(mockChannel.sendTransactionProposal);
2076-
sinon.assert.calledWith(mockChannel.sendTransactionProposal, {
2077-
chaincodeId: mockBusinessNetwork.getName(),
2078-
txId: mockTransactionID,
2079-
fcn: 'myfunc',
2080-
args: ['arg1', 'arg2']
2081-
});
2082-
sinon.assert.notCalled(mockChannel.sendTransaction);
2083-
sinon.assert.notCalled(connection._checkCCListener);
2084-
sinon.assert.calledOnce(connection._checkEventhubs);
2059+
sinon.assert.calledOnce(connection.queryChainCode);
2060+
sinon.assert.calledWith(connection.queryChainCode, mockSecurityContext, 'myfunc', ['arg1', 'arg2'], { commit: false });
20852061
});
20862062
});
20872063

packages/composer-runtime/lib/engine.transactions.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ class EngineTransactions {
297297
LOG.error(method, error);
298298
throw error;
299299
}
300-
return context.getSerializer().toJSON(actualReturnValue);
300+
return context.getSerializer().toJSON(actualReturnValue, { convertResourcesToRelationships: true, permitResourcesForRelationships: false });
301301
};
302302

303303
// Handle the non-array case - a single return value.

0 commit comments

Comments
 (0)