From 6ea632c146af25f457233ae440e6e715d1202683 Mon Sep 17 00:00:00 2001 From: "nkl199@yahoo.co.uk" Date: Wed, 30 Oct 2019 17:43:44 +0000 Subject: [PATCH] [FABN-1396] hoist typescript tests to cucumber - Add error case testing to submit/evaluate - Add handler options to submit/evaluate - Add transient data to submit/evaluate - Delete obsolete typescript test suite and references Change-Id: I8db78aedfad044597769acb0d9a44e5299b4d7c5 Signed-off-by: nkl199@yahoo.co.uk --- build/tasks/eslint.js | 1 - build/tasks/test.js | 2 +- package.json | 10 +- test/README.md | 11 +- test/integration/network-e2e/e2e-hsm.js | 4 - test/integration/network-e2e/e2e.js | 5 - .../chaincode/node/fabcar/lib/fabcar.js | 12 + .../node/fabcarUpgrade/lib/fabcar.js | 12 + .../config/handlers}/sample-query-handler.ts | 34 +- .../sample-transaction-event-handler.ts | 172 +++ test/ts-scenario/features/discovery.feature | 36 +- test/ts-scenario/features/gateway.feature | 40 +- test/ts-scenario/steps/constants.ts | 1 + test/ts-scenario/steps/lib/gateway.ts | 244 +++- .../steps/lib/utility/baseUtils.ts | 22 + test/ts-scenario/steps/network-model.ts | 17 +- test/ts-scenario/support/hooks.ts | 6 +- test/typescript/declarations.d.ts | 7 - .../integration/network-e2e/invoke.ts | 1091 ----------------- .../integration/network-e2e/query.ts | 378 ------ test/typescript/tsconfig.json | 11 - 21 files changed, 554 insertions(+), 1562 deletions(-) rename test/{typescript/integration/network-e2e => ts-scenario/config/handlers}/sample-query-handler.ts (60%) create mode 100644 test/ts-scenario/config/handlers/sample-transaction-event-handler.ts delete mode 100644 test/typescript/declarations.d.ts delete mode 100644 test/typescript/integration/network-e2e/invoke.ts delete mode 100644 test/typescript/integration/network-e2e/query.ts delete mode 100644 test/typescript/tsconfig.json diff --git a/build/tasks/eslint.js b/build/tasks/eslint.js index 462d4eb31e..c54e0893f9 100644 --- a/build/tasks/eslint.js +++ b/build/tasks/eslint.js @@ -25,7 +25,6 @@ gulp.task('eslint', () => { '!fabric-common/node_modules/**', '!fabric-common/coverage/**', '!node_modules/**', - '!test/typescript/**/*.js', '!fabric-protos/**', ]).pipe(eslint()) .pipe(eslint.format()) diff --git a/build/tasks/test.js b/build/tasks/test.js index 92db1f3437..4aa4ec2386 100644 --- a/build/tasks/test.js +++ b/build/tasks/test.js @@ -190,7 +190,7 @@ gulp.task('test-fv-scenario', shell.task('npx nyc gulp run-test-fv-scenario')); // run fv only with code coverage // override the global nyc configuration -gulp.task('test-fv-only', shell.task('npx nyc --check-coverage --statements 54 --branches 43 --functions 56 --lines 54 gulp run-tape-e2e')); +gulp.task('test-fv-only', shell.task('npx nyc --check-coverage --statements 53 --branches 42 --functions 53 --lines 53 gulp run-tape-e2e')); gulp.task('run-test-fv-scenario', (done) => { const tasks = ['run-tape-e2e', 'docker-clean', 'run-test:cucumber', 'docker-clean', 'run-test:ts-cucumber']; diff --git a/package.json b/package.json index 040f6af4f3..362f145a18 100644 --- a/package.json +++ b/package.json @@ -22,16 +22,14 @@ "test:network": "npm run compile-src && npm run coverage -- 'fabric-network/test/**/*.{js,ts}'", "test:protos": "npm run coverage -- 'fabric-protos/test/**/*.{js,ts}", "test:cucumber": "cucumber-js ./test/scenario/features/*.feature", - "test:ts-cucumber": "cucumber-js ./test/ts-scenario/features/*.feature --require './test/ts-scenario/steps/**/*.ts' --require './test/ts-scenario/support/**/*.ts' --require-module ts-node/register", - "test:ts-cucumber-tagged": "cucumber-js ./test/ts-scenario/features/*.feature --require './test/ts-scenario/steps/**/*.ts' --require './test/ts-scenario/support/**/*.ts' --require-module ts-node/register --tags @lifecycle", - "test:all": "nyc npm run unit-test:all", + "test:ts-cucumber": "cucumber-js ./test/ts-scenario/features/*.feature --require './test/ts-scenario/steps/**/*.ts' --require './test/ts-scenario/support/**/*.ts' --require-module ts-node/register", + "test:ts-cucumber-tagged": "cucumber-js ./test/ts-scenario/features/*.feature --require './test/ts-scenario/steps/**/*.ts' --require './test/ts-scenario/support/**/*.ts' --require-module ts-node/register --tags @discovery", + "test:all": "nyc npm run unit-test:all", "unit-test:all": "npm run unit-test -- 'fabric-common/test/**/*.{js,ts}' && npm run unit-test -- 'fabric-ca-client/test/**/*.{js,ts}' && npm run unit-test -- 'fabric-client/test/**/*.{js,ts}' && npm run unit-test -- 'fabric-network/test/**/*.{js,ts}'", "unit-test": "mocha --require ts-node/register --exclude 'fabric-client/test/data/**'", - "compile": "npm run compile-src && npm run compile-test", + "compile": "npm run compile-src", "compile-src": "tsc --project fabric-network/tsconfig-declaration.json && tsc --project fabric-network/tsconfig.json", "compile-src:w": "tsc --project fabric-network --watch", - "compile-test": "tsc --project test/typescript", - "compile-test:w": "tsc --project test/typescript --watch", "coverage": "nyc npm run unit-test", "retrieve-images": "./scripts/utility/fabric_images.sh amd64 2.0.0-stable" }, diff --git a/test/README.md b/test/README.md index ca9f2d5a7e..438bb5b710 100644 --- a/test/README.md +++ b/test/README.md @@ -11,7 +11,7 @@ Unit tests are located in a `package/test` directory that mirrors the `package/l The functional tests are currently written in [Tape](https://github.com/substack/tape), with the intention of testing the Fabric-SDK-Node package capabilities from a user perspective against a live Hyperledger Fabric Network. -The scenario tests are written in [Cucumber](https://github.com/cucumber/cucumber-js), with the intention of providing high level test coverage from a scenario perspective. For more information, please refer to the [README](./scenario/README.md) within the scenario directory. +The scenario tests are written in typescript and use [Cucumber](https://github.com/cucumber/cucumber-js), with the intention of providing high level test coverage from a scenario perspective. For more information, please refer to the [README](./ts-scenario/README.md) within the scenario directory. Test certificates are set to expire a year after generation. Due to this the test suite generates new certificates as part of the build process, and is a manual requirement prior to running the tests locally. This process is orchestrated using gulp files that: - Download, install and export the path to the 1.4 Hyperledger Fabric binaries used for generating crypto material @@ -29,15 +29,12 @@ test │ └───fixtures └───integration -└───scenario -└───typescript -└───unit +└───ts-scenario ``` - `fixtures` holds all the configuration files used by the integration and scenario tests -- `integration` contains the interation test suite -- `scenario` contains the sceanrio test suite -- `typescript` contains the typescript test suite +- `integration` contains the integration test suite +- `ts-scenario` contains the typescripts scenario test suite - `unit` contains the deprecated unit test suite ## Configuring and running Hardware Security Module tests diff --git a/test/integration/network-e2e/e2e-hsm.js b/test/integration/network-e2e/e2e-hsm.js index a4e95ac955..52c9f9f4a7 100644 --- a/test/integration/network-e2e/e2e-hsm.js +++ b/test/integration/network-e2e/e2e-hsm.js @@ -4,12 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -const tsPath = '../../typescript/integration/network-e2e'; - require('../e2e/create-channel.js'); require('../e2e/join-channel.js'); require('./install-chaincode.js'); require('./instantiate-chaincode.js'); require('./invoke-hsm.js'); -require(tsPath + '/invoke.js'); -require(tsPath + '/query.js'); diff --git a/test/integration/network-e2e/e2e.js b/test/integration/network-e2e/e2e.js index 099e24c457..9e2d2c9a12 100644 --- a/test/integration/network-e2e/e2e.js +++ b/test/integration/network-e2e/e2e.js @@ -4,12 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -const tsPath = '../../typescript/integration/network-e2e'; - require('../e2e/create-channel.js'); require('../e2e/join-channel.js'); require('./install-chaincode.js'); require('./instantiate-chaincode.js'); -// require(tsPath + '/updateAnchorPeers'); -require(tsPath + '/invoke.js'); -require(tsPath + '/query.js'); diff --git a/test/ts-fixtures/chaincode/node/fabcar/lib/fabcar.js b/test/ts-fixtures/chaincode/node/fabcar/lib/fabcar.js index 0f857cdf9d..7f23a2c137 100644 --- a/test/ts-fixtures/chaincode/node/fabcar/lib/fabcar.js +++ b/test/ts-fixtures/chaincode/node/fabcar/lib/fabcar.js @@ -82,6 +82,7 @@ class FabCar extends Contract { } async queryCar(ctx, carNumber) { + console.info('============= START : queryCar ==========='); // eslint-disable-line const carAsBytes = await ctx.stub.getState(carNumber); // get the car from chaincode state if (!carAsBytes || carAsBytes.length === 0) { throw new Error(`${carNumber} does not exist`); @@ -106,6 +107,7 @@ class FabCar extends Contract { } async queryAllCars(ctx) { + console.info('============= START : queryAllCars ==========='); // eslint-disable-line const startKey = 'CAR0'; const endKey = 'CAR999'; @@ -150,6 +152,16 @@ class FabCar extends Contract { console.info('============= END : changeCarOwner ==========='); // eslint-disable-line } + async getTransient(ctx) { + console.info('============= START : getTransient ==========='); // eslint-disable-line + const transientMap = ctx.stub.getTransient(); + const result = {}; + transientMap.forEach((value, key) => { + result[key] = value.toString('utf8'); + }); + return JSON.stringify(result); + } + } module.exports = FabCar; diff --git a/test/ts-fixtures/chaincode/node/fabcarUpgrade/lib/fabcar.js b/test/ts-fixtures/chaincode/node/fabcarUpgrade/lib/fabcar.js index 0ba13f16b6..9be2fba033 100644 --- a/test/ts-fixtures/chaincode/node/fabcarUpgrade/lib/fabcar.js +++ b/test/ts-fixtures/chaincode/node/fabcarUpgrade/lib/fabcar.js @@ -14,6 +14,7 @@ class FabCar extends Contract { } async querySingleCar(ctx, carNumber) { + console.info('============= START : querySingleCar ==========='); // eslint-disable-line const carAsBytes = await ctx.stub.getState(carNumber); // get the car from chaincode state if (!carAsBytes || carAsBytes.length === 0) { throw new Error(`${carNumber} does not exist`); @@ -38,6 +39,7 @@ class FabCar extends Contract { } async queryAllCars(ctx) { + console.info('============= START : queryAllCars ==========='); // eslint-disable-line const startKey = 'CAR0'; const endKey = 'CAR999'; @@ -82,6 +84,16 @@ class FabCar extends Contract { console.info('============= END : changeCarOwner ==========='); // eslint-disable-line } + async getTransient(ctx) { + console.info('============= START : getTransient ==========='); // eslint-disable-line + const transientMap = ctx.stub.getTransient(); + const result = {}; + transientMap.forEach((value, key) => { + result[key] = value.toString('utf8'); + }); + return JSON.stringify(result); + } + } module.exports = FabCar; diff --git a/test/typescript/integration/network-e2e/sample-query-handler.ts b/test/ts-scenario/config/handlers/sample-query-handler.ts similarity index 60% rename from test/typescript/integration/network-e2e/sample-query-handler.ts rename to test/ts-scenario/config/handlers/sample-query-handler.ts index 1f2589d826..dde52f7b5e 100644 --- a/test/typescript/integration/network-e2e/sample-query-handler.ts +++ b/test/ts-scenario/config/handlers/sample-query-handler.ts @@ -7,20 +7,12 @@ // Sample query handler that will use all queryable peers within the network to evaluate transactions, with preference // given to peers within the same organization. -// tslint:disable:typedef +import { Network, Query, QueryHandler, QueryHandlerFactory, QueryResults } from 'fabric-network'; -import { - Network, - Query, - QueryHandler, - QueryHandlerFactory, -} from 'fabric-network'; - -import { - ChannelPeer, -} from 'fabric-client'; +import { ChannelPeer } from 'fabric-client'; import util = require('util'); +import Client = require('fabric-client'); /** * Query handler implementation that simply tries all the peers it is given in order until it gets a result. @@ -36,8 +28,8 @@ class SampleQueryHandler implements QueryHandler { const errorMessages: string[] = []; for (const peer of this.peers) { - const results = await query.evaluate([peer]); - const result = results[peer.getName()]; + const results: QueryResults = await query.evaluate([peer]); + const result: Buffer | Client.ProposalErrorResponse = results[peer.getName()]; if (!(result instanceof Error)) { // Good response from peer @@ -51,13 +43,13 @@ class SampleQueryHandler implements QueryHandler { errorMessages.push(result.message); } - const message = util.format('Evaluate failed with the following errors: %j', errorMessages); + const message: string = util.format('Evaluate failed with the following errors: %j', errorMessages); throw new Error(message); } } function filterQueryablePeers(peers: ChannelPeer[]): ChannelPeer[] { - return peers.filter((peer) => peer.isInRole('chaincodeQuery')); + return peers.filter((peer: ChannelPeer) => peer.isInRole('chaincodeQuery')); } /** @@ -65,13 +57,13 @@ function filterQueryablePeers(peers: ChannelPeer[]): ChannelPeer[] { * @param {Network} network The network where transactions are to be evaluated. * @returns {QueryHandler} A query handler implementation. */ -const createQueryHandler: QueryHandlerFactory = (network: Network) => { - const channel = network.getChannel(); - const orgPeers = filterQueryablePeers(channel.getPeersForOrg()); - const networkPeers = filterQueryablePeers(channel.getChannelPeers()) - .filter((peer) => !orgPeers.includes(peer)); // Exclude peers already in the orgPeer array +const createQueryHandler: QueryHandlerFactory = (network: Network): SampleQueryHandler => { + const channel: Client.Channel = network.getChannel(); + const orgPeers: ChannelPeer[] = filterQueryablePeers(channel.getPeersForOrg()); + const networkPeers: ChannelPeer[] = filterQueryablePeers(channel.getChannelPeers()) + .filter((peer: ChannelPeer) => !orgPeers.includes(peer)); // Exclude peers already in the orgPeer array - const allPeers = orgPeers.concat(networkPeers); // Peers in our organization first + const allPeers: ChannelPeer[] = orgPeers.concat(networkPeers); // Peers in our organization first return new SampleQueryHandler(allPeers); }; diff --git a/test/ts-scenario/config/handlers/sample-transaction-event-handler.ts b/test/ts-scenario/config/handlers/sample-transaction-event-handler.ts new file mode 100644 index 0000000000..e7e308cc19 --- /dev/null +++ b/test/ts-scenario/config/handlers/sample-transaction-event-handler.ts @@ -0,0 +1,172 @@ +/** + * Copyright 2018 IBM All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict'; + +import { Network, Transaction } from 'fabric-network'; +import { ChannelPeer, ChannelEventHub } from 'fabric-client'; +import Client = require('fabric-client'); + +// --- Plug-in event handler sample where the user takes full responsibility for event hub connection and event handling + +/** + * Handler that listens for commit events for a specific transaction from a set of event hubs. + * A new instance of this class is created to handle each transaction as it maintains state + * related to events for a given transaction. + * + * This implementation will unblock once all the event hubs have either supplied a vaid transaction commit event or + * disconnected. + */ +class SampleTransactionEventHandler { + + protected transactionId: string; + protected eventHubs: ChannelEventHub[]; + protected options: any; + protected notificationPromise: any; + protected _resolveNotificationPromise: any; + protected _rejectNotificationPromise: any; + protected eventCounts: any; + protected timeoutHandler: any; + + /** + * Constructor. + * @param {Transaction} transaction Transaction ID for which events will be received. + * @param {ChannelEventHub[]} eventHubs Event hubs from which events will be received. + * @param {Object} [options] Additional configuration options. + * @param {Number} [options.commitTimeout] Time in seconds to wait for commit events to be received. + */ + constructor(transaction: Transaction, eventHubs: ChannelEventHub[], options: object) { + this.transactionId = transaction.getTransactionID().getTransactionID(); + this.eventHubs = eventHubs; + + const defaultOptions: any = { + commitTimeout: 120 // 2 minutes (120 seconds) + }; + this.options = Object.assign(defaultOptions, options); + + this.notificationPromise = new Promise((resolve: any, reject: any): any => { + this._resolveNotificationPromise = resolve; + this._rejectNotificationPromise = reject; + }); + this.eventCounts = { + expected: this.eventHubs.length, + received: 0 + }; + } + + /** + * Called to initiate listening for transaction events. + * @throws {Error} if not in a state where the handling strategy can be satisfied and the transaction should + * be aborted. For example, if insufficient event hubs could be connected. + */ + async startListening(): Promise { + if (this.eventHubs.length > 0) { + this._setListenTimeout(); + this._registerTxEventListeners(); + } else { + // Assume success if no event hubs + this._resolveNotificationPromise(); + } + } + + /** + * Wait until enough events have been received from the event hubs to satisfy the event handling strategy. + * @throws {Error} if the transaction commit is not successful within the timeout period. + */ + async waitForEvents(): Promise { + await this.notificationPromise; + } + + /** + * Cancel listening for events. + */ + cancelListening(): void { + clearTimeout(this.timeoutHandler); + this.eventHubs.forEach((eventHub: ChannelEventHub) => eventHub.unregisterTxEvent(this.transactionId)); + } + + _setListenTimeout(): void { + if (this.options.commitTimeout <= 0) { + return; + } + + this.timeoutHandler = setTimeout(() => { + this._fail(new Error(`Timeout waiting for commit events for transaction ID ${this.transactionId}`)); + }, this.options.commitTimeout * 1000); + } + + _registerTxEventListeners(): void { + this.eventHubs.forEach((eventHub: ChannelEventHub) => { + eventHub.registerTxEvent( + this.transactionId, + (txId: string, code: string) => this._onEvent(eventHub, txId, code), + (err: Error) => this._onError(eventHub, err) + ); + eventHub.connect(); + }); + } + + _onEvent(eventHub: ChannelEventHub, txId: string, code: string): void { + if (code !== 'VALID') { + // Peer has rejected the transaction so stop listening with a failure + const message: string = `Peer ${eventHub.getPeerAddr()} has rejected transaction ${txId} with code ${code}`; + this._fail(new Error(message)); + } else { + // -------------------------------------------------------------- + // Handle processing of successful transaction commit events here + // -------------------------------------------------------------- + this._responseReceived(); + } + } + + _onError(eventHub: ChannelEventHub, err: Error): void { // eslint-disable-line no-unused-vars + // ---------------------------------------------------------- + // Handle processing of event hub communication failures here + // ---------------------------------------------------------- + this._responseReceived(); + } + + /** + * Simple event handling logic that is satisfied once all of the event hubs have either responded with valid + * events or disconnected. + */ + _responseReceived(): void { + this.eventCounts.received++; + if (this.eventCounts.received === this.eventCounts.expected) { + this._success(); + } + } + + _fail(error: Error): void { + this.cancelListening(); + this._rejectNotificationPromise(error); + } + + _success(): void { + this.cancelListening(); + this._resolveNotificationPromise(); + } +} + +/** + * Factory function called for each submitted transaction, which supplies a commit handler instance for that + * transaction. This implementation returns a commit handler that listens to all eventing peers in the user's + * organization. + * @param {Transaction} transaction The transaction being submitted. + * @param {Network} network The network where the transaction is being submitted. + */ +function createTransactionEventHandler(transaction: Transaction, network: Network): SampleTransactionEventHandler { + const channel: Client.Channel = network.getChannel(); + const peers: ChannelPeer[] = channel.getPeersForOrg().filter(hasEventSourceRole); + const eventHubs: ChannelEventHub[] = peers.map((peer: ChannelPeer) => channel.getChannelEventHub(peer.getName())); + return new SampleTransactionEventHandler(transaction, eventHubs, {}); +} + +function hasEventSourceRole(peer: ChannelPeer): boolean { + return peer.isInRole('eventSource'); +} + +module.exports = createTransactionEventHandler; diff --git a/test/ts-scenario/features/discovery.feature b/test/ts-scenario/features/discovery.feature index 7eb0889e9d..664bb7d2db 100644 --- a/test/ts-scenario/features/discovery.feature +++ b/test/ts-scenario/features/discovery.feature @@ -12,10 +12,44 @@ Feature: Configure Fabric using CLI and submit/evaluate using a network Gateway And I use the cli to create and join the channel named discoverychannel on the deployed network And I use the cli to update the channel with name discoverychannel with config file discoverychannel-anchor.tx on the deployed network And I use the cli to deploy a node smart contract named fabcar at version 1.0.0 for all organizations on channel discoverychannel with endorsement policy 1of and arguments ["initLedger"] + And I have a file backed gateway named myDiscoveryGateway with discovery set to true for user User1 using the connection profile named ccp-tls.json Scenario: Using a Gateway I can submit and evaluate transactions on instantiated node smart contract - Given I have a file backed gateway named myDiscoveryGateway with discovery set to true for user User1 using the connection profile named ccp-tls.json When I use the gateway named myDiscoveryGateway to submit a transaction with args [createCar,1001,Ariel,Atom,red,Nick] for contract fabcar instantiated on channel discoverychannel Then The gateway named myDiscoveryGateway has a submit type response When I use the gateway named myDiscoveryGateway to evaluate a transaction with args [queryCar,1001] for contract fabcar instantiated on channel discoverychannel Then The gateway named myDiscoveryGateway has a evaluate type response matching "{\"color\":\"red\",\"docType\":\"car\",\"make\":\"Ariel\",\"model\":\"Atom\",\"owner\":\"Nick\"}" + + Scenario: Using a Gateway I recieve useful error messages when I submit or evaulate invalid transactions + When I use the gateway named myDiscoveryGateway to submit a transaction with args [noSuchSubmitTransaction,1001,Trabant,601 Estate,brown,Simon] for contract fabcar instantiated on channel discoverychannel + Then The gateway named myDiscoveryGateway has a error type response containing "Error: Endorsement has failed" + When I use the gateway named myDiscoveryGateway to submit a transaction with args [createCar,9,Ford] for contract fabcar instantiated on channel discoverychannel + Then The gateway named myDiscoveryGateway has a error type response containing "Error: Endorsement has failed" + When I use the gateway named myDiscoveryGateway to evaluate a transaction with args [noSuchEvauateTransaction,1001] for contract fabcar instantiated on channel discoverychannel + Then The gateway named myDiscoveryGateway has a error type response containing "Error: You've asked to invoke a function that does not exist: noSuchEvauateTransaction" + When I use the gateway named myDiscoveryGateway to evaluate a transaction with args [queryCar,because,I,said,so] for contract fabcar instantiated on channel discoverychannel + Then The gateway named myDiscoveryGateway has a error type response containing "Error: Expected 1 parameters, but 4 have been supplied" + + Scenario: Using a Gateway to submit transactions I can use different event handler strategies + When I modify myDiscoveryGateway to submit a transaction with args [createCar,1002,Ford,Mustang,Silver,Andy] for contract fabcar instantiated on channel discoverychannel using handler option MSPID_SCOPE_ALLFORTX + Then The gateway named myDiscoveryGateway has a event type response matching "{\"Org1\":1,\"Org2\":0}" + When I modify myDiscoveryGateway to submit a transaction with args [createCar,1003,Ford,Mustang,Silver,Andy] for contract fabcar instantiated on channel discoverychannel using handler option MSPID_SCOPE_ANYFORTX + Then The gateway named myDiscoveryGateway has a event type response matching "{\"Org1\":1,\"Org2\":0}" + When I modify myDiscoveryGateway to submit a transaction with args [createCar,1004,Ford,Mustang,Silver,Andy] for contract fabcar instantiated on channel discoverychannel using handler option NETWORK_SCOPE_ALLFORTX + Then The gateway named myDiscoveryGateway has a event type response matching "{\"Org1\":1,\"Org2\":0}" + When I modify myDiscoveryGateway to submit a transaction with args [createCar,1005,Ford,Mustang,Silver,Andy] for contract fabcar instantiated on channel discoverychannel using handler option NETWORK_SCOPE_ANYFORTX + Then The gateway named myDiscoveryGateway has a event type response matching "{\"Org1\":1,\"Org2\":0}" + + Scenario: Using a Gateway to evaluate transactions I can use different query handler strategies + When I modify myDiscoveryGateway to evaluate a transaction with args [queryCar,1001] for contract fabcar instantiated on channel discoverychannel using handler option MSPID_SCOPE_SINGLE + Then The gateway named myDiscoveryGateway has a evaluate type response matching "{\"color\":\"red\",\"docType\":\"car\",\"make\":\"Ariel\",\"model\":\"Atom\",\"owner\":\"Nick\"}" + When I modify myDiscoveryGateway to evaluate a transaction with args [queryCar,1001] for contract fabcar instantiated on channel discoverychannel using handler option MSPID_SCOPE_ROUND_ROBIN + Then The gateway named myDiscoveryGateway has a evaluate type response matching "{\"color\":\"red\",\"docType\":\"car\",\"make\":\"Ariel\",\"model\":\"Atom\",\"owner\":\"Nick\"}" + When I modify myDiscoveryGateway to evaluate a transaction with args [queryCar,1001] for contract fabcar instantiated on channel discoverychannel using handler option custom + Then The gateway named myDiscoveryGateway has a evaluate type response matching "{\"color\":\"red\",\"docType\":\"car\",\"make\":\"Ariel\",\"model\":\"Atom\",\"owner\":\"Nick\"}" + + Scenario: Using a Gateway I can use transient data + When I modify myDiscoveryGateway to submit a transaction with transient data using args [getTransient,value1,value2] for contract fabcar instantiated on channel discoverychannel + Then The gateway named myDiscoveryGateway has a submit type response matching "{\"key0\":\"value1\",\"key1\":\"value2\"}" + When I modify myDiscoveryGateway to evaluate a transaction with transient data using args [getTransient,valueA,valueB] for contract fabcar instantiated on channel discoverychannel + Then The gateway named myDiscoveryGateway has a evaluate type response matching "{\"key0\":\"valueA\",\"key1\":\"valueB\"}" diff --git a/test/ts-scenario/features/gateway.feature b/test/ts-scenario/features/gateway.feature index d6c7d85c11..2aae62e449 100644 --- a/test/ts-scenario/features/gateway.feature +++ b/test/ts-scenario/features/gateway.feature @@ -11,10 +11,44 @@ Feature: Configure Fabric using CLI and submit/evaluate using a network Gateway Given I deploy a tls Fabric network And I use the cli to create and join the channel named gatewaychannel on the deployed network And I use the cli to deploy a node smart contract named fabcar at version 1.0.0 for all organizations on channel gatewaychannel with endorsement policy 1ofAny and arguments ["initLedger"] + And I have a couchDB backed gateway named mycouchgateway with discovery set to false for user User1 using the connection profile named ccp-tls.json Scenario: Using a Gateway I can submit and evaluate transactions on instantiated node smart contract - Given I have a couchDB backed gateway named mycouchgateway with discovery set to false for user User1 using the connection profile named ccp-tls.json - When I use the gateway named mycouchgateway to submit a transaction with args [createCar,1001,Trabant,601 Estate,brown,Simon] for contract fabcar instantiated on channel gatewaychannel + When I use the gateway named mycouchgateway to submit a transaction with args [createCar,1001,Trabant,601 Estate,brown,Simon] for contract fabcar instantiated on channel gatewaychannel Then The gateway named mycouchgateway has a submit type response When I use the gateway named mycouchgateway to evaluate a transaction with args [queryCar,1001] for contract fabcar instantiated on channel gatewaychannel - Then The gateway named mycouchgateway has a evaluate type response matching "{\"color\":\"brown\",\"docType\":\"car\",\"make\":\"Trabant\",\"model\":\"601 Estate\",\"owner\":\"Simon\"}" + Then The gateway named mycouchgateway has a evaluate type response matching "{\"color\":\"brown\",\"docType\":\"car\",\"make\":\"Trabant\",\"model\":\"601 Estate\",\"owner\":\"Simon\"}" + + Scenario: Using a Gateway I recieve useful error messages when I submit or evaulate invalid transactions + When I use the gateway named mycouchgateway to submit a transaction with args [noSuchSubmitTransaction,1001,Trabant,601 Estate,brown,Simon] for contract fabcar instantiated on channel gatewaychannel + Then The gateway named mycouchgateway has a error type response containing "Error: You've asked to invoke a function that does not exist: noSuchSubmitTransaction" + When I use the gateway named mycouchgateway to submit a transaction with args [createCar,9,Ford] for contract fabcar instantiated on channel gatewaychannel + Then The gateway named mycouchgateway has a error type response containing "Error: Expected 5 parameters, but 2 have been supplied" + When I use the gateway named mycouchgateway to evaluate a transaction with args [noSuchEvauateTransaction,1001] for contract fabcar instantiated on channel gatewaychannel + Then The gateway named mycouchgateway has a error type response containing "Error: You've asked to invoke a function that does not exist: noSuchEvauateTransaction" + When I use the gateway named mycouchgateway to evaluate a transaction with args [queryCar,because,I,said,so] for contract fabcar instantiated on channel gatewaychannel + Then The gateway named mycouchgateway has a error type response containing "Error: Expected 1 parameters, but 4 have been supplied" + + Scenario: Using a Gateway to submit transactions I can use different event handler strategies + When I modify mycouchgateway to submit a transaction with args [createCar,1002,Ford,Mustang,Silver,Andy] for contract fabcar instantiated on channel gatewaychannel using handler option MSPID_SCOPE_ALLFORTX + Then The gateway named mycouchgateway has a event type response matching "{\"Org1\":1,\"Org2\":0}" + When I modify mycouchgateway to submit a transaction with args [createCar,1003,Ford,Mustang,Silver,Andy] for contract fabcar instantiated on channel gatewaychannel using handler option MSPID_SCOPE_ANYFORTX + Then The gateway named mycouchgateway has a event type response matching "{\"Org1\":1,\"Org2\":0}" + When I modify mycouchgateway to submit a transaction with args [createCar,1004,Ford,Mustang,Silver,Andy] for contract fabcar instantiated on channel gatewaychannel using handler option NETWORK_SCOPE_ALLFORTX + Then The gateway named mycouchgateway has a event type response matching "{\"Org1\":1,\"Org2\":0}" + When I modify mycouchgateway to submit a transaction with args [createCar,1005,Ford,Mustang,Silver,Andy] for contract fabcar instantiated on channel gatewaychannel using handler option NETWORK_SCOPE_ANYFORTX + Then The gateway named mycouchgateway has a event type response matching "{\"Org1\":1,\"Org2\":0}" + + Scenario: Using a Gateway to evaluate transactions I can use different query handler strategies + When I modify mycouchgateway to evaluate a transaction with args [queryCar,1001] for contract fabcar instantiated on channel gatewaychannel using handler option MSPID_SCOPE_SINGLE + Then The gateway named mycouchgateway has a evaluate type response matching "{\"color\":\"brown\",\"docType\":\"car\",\"make\":\"Trabant\",\"model\":\"601 Estate\",\"owner\":\"Simon\"}" + When I modify mycouchgateway to evaluate a transaction with args [queryCar,1001] for contract fabcar instantiated on channel gatewaychannel using handler option MSPID_SCOPE_ROUND_ROBIN + Then The gateway named mycouchgateway has a evaluate type response matching "{\"color\":\"brown\",\"docType\":\"car\",\"make\":\"Trabant\",\"model\":\"601 Estate\",\"owner\":\"Simon\"}" + When I modify mycouchgateway to evaluate a transaction with args [queryCar,1001] for contract fabcar instantiated on channel gatewaychannel using handler option custom + Then The gateway named mycouchgateway has a evaluate type response matching "{\"color\":\"brown\",\"docType\":\"car\",\"make\":\"Trabant\",\"model\":\"601 Estate\",\"owner\":\"Simon\"}" + + Scenario: Using a Gateway I can use transient data + When I modify mycouchgateway to submit a transaction with transient data using args [getTransient,value1,value2] for contract fabcar instantiated on channel gatewaychannel + Then The gateway named mycouchgateway has a submit type response matching "{\"key0\":\"value1\",\"key1\":\"value2\"}" + When I modify mycouchgateway to evaluate a transaction with transient data using args [getTransient,valueA,valueB] for contract fabcar instantiated on channel gatewaychannel + Then The gateway named mycouchgateway has a evaluate type response matching "{\"key0\":\"valueA\",\"key1\":\"valueB\"}" diff --git a/test/ts-scenario/steps/constants.ts b/test/ts-scenario/steps/constants.ts index 676ae7a07c..66ae819868 100644 --- a/test/ts-scenario/steps/constants.ts +++ b/test/ts-scenario/steps/constants.ts @@ -22,6 +22,7 @@ export enum Constants { LIB_TO_CONFIG = '../../config', LIB_TO_CHAINCODE = '../../../ts-fixtures/chaincode', LIB_TO_POLICIES = '../../config/policies.json', + LIB_TO_TEMP = '../../../temp', STEPS_TO_POLICIES= '../config/policies.json', UTIL_TO_CONFIG = '../../../config', diff --git a/test/ts-scenario/steps/lib/gateway.ts b/test/ts-scenario/steps/lib/gateway.ts index 5347293ebc..d3b0249eb9 100644 --- a/test/ts-scenario/steps/lib/gateway.ts +++ b/test/ts-scenario/steps/lib/gateway.ts @@ -10,17 +10,32 @@ import * as BaseUtils from './utility/baseUtils'; import { CommonConnectionProfileHelper } from './utility/commonConnectionProfileHelper'; import { StateStore } from './utility/stateStore'; -import { Gateway, GatewayOptions, Wallet, Wallets, Identity, Contract, Network } from 'fabric-network'; +import sampleQueryStrategy = require('../../config/handlers/sample-query-handler'); +import sampleTxnEventStrategy = require('../../config/handlers/sample-transaction-event-handler'); + +import { DefaultEventHandlerStrategies, DefaultQueryHandlerStrategies, Gateway, GatewayOptions, Wallet, Wallets, Identity, Contract, Network, TxEventHandlerFactory, QueryHandlerFactory, EventHubManager, Transaction, TransientMap } from 'fabric-network'; import * as fs from 'fs'; -import * as os from 'os'; import * as path from 'path'; +import { ChannelEventHub } from 'fabric-client'; import Client = require('fabric-client'); const stateStore: StateStore = StateStore.getInstance(); const txnTypes: string[] = ['evaluate', 'submit']; -const txnResponseTypes: string[] = ['evaluate', 'error', 'submit']; +const txnResponseTypes: string[] = ['evaluate', 'event', 'error', 'submit']; const supportedWallets: string[] = [Constants.FILE_WALLET as string, Constants.MEMORY_WALLET as string, Constants.COUCH_WALLET as string]; +const EventStrategies: any = { + MSPID_SCOPE_ALLFORTX : DefaultEventHandlerStrategies.MSPID_SCOPE_ALLFORTX, + MSPID_SCOPE_ANYFORTX : DefaultEventHandlerStrategies.MSPID_SCOPE_ANYFORTX, + NETWORK_SCOPE_ALLFORTX : DefaultEventHandlerStrategies.NETWORK_SCOPE_ALLFORTX, + NETWORK_SCOPE_ANYFORTX : DefaultEventHandlerStrategies.NETWORK_SCOPE_ANYFORTX, +}; + +const QueryStrategies: any = { + MSPID_SCOPE_SINGLE : DefaultQueryHandlerStrategies.MSPID_SCOPE_SINGLE, + MSPID_SCOPE_ROUND_ROBIN : DefaultQueryHandlerStrategies.MSPID_SCOPE_ROUND_ROBIN, +}; + /** * Create a gateway * @param {CommonConnectionProfileHelper} ccp The common connection profile @@ -48,9 +63,9 @@ export async function createGateway(ccp: CommonConnectionProfileHelper, tls: boo wallet = await Wallets.newInMemoryWallet(); break; case Constants.FILE_WALLET: - const tempDir: string = path.join(os.tmpdir(), Constants.FILE_WALLET); + const tempDir: string = path.join(__dirname, Constants.LIB_TO_TEMP, Constants.FILE_WALLET); if (fs.existsSync(tempDir)) { - fs.rmdirSync(tempDir); + BaseUtils.recursiveDirDelete(tempDir); } await fs.mkdirSync(tempDir); wallet = await Wallets.newFileSystemWallet(tempDir); @@ -105,17 +120,21 @@ export async function createGateway(ccp: CommonConnectionProfileHelper, tls: boo }; await gateway.connect(ccp.getProfile(), opts); - addGatewayObjectToStateStore(gatewayName, gateway); + const gatewayObj: any = { + profile: ccp.getProfile(), + gateway, + }; + addGatewayObjectToStateStore(gatewayName, gatewayObj); BaseUtils.logMsg(`Gateway ${gatewayName} connected`, undefined); } function addGatewayObjectToStateStore(gatewayName: string, gateway: any): void { let gateways: Map = stateStore.get(Constants.GATEWAYS); if (gateways) { - gateways.set(gatewayName, { gateway }); + gateways.set(gatewayName, gateway); } else { gateways = new Map(); - gateways.set(gatewayName, { gateway }); + gateways.set(gatewayName, gateway); } stateStore.set(Constants.GATEWAYS, gateways); @@ -183,7 +202,7 @@ async function identitySetup(wallet: Wallet, ccp: CommonConnectionProfileHelper, * @param {String} txnType the type of transaction (submit/evaluate) * @return {Object} resolved Promise if a submit transaction; evaluate result if not */ -export async function performGatewayTransaction(gatewayName: string, contractName: string, channelName: string, args: string, txnType: string): Promise { +export async function performGatewayTransaction(gatewayName: string, contractName: string, channelName: string, args: string, txnType: string): Promise { // What type of txn is this? if (txnTypes.indexOf(txnType) === -1) { throw new Error(`Unknown transaction type ${txnType}, must be one of ${txnTypes}`); @@ -211,18 +230,193 @@ export async function performGatewayTransaction(gatewayName: string, contractNam const result: Buffer = await contract.submitTransaction(func, ...funcArgs); gatewayObj.result = {type: 'submit', response: result.toString()}; BaseUtils.logMsg('Successfully submitted transaction [' + func + ']', undefined); - return result.toString(); } else { BaseUtils.logMsg('Evaluating transaction [' + func + '] with arguments ' + args, undefined); const result: Buffer = await contract.evaluateTransaction(func, ...funcArgs); - BaseUtils.logMsg('Successfully evaluated transaction [' + func + '] with result [' + result + ']', undefined); - gatewayObj.result = {type: 'evaluate', response: result.toString()}; - return result.toString(); + BaseUtils.logMsg('Successfully evaluated transaction [' + func + '] with result [' + result.toString() + ']', undefined); + gatewayObj.result = {type: 'evaluate', response: JSON.parse(result.toString())}; } } catch (err) { - gatewayObj.result = {type: 'error', result: err.toString()}; - BaseUtils.logError(err, undefined); - throw err; + gatewayObj.result = {type: 'error', response: err.toString()}; + // Don't log the full error, since we might be forcing the error + BaseUtils.logError(err.toString(), undefined); + } +} + +/** + * Perform a transaction using a handler + * @param gatewayName the name of the gateway to use + * @param ccName chaincode name to use + * @param channelName chanel to submit on + * @param args transaction arguments + * @param txnType the type of transaction (submit/evaluate) + * @param handlerOption the handler option to use + */ +export async function performHandledGatewayTransaction(gatewayName: string, ccName: string, channelName: string, args: string, txnType: string, handlerOption: string): Promise { + // Split args out, we need these for later + const argArray: string[] = args.slice(1, -1).split(','); + const func: string = argArray[0]; + const funcArgs: string[] = argArray.slice(1); + + // Retrieve the base gateway + const gateways: Map = stateStore.get(Constants.GATEWAYS); + + if (!gateways || !gateways.has(gatewayName)) { + throw new Error(`Gateway named ${gatewayName} is not present in the state store ${Object.keys(gateways)}`); + } + + const gatewayObj: any = gateways.get(gatewayName); + const gateway: Gateway = gatewayObj.gateway; + + const currentOptions: GatewayOptions = gateway.getOptions(); + + // Disconnect + await gateway.disconnect(); + + // Reconnect with new options based on modifying existing with handler option + const submit: boolean = ( txnType.localeCompare('submit') === 0 ); + if (submit) { + // add event handler options + if (handlerOption.localeCompare('custom') === 0) { + currentOptions.eventHandlerOptions = { + strategy: sampleTxnEventStrategy as TxEventHandlerFactory + }; + } else { + currentOptions.eventHandlerOptions = { + strategy: EventStrategies[handlerOption] + }; + } + } else { + // Add queryHandlerOptions + if (handlerOption.localeCompare('custom') === 0) { + currentOptions.queryHandlerOptions = { + strategy: sampleQueryStrategy as QueryHandlerFactory + }; + } else { + currentOptions.queryHandlerOptions = { + strategy: QueryStrategies[handlerOption] + }; + } + } + + // Reconnect + await gateway.connect(gatewayObj.profile, currentOptions); + + // Retrieve contract + const network: Network = await gateway.getNetwork(channelName); + const contract: Contract = network.getContract(ccName); + + // Build a transaction + const transaction: Transaction = contract.createTransaction(func); + const transactionId: string = transaction.getTransactionID().getTransactionID(); + + // Submit/evaluate transaction + if (submit) { + // Create event hubs to monitor so we can test it worked + const org1EventHub: ChannelEventHub = await getInternalEventHubForOrg(gateway, channelName, 'Org1MSP'); + const org2EventHub: ChannelEventHub = await getInternalEventHubForOrg(gateway, channelName, 'Org2MSP'); + + let org1EventsFired: any = 0; + let org2EventsFired: number = 0; + org1EventHub.registerTxEvent('all', (txId: string, code: string) => { + if (code === 'VALID' && txId === transactionId) { + org1EventsFired++; + } + }, () => { + // Ignore errors + }); + org2EventHub.registerTxEvent('all', (txId: string, code: string) => { + if (code === 'VALID' && txId === transactionId) { + org2EventsFired++; + } + }, () => { + // Ignore errors + }); + + try { + // Split args, do not capture response + await transaction.submit(...funcArgs); + BaseUtils.logMsg(`Successfully submitted transaction [${func}] using handler [${EventStrategies[handlerOption]}]`, undefined); + } catch (error) { + gatewayObj.result = {type: 'error', response: error.toString()}; + BaseUtils.logError(error.toString(), undefined); + } + + // Record result on gateway object + const events: any = { + Org1: org1EventsFired, + Org2: org2EventsFired, + }; + + gatewayObj.result = {type: 'event', response: JSON.stringify(events)}; + + } else { + // No event hubs, just query away + try { + // Split args, capture response + const result: Buffer = await transaction.evaluate(...funcArgs); + BaseUtils.logMsg(`Successfully evaluated transaction [${func}] using handler [${QueryStrategies[handlerOption]}] with result [${result}]`, undefined); + gatewayObj.result = {type: 'evaluate', response: JSON.parse(result.toString())}; + } catch (error) { + gatewayObj.result = {type: 'error', response: error.toString()}; + BaseUtils.logError(error.toString(), undefined); + } + } +} + +/** + * Perform a transaction that uses transient data + * @param gatewayName the gateway to use + * @param ccName the chaincode name + * @param channelName the channel to submit on + * @param txnArgs transaction arguments [methodName, methodArgs...] + * @param txnType the type of transaction (submit/evaluate) + */ +export async function performTransientGatewayTransaction(gatewayName: string, ccName: string, channelName: string, args: string, txnType: string): Promise { + // Split args out, we need these for later + const argArray: string[] = args.slice(1, -1).split(','); + const func: string = argArray[0]; + const funcArgs: string[] = argArray.slice(1); + + // Retrieve the base gateway + const gateways: Map = stateStore.get(Constants.GATEWAYS); + + if (!gateways || !gateways.has(gatewayName)) { + throw new Error(`Gateway named ${gatewayName} is not present in the state store ${Object.keys(gateways)}`); + } + + // Retrieve gateway and contract + const gatewayObj: any = gateways.get(gatewayName); + const gateway: Gateway = gatewayObj.gateway; + const network: Network = await gateway.getNetwork(channelName); + const contract: Contract = network.getContract(ccName); + + // Build a transaction + const transaction: Transaction = contract.createTransaction(func); + const transactionId: string = transaction.getTransactionID().getTransactionID(); + + // Build Transient data + const transientMap: TransientMap = {}; + let i: number = 0; + for (const value of funcArgs) { + transientMap[`key${i}`] = Buffer.from(value); + i++; + } + + try { + const submit: boolean = ( txnType.localeCompare('submit') === 0 ); + if (submit) { + const result: Buffer = await transaction.setTransient(transientMap).submit(); + BaseUtils.logMsg(`Successfully submitted transaction [${func}] with transient data`, undefined); + gatewayObj.result = {type: 'submit', response: JSON.parse(result.toString())}; + } else { + const result: Buffer = await transaction.setTransient(transientMap).evaluate(); + BaseUtils.logMsg(`Successfully evaluated transaction [${func}] with transient data`, undefined); + gatewayObj.result = {type: 'evaluate', response: JSON.parse(result.toString())}; + } + } catch (error) { + gatewayObj.result = {type: 'error', response: error.toString()}; + BaseUtils.logError(error.toString(), undefined); } } @@ -282,9 +476,13 @@ export function getLastTransactionResult(gatewayName: string): any { * @param {String} gatewayName the gateway to get the response from * @param {*} msg the message to compare against */ -export function lastTransactionResponseCompare(gatewayName: string, msg: string): boolean { +export function lastTransactionResponseCompare(gatewayName: string, msg: string, exactMatch: boolean): boolean { const gatewayObj: any = getGatewayObject(gatewayName); - return (gatewayObj.result.response.localeCompare(msg) === 0); + if (exactMatch) { + return (gatewayObj.result.response.localeCompare(JSON.parse(msg)) === 0); + } else { + return gatewayObj.result.response.includes(JSON.parse(msg)); + } } /** @@ -311,3 +509,13 @@ export async function disconnectAllGateways(): Promise { throw err; } } + +async function getInternalEventHubForOrg(gateway: Gateway, channelName: string, orgMSP: string): Promise { + const network: Network = await gateway.getNetwork(channelName); + const channel: Client.Channel = network.getChannel(); + const orgPeer: any = channel.getPeersForOrg(orgMSP)[0]; // Only one peer per org in the test configuration + + // Using private functions to get hold of an internal event hub. Don't try this at home, kids! + const eventHubManager: EventHubManager = (network as any).getEventHubManager(); + return eventHubManager.getEventHub(orgPeer); +} diff --git a/test/ts-scenario/steps/lib/utility/baseUtils.ts b/test/ts-scenario/steps/lib/utility/baseUtils.ts index 425520d466..8890dae5f8 100644 --- a/test/ts-scenario/steps/lib/utility/baseUtils.ts +++ b/test/ts-scenario/steps/lib/utility/baseUtils.ts @@ -7,6 +7,8 @@ import { Constants } from '../../constants'; import { StateStore } from './stateStore'; +import * as fs from 'fs'; + const stateStore: StateStore = StateStore.getInstance(); /** @@ -116,3 +118,23 @@ export function logScenarioStart(featureType: string): void { logMsg('**********************************************************************************', undefined); logMsg('**********************************************************************************\n\n\n', undefined); } + +export function recursiveDirDelete(dirPath: string): void { + let files: string[]; + try { + files = fs.readdirSync(dirPath); + } catch (error) { + return; + } + if (files.length > 0) { + for (let i: number = 0; i < files.length; i++) { + const filePath: string = dirPath + '/' + files[i]; + if (fs.statSync(filePath).isFile()) { + fs.unlinkSync(filePath); + } else { + recursiveDirDelete(filePath); + } + } + fs.rmdirSync(dirPath); + } + } diff --git a/test/ts-scenario/steps/network-model.ts b/test/ts-scenario/steps/network-model.ts index 10a55dacd9..97f61f9b3c 100644 --- a/test/ts-scenario/steps/network-model.ts +++ b/test/ts-scenario/steps/network-model.ts @@ -17,16 +17,17 @@ const stateStore: StateStore = StateStore.getInstance(); Given(/^I have a (.+?) backed gateway named (.+?) with discovery set to (.+?) for user (.+?) using the connection profile named (.+?)$/, { timeout: Constants.STEP_MED as number }, async (walletType: string, gatewayName: string, useDiscovery: string, userName: string, ccpName: string) => { - const gateways: any = stateStore.get(Constants.GATEWAYS); + const gateways: Map = stateStore.get(Constants.GATEWAYS); const fabricState: any = stateStore.get(Constants.FABRIC_STATE); const tls: boolean = (fabricState.type.localeCompare('tls') === 0); - if (gateways && Object.keys(gateways).includes(gatewayName)) { + if (gateways && gateways.has(gatewayName)) { BaseUtils.logMsg(`Gateway named ${gatewayName} already exists`, undefined); return; } else { try { // Create and persist the new gateway + BaseUtils.logMsg(`Creating new Gateway named ${gatewayName}`, undefined); const profilePath: string = path.join(__dirname, '../config', ccpName); const ccp: CommonConnectionProfileHelper = new CommonConnectionProfileHelper(profilePath, true); return await Gateway.createGateway(ccp, tls, userName, Constants.DEFAULT_ORG, gatewayName, JSON.parse(useDiscovery), walletType); @@ -47,6 +48,14 @@ When(/^I use the gateway named (.+?) to (.+?) a total of (.+?) transactions with } }); +When(/^I modify (.+?) to (.+?) a transaction with args (.+?) for contract (.+?) instantiated on channel (.+?) using handler option (.+?)$/, { timeout: Constants.STEP_MED as number }, async (gatewayName: string, txnType: string, txnArgs: string, ccName: string, channelName: string, handlerOption: string) => { + return await Gateway.performHandledGatewayTransaction(gatewayName, ccName, channelName, txnArgs, txnType, handlerOption); +}); + +When(/^I modify (.+?) to (.+?) a transaction with transient data using args (.+?) for contract (.+?) instantiated on channel (.+?)$/, { timeout: Constants.STEP_MED as number }, async (gatewayName: string, txnType: string, txnArgs: string, ccName: string, channelName: string) => { + return await Gateway.performTransientGatewayTransaction(gatewayName, ccName, channelName, txnArgs, txnType); +}); + Then(/^The gateway named (.+?) has a (.+?) type response$/, { timeout: Constants.STEP_LONG as number }, async (gatewayName: string, type: string) => { if (Gateway.lastTransactionTypeCompare(gatewayName, type)) { return Promise.resolve(); @@ -55,9 +64,9 @@ Then(/^The gateway named (.+?) has a (.+?) type response$/, { timeout: Constants } }); -Then(/^The gateway named (.+?) has a (.+?) type response matching (.+?)$/, { timeout: Constants.STEP_LONG as number }, async (gatewayName: string, type: string, expected: string) => { +Then(/^The gateway named (.+?) has a (.+?) type response (matching|containing) (.+?)$/, { timeout: Constants.STEP_LONG as number }, async (gatewayName: string, type: string, matchType: string, expected: string) => { const sameType: boolean = Gateway.lastTransactionTypeCompare(gatewayName, type); - const sameResponse: boolean = Gateway.lastTransactionResponseCompare(gatewayName, expected); + const sameResponse: boolean = Gateway.lastTransactionResponseCompare(gatewayName, expected, matchType === 'matching'); if (sameType && sameResponse) { return Promise.resolve(); } else { diff --git a/test/ts-scenario/support/hooks.ts b/test/ts-scenario/support/hooks.ts index ceffc5287c..7b442632ac 100644 --- a/test/ts-scenario/support/hooks.ts +++ b/test/ts-scenario/support/hooks.ts @@ -10,9 +10,7 @@ import * as BaseUtils from '../steps/lib/utility/baseUtils'; import { CommandRunner } from '../steps/lib/utility/commandRunner'; import { StateStore } from '../steps/lib/utility/stateStore'; -import { After, AfterAll } from 'cucumber'; - -// const networkUtils = require('../lib/network'); +import { AfterAll } from 'cucumber'; const commandRunner: CommandRunner = CommandRunner.getInstance(); const stateStore: StateStore = StateStore.getInstance(); @@ -27,7 +25,7 @@ AfterAll({ timeout: Constants.HUGE_TIME as number }, async () => { await commandRunner.runShellCommand(undefined, 'docker rmi $(docker images dev-* -q)'); }); -After({tags: '@clean-gateway', timeout: Constants.HUGE_TIME as number}, async () => { +AfterAll({timeout: Constants.HUGE_TIME as number}, async () => { // If a test fails without disconnecting gateways, then the tests will hang BaseUtils.logMsg('Disconnecting from all gateways ...', null); await Gateway.disconnectAllGateways(); diff --git a/test/typescript/declarations.d.ts b/test/typescript/declarations.d.ts deleted file mode 100644 index cf21420d14..0000000000 --- a/test/typescript/declarations.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - Copyright 2018 IBM All Rights Reserved. - - SPDX-License-Identifier: Apache-2.0 -*/ - -declare module '*'; diff --git a/test/typescript/integration/network-e2e/invoke.ts b/test/typescript/integration/network-e2e/invoke.ts deleted file mode 100644 index 8f9b7eeb09..0000000000 --- a/test/typescript/integration/network-e2e/invoke.ts +++ /dev/null @@ -1,1091 +0,0 @@ -/** - * Copyright 2018 IBM All Rights Reserved. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -// This is an end-to-end test that focuses on exercising all parts of the fabric APIs -// in a happy-path scenario -'use strict'; - -// tslint:disable:typedef -import fs = require('fs-extra'); -import os = require('os'); -import path = require('path'); -import rimraf = require('rimraf'); -import tape = require('tape'); -import tapePromise = require('tape-promise'); - -import { - Contract, - DefaultEventHandlerStrategies, - Gateway, - GatewayOptions, - Transaction, - TransientMap, - Wallet, - Wallets, - X509Identity, -} from 'fabric-network'; - -import { - ChannelEventHub, - RegistrationOpts, -} from 'fabric-client'; - -import e2eUtils = require('../../../integration/e2e/e2eUtils.js'); -import sampleEventStrategy = require('../../../integration/network-e2e/sample-transaction-event-handler'); -import testUtils = require('../../../integration/util.js'); - -const test: any = tapePromise.default(tape); -const channelName: string = testUtils.NETWORK_END2END.channel; -const chaincodeId: string = testUtils.NETWORK_END2END.chaincodeId; - -const fixtures = process.cwd() + '/test/fixtures'; -const credPath = fixtures + '/crypto-material/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp'; -const certificatePem: string = fs.readFileSync(credPath + '/signcerts/User1@org1.example.com-cert.pem').toString(); -const privateKeyPem: string = fs.readFileSync(credPath + '/keystore/key.pem').toString(); -const ccp: Buffer = fs.readFileSync(fixtures + '/profiles/network.json'); -const ccpDiscovery: Buffer = fs.readFileSync(fixtures + '/profiles/network-discovery.json'); -const identityName = 'User1@org1.example.com'; -const tlsIdentityName = 'tlsId'; - -const expectedMoveResult = 'move succeed'; - -let inMemoryWallet: Wallet | undefined; -async function getWallet(): Promise { - if (!inMemoryWallet) { - inMemoryWallet = await Wallets.newInMemoryWallet(); - await identitySetup(inMemoryWallet); - } - return inMemoryWallet; -} - -async function identitySetup(wallet: Wallet): Promise { - const identity: X509Identity = { - credentials: { - certificate: certificatePem, - privateKey: privateKeyPem, - }, - mspId: 'Org1MSP', - type: 'X.509', - }; - await wallet.put(identityName, identity); - - const tlsInfo = await e2eUtils.tlsEnroll('org1'); - const tlsIdentity: X509Identity = { - credentials: { - certificate: tlsInfo.certificate, - privateKey: tlsInfo.key, - }, - mspId: 'org1', - type: 'X.509', - }; - await wallet.put(tlsIdentityName, tlsIdentity); -} - -async function createContract(t: tape.Test, gateway: Gateway, gatewayOptions: GatewayOptions): Promise { - const useDiscovery = !(gatewayOptions.discovery && gatewayOptions.discovery.enabled === false); - const profile = useDiscovery ? ccpDiscovery : ccp; - await gateway.connect(JSON.parse(profile.toString()), gatewayOptions); - t.pass('Connected to the gateway'); - - const network = await gateway.getNetwork(channelName); - t.pass('Initialized the network, ' + channelName); - - const contract = network.getContract(chaincodeId); - t.pass('Got the contract'); - - return contract; -} - -async function getInternalEventHubForOrg(gateway: Gateway, orgMSP: string): Promise { - const network = await gateway.getNetwork(channelName); - const channel = network.getChannel(); - const orgPeer = channel.getPeersForOrg(orgMSP)[0]; // Only one peer per org in the test configuration - - // Using private functions to get hold of an internal event hub. Don't try this at home, kids! - const eventHubManager = (network as any).getEventHubManager(); - return eventHubManager.getEventHub(orgPeer); -} - -async function testErrorResponse(t: tape.Test, contract: Contract): Promise { - const errorMessage = 'TRANSACTION_ERROR_RESPONSE_MESSAGE'; - - try { - const response = await contract.submitTransaction('returnError', errorMessage); - t.fail('Transaction "returnError" should have thrown an error. Got response: ' + response.toString()); - } catch (expectedErr) { - if (expectedErr.message.includes(errorMessage)) { - t.pass('Successfully handled invocation errors'); - } else { - t.fail('Unexpected exception: ' + expectedErr); - } - } -} - -async function createTempDir(): Promise { - const prefix = path.join(os.tmpdir(), 'integration-network-test-'); - return await fs.mkdtemp(prefix); -} - -test('\n\n***** Network End-to-end flow: import identity into wallet and configure tls *****\n\n', async (t: tape.Test) => { - try { - const wallet = await getWallet(); - const identity = await wallet.get(identityName); - if (identity) { - t.pass('Successfully imported User1@org1.example.com into wallet'); - } else { - t.fail('Failed to import User1@org1.example.com into wallet'); - } - } catch (err) { - t.fail('Failed to import identity into wallet and configure tls. ' + err.stack ? err.stack : err); - } - t.end(); -}); - -test('\n\n***** Network End-to-end flow: invoke transaction to move money using in memory wallet and default event strategy with discovery *****\n\n', async (t: tape.Test) => { - const gateway = new Gateway(); - let org1EventHub: ChannelEventHub | undefined; - - try { - const wallet = await getWallet(); - const contract = await createContract(t, gateway, { - clientTlsIdentity: tlsIdentityName, - discovery: { - asLocalhost: true, - enabled: true, - }, - identity: identityName, - wallet, - }); - - const transaction = contract.createTransaction('move'); - const transactionId = transaction.getTransactionID().getTransactionID(); - - // Obtain an event hub that that will be used by the underlying implementation - org1EventHub = await getInternalEventHubForOrg(gateway, 'Org1MSP'); - const org2EventHub = await getInternalEventHubForOrg(gateway, 'Org2MSP'); - - let eventFired = 0; - org1EventHub.registerTxEvent('all', (txId, code) => { - if (code === 'VALID' && txId === transactionId) { - eventFired++; - } - }, () => { - // Ignore errors - }); - - const response = await transaction.submit('a', 'b', '100'); - - t.true(org1EventHub.isconnected(), 'org1 event hub correctly connected'); - t.false(org2EventHub.isconnected(), 'org2 event hub correctly not connected'); - t.equal(eventFired, 1, 'single event for org1 correctly unblocked submitTransaction'); - - if (response.toString() === expectedMoveResult) { - t.pass('Successfully invoked transaction chaincode on channel'); - } else { - t.fail('Unexpected response from transaction chaincode: ' + response); - } - } catch (err) { - t.fail('Failed to invoke transaction chaincode on channel. ' + err.stack ? err.stack : err); - } finally { - gateway.disconnect(); - t.false(org1EventHub && org1EventHub.isconnected(), 'org1 event hub correctly been disconnected'); - } - - t.end(); -}); - -test('\n\n***** Network End-to-end flow: invoke multiple transactions to move money using in memory wallet and default event strategy *****\n\n', async (t: tape.Test) => { - const gateway = new Gateway(); - let org1EventHub: ChannelEventHub | undefined; - - try { - const wallet = await getWallet(); - const contract = await createContract(t, gateway, { - clientTlsIdentity: tlsIdentityName, - discovery: { - asLocalhost: true, // Redundant since discovery is disabled but ensures TS definitions are correct - enabled: false, - }, - identity: identityName, - wallet, - }); - - const transactions: Transaction[] = []; - const transactionIds: string[] = []; - for (let i = 0; i < 3; i++) { - const transaction = contract.createTransaction('move'); - transactions.push(transaction); - transactionIds.push(transaction.getTransactionID().getTransactionID()); - } - - // Obtain an event hub that that will be used by the underlying implementation - org1EventHub = await getInternalEventHubForOrg(gateway, 'Org1MSP'); - const org2EventHub = await getInternalEventHubForOrg(gateway, 'Org2MSP'); - - let eventFired = 0; - org1EventHub.registerTxEvent('all', (txId, code) => { - if (code === 'VALID' && transactionIds.includes(txId)) { - eventFired++; - } - }, () => { - // Ignore errors - }); - - let response = await transactions[0].submit('a', 'b', '100'); - - t.true(org1EventHub.isconnected(), 'org1 event hub correctly connected'); - t.false(org2EventHub.isconnected(), 'org2 event hub correctly not connected'); - t.equal(eventFired, 1, 'single event for org1 correctly unblocked submitTransaction'); - - if (response.toString() === expectedMoveResult) { - t.pass('Successfully invoked first transaction chaincode on channel'); - } else { - t.fail('Unexpected response first from transaction chaincode: ' + response); - } - - // second transaction for same connection - response = await transactions[1].submit('a', 'b', '50'); - - t.equal(eventFired, 2, 'single event for org1 correctly unblocked submitTransaction'); - - if (response.toString() === expectedMoveResult) { - t.pass('Successfully invoked second transaction chaincode on channel'); - } else { - t.fail('Unexpected response from second transaction chaincode: ' + response); - } - - // third transaction for same connection - response = await transactions[2].submit('a', 'b', '25'); - - t.equal(eventFired, 3, 'single event for org1 correctly unblocked submitTransaction'); - - if (response.toString() === expectedMoveResult) { - t.pass('Successfully invoked third transaction chaincode on channel'); - } else { - t.fail('Unexpected response from third transaction chaincode: ' + response); - } - } catch (err) { - t.fail('Failed to invoke transaction chaincode on channel. ' + err.stack ? err.stack : err); - } finally { - gateway.disconnect(); - t.false(org1EventHub && org1EventHub.isconnected(), 'org1 event hub correctly been disconnected'); - } - - t.end(); -}); - -test('\n\n***** Network End-to-end flow: invoke transaction to move money using in memory wallet and MSPID_SCOPE_ALLFORTX event strategy *****\n\n', async (t: tape.Test) => { - const gateway = new Gateway(); - let org1EventHub: ChannelEventHub | undefined; - - try { - const wallet = await getWallet(); - const contract = await createContract(t, gateway, { - clientTlsIdentity: tlsIdentityName, - discovery: { - enabled: false, - }, - eventHandlerOptions: { - strategy: DefaultEventHandlerStrategies.MSPID_SCOPE_ALLFORTX, - }, - identity: identityName, - wallet, - }); - - const transaction = contract.createTransaction('move'); - const transactionId = transaction.getTransactionID().getTransactionID(); - - // Obtain an event hub that that will be used by the underlying implementation - org1EventHub = await getInternalEventHubForOrg(gateway, 'Org1MSP'); - const org2EventHub = await getInternalEventHubForOrg(gateway, 'Org2MSP'); - - let eventFired = 0; - - org1EventHub.registerTxEvent('all', (txId, code) => { - if (code === 'VALID' && txId === transactionId) { - eventFired++; - } - }, () => { - // Ignore errors - }); - - const response = await transaction.submit('a', 'b', '100'); - - t.false(org2EventHub.isconnected(), 'org2 event hub correctly not connected'); - t.equal(eventFired, 1, 'single event for org1 correctly unblocked submitTransaction'); - - if (response.toString() === expectedMoveResult) { - t.pass('Successfully invoked transaction chaincode on channel'); - } else { - t.fail('Unexpected response from transaction chaincode: ' + response); - } - } catch (err) { - t.fail('Failed to invoke transaction chaincode on channel. ' + err.stack ? err.stack : err); - } finally { - gateway.disconnect(); - t.false(org1EventHub && org1EventHub.isconnected(), 'org1 event hub correctly been disconnected'); - } - - t.end(); -}); - -test('\n\n***** Network End-to-end flow: invoke transaction to move money using in memory wallet and MSPID_SCOPE_ANYFORTX event strategy *****\n\n', async (t: tape.Test) => { - const gateway = new Gateway(); - let org1EventHub: ChannelEventHub | undefined; - - try { - const wallet = await getWallet(); - const contract = await createContract(t, gateway, { - clientTlsIdentity: tlsIdentityName, - discovery: { - enabled: false, - }, - eventHandlerOptions: { - strategy: DefaultEventHandlerStrategies.MSPID_SCOPE_ANYFORTX, - }, - identity: identityName, - wallet, - }); - - const transaction = contract.createTransaction('move'); - const transactionId = transaction.getTransactionID().getTransactionID(); - - // Obtain an event hub that that will be used by the underlying implementation - org1EventHub = await getInternalEventHubForOrg(gateway, 'Org1MSP'); - const org2EventHub = await getInternalEventHubForOrg(gateway, 'Org2MSP'); - - let eventFired = 0; - - org1EventHub.registerTxEvent('all', (txId, code) => { - if (code === 'VALID' && txId === transactionId) { - eventFired++; - } - }, () => { - // Ignore errors - }); - - const response = await transaction.submit('a', 'b', '100'); - - t.false(org2EventHub.isconnected(), 'org2 event hub correctly not connected'); - t.equal(eventFired, 1, 'single event for org1 correctly unblocked submitTransaction'); - - if (response.toString() === expectedMoveResult) { - t.pass('Successfully invoked transaction chaincode on channel'); - } else { - t.fail('Unexpected response from transaction chaincode: ' + response); - } - } catch (err) { - t.fail('Failed to invoke transaction chaincode on channel. ' + err.stack ? err.stack : err); - } finally { - gateway.disconnect(); - t.false(org1EventHub && org1EventHub.isconnected(), 'org1 event hub correctly been disconnected'); - } - - t.end(); -}); - -test('\n\n***** Network End-to-end flow: invoke transaction to move money using in memory wallet and NETWORK_SCOPE_ALLFORTX event strategy *****\n\n', async (t: tape.Test) => { - const gateway = new Gateway(); - let org1EventHub: ChannelEventHub | undefined; - let org2EventHub: ChannelEventHub | undefined; - - try { - const wallet = await getWallet(); - const contract = await createContract(t, gateway, { - clientTlsIdentity: tlsIdentityName, - discovery: { - enabled: false, - }, - eventHandlerOptions: { - strategy: DefaultEventHandlerStrategies.NETWORK_SCOPE_ALLFORTX, - }, - identity: identityName, - wallet, - }); - - const transaction = contract.createTransaction('move'); - const transactionId = transaction.getTransactionID().getTransactionID(); - - // Obtain an event hub that that will be used by the underlying implementation - org1EventHub = await getInternalEventHubForOrg(gateway, 'Org1MSP'); - org2EventHub = await getInternalEventHubForOrg(gateway, 'Org2MSP'); - - let org1EventFired = 0; - let org2EventFired = 0; - org1EventHub.registerTxEvent('all', (txId, code) => { - if (code === 'VALID' && txId === transactionId) { - org1EventFired++; - } - }, () => { - // Ignore errors - }); - org2EventHub.registerTxEvent('all', (txId, code) => { - if (code === 'VALID' && txId === transactionId) { - org2EventFired++; - } - }, () => { - // Ignore errors - }); - - const response = await transaction.submit('a', 'b', '100'); - - const unblockCorrectly = (org1EventFired === 1) && (org2EventFired === 1); - t.pass(`org1 events: ${org1EventFired}, org2 events: ${org2EventFired}`); - t.true(unblockCorrectly, 'got single events at both org event hubs before submitTransaction was unblocked'); - - if (response.toString() === expectedMoveResult) { - t.pass('Successfully invoked transaction chaincode on channel'); - } else { - t.fail('Unexpected response from transaction chaincode: ' + response); - } - } catch (err) { - t.fail('Failed to invoke transaction chaincode on channel. ' + err.stack ? err.stack : err); - } finally { - gateway.disconnect(); - t.false(org1EventHub && org1EventHub.isconnected(), 'org1 event hub correctly been disconnected'); - t.false(org2EventHub && org2EventHub.isconnected(), 'org2 event hub correctly been disconnected'); - } - - t.end(); -}); - -test('\n\n***** Network End-to-end flow: invoke transaction to move money using in memory wallet and NETWORK_SCOPE_ALLFORTX event strategy with discovery *****\n\n', async (t: tape.Test) => { - const gateway = new Gateway(); - let org1EventHub: ChannelEventHub | undefined; - let org2EventHub: ChannelEventHub | undefined; - - try { - const wallet = await getWallet(); - const contract = await createContract(t, gateway, { - clientTlsIdentity: tlsIdentityName, - discovery: { - asLocalhost: true, - enabled: true, - }, - eventHandlerOptions: { - strategy: DefaultEventHandlerStrategies.NETWORK_SCOPE_ALLFORTX, - }, - identity: identityName, - wallet, - }); - - const transaction = contract.createTransaction('move'); - const transactionId = transaction.getTransactionID().getTransactionID(); - - // Obtain an event hub that that will be used by the underlying implementation - org1EventHub = await getInternalEventHubForOrg(gateway, 'Org1MSP'); - org2EventHub = await getInternalEventHubForOrg(gateway, 'Org2MSP'); - - let org1EventFired = 0; - let org2EventFired = 0; - org1EventHub.registerTxEvent('all', (txId, code) => { - if (code === 'VALID' && txId === transactionId) { - org1EventFired++; - } - }, () => { - // Ignore errors - }); - org2EventHub.registerTxEvent('all', (txId, code) => { - if (code === 'VALID' && txId === transactionId) { - org2EventFired++; - } - }, () => { - // Ignore errors - }); - - const response = await transaction.submit('a', 'b', '100'); - - const unblockCorrectly = (org1EventFired === 1) && (org2EventFired === 1); - t.pass(`org1 events: ${org1EventFired}, org2 events: ${org2EventFired}`); - t.true(unblockCorrectly, 'got single events at both org event hubs before submitTransaction was unblocked'); - - if (response.toString() === expectedMoveResult) { - t.pass('Successfully invoked transaction chaincode on channel'); - } else { - t.fail('Unexpected response from transaction chaincode: ' + response); - } - } catch (err) { - t.fail('Failed to invoke transaction chaincode on channel. ' + err.stack ? err.stack : err); - } finally { - gateway.disconnect(); - t.false(org1EventHub && org1EventHub.isconnected(), 'org1 event hub correctly been disconnected'); - t.false(org2EventHub && org2EventHub.isconnected(), 'org2 event hub correctly been disconnected'); - } - - t.end(); -}); - -test('\n\n***** Network End-to-end flow: invoke transaction to move money using in memory wallet and NETWORK_SCOPE_ANYFORTX event strategy *****\n\n', async (t: tape.Test) => { - const gateway = new Gateway(); - let org1EventHub: ChannelEventHub | undefined; - let org2EventHub: ChannelEventHub | undefined; - - try { - const wallet = await getWallet(); - const contract = await createContract(t, gateway, { - clientTlsIdentity: tlsIdentityName, - discovery: { - enabled: false, - }, - eventHandlerOptions: { - strategy: DefaultEventHandlerStrategies.NETWORK_SCOPE_ANYFORTX, - }, - identity: identityName, - wallet, - }); - - const transaction = contract.createTransaction('move'); - const transactionId = transaction.getTransactionID().getTransactionID(); - - // Obtain an event hub that that will be used by the underlying implementation - org1EventHub = await getInternalEventHubForOrg(gateway, 'Org1MSP'); - org2EventHub = await getInternalEventHubForOrg(gateway, 'Org2MSP'); - - let org1EventFired = 0; - let org2EventFired = 0; - org1EventHub.registerTxEvent('all', (txId, code) => { - if (code === 'VALID' && txId === transactionId) { - org1EventFired++; - } - }, () => { - // Ignore errors - }); - - org2EventHub.registerTxEvent('all', (txId, code) => { - if (code === 'VALID' && txId === transactionId) { - org2EventFired++; - } - }, () => { - // Ignore errors - }); - - const response = await transaction.submit('a', 'b', '100'); - - const unblockCorrectly = (org1EventFired === 1 && org2EventFired === 0) || - (org1EventFired === 0 && org2EventFired === 1); - - t.pass(`org1 events: ${org1EventFired}, org2 events: ${org2EventFired}`); - t.true(unblockCorrectly, 'single event received by one of the event hubs caused submitTransaction to unblock, before other event received'); - - if (response.toString() === expectedMoveResult) { - t.pass('Successfully invoked transaction chaincode on channel'); - } else { - t.fail('Unexpected response from transaction chaincode: ' + response); - } - } catch (err) { - t.fail('Failed to invoke transaction chaincode on channel. ' + err.stack ? err.stack : err); - } finally { - gateway.disconnect(); - t.false(org1EventHub && org1EventHub.isconnected(), 'org1 event hub correctly been disconnected'); - t.false(org2EventHub && org2EventHub.isconnected(), 'org2 event hub correctly been disconnected'); - } - - t.end(); -}); - -test('\n\n***** Network End-to-end flow: invoke transaction to move money using in memory wallet and NETWORK_SCOPE_ANYFORTX event strategy with discovery *****\n\n', async (t: tape.Test) => { - const gateway = new Gateway(); - let org1EventHub: ChannelEventHub | undefined; - let org2EventHub: ChannelEventHub | undefined; - - try { - const wallet = await getWallet(); - const contract = await createContract(t, gateway, { - clientTlsIdentity: tlsIdentityName, - discovery: { - asLocalhost: true, - enabled: true, - }, - eventHandlerOptions: { - strategy: DefaultEventHandlerStrategies.NETWORK_SCOPE_ANYFORTX, - }, - identity: identityName, - wallet, - }); - - const transaction = contract.createTransaction('move'); - const transactionId = transaction.getTransactionID().getTransactionID(); - - // Obtain an event hub that that will be used by the underlying implementation - org1EventHub = await getInternalEventHubForOrg(gateway, 'Org1MSP'); - org2EventHub = await getInternalEventHubForOrg(gateway, 'Org2MSP'); - - let org1EventFired = 0; - let org2EventFired = 0; - org1EventHub.registerTxEvent('all', (txId, code) => { - if (code === 'VALID' && txId === transactionId) { - org1EventFired++; - } - }, () => { - // Ignore errors - }); - org2EventHub.registerTxEvent('all', (txId, code) => { - if (code === 'VALID' && txId === transactionId) { - org2EventFired++; - } - }, () => { - // Ignore errors - }); - - const response = await transaction.submit('a', 'b', '100'); - - const unblockCorrectly = (org1EventFired === 1 && org2EventFired === 0) || - (org1EventFired === 0 && org2EventFired === 1); - - t.pass(`org1 events: ${org1EventFired}, org2 events: ${org2EventFired}`); - t.true(unblockCorrectly, 'single event received by one of the event hubs caused submitTransaction to unblock, before other event received'); - - if (response.toString() === expectedMoveResult) { - t.pass('Successfully invoked transaction chaincode on channel'); - } else { - t.fail('Unexpected response from transaction chaincode: ' + response); - } - } catch (err) { - t.fail('Failed to invoke transaction chaincode on channel. ' + err.stack ? err.stack : err); - } finally { - gateway.disconnect(); - t.false(org1EventHub && org1EventHub.isconnected(), 'org1 event hub correctly been disconnected'); - t.false(org2EventHub && org2EventHub.isconnected(), 'org2 event hub correctly been disconnected'); - } - - t.end(); -}); - -test('\n\n***** Network End-to-end flow: invoke transaction to move money using in memory wallet and plug-in event strategy *****\n\n', async (t: tape.Test) => { - const gateway = new Gateway(); - - try { - const wallet = await getWallet(); - const contract = await createContract(t, gateway, { - clientTlsIdentity: tlsIdentityName, - discovery: { - enabled: false, - }, - eventHandlerOptions: { - strategy: sampleEventStrategy, - }, - identity: identityName, - wallet, - }); - - const response = await contract.submitTransaction('move', 'a', 'b', '100'); - - if (response.toString() === expectedMoveResult) { - t.pass('Successfully invoked transaction chaincode on channel'); - } else { - t.fail('Unexpected response from transaction chaincode: ' + response); - } - } catch (err) { - t.fail('Failed to invoke transaction chaincode on channel. ' + err.stack ? err.stack : err); - } finally { - gateway.disconnect(); - } - - t.end(); -}); - -test('\n\n***** Network End-to-end flow: invoke transaction with transient data *****\n\n', async (t: tape.Test) => { - const gateway = new Gateway(); - - try { - const wallet = await getWallet(); - const contract = await createContract(t, gateway, { - clientTlsIdentity: tlsIdentityName, - discovery: { - enabled: false, - }, - identity: identityName, - wallet, - }); - - const transaction = contract.createTransaction('getTransient'); - const transientMap: TransientMap = { - key1: Buffer.from('value1'), - key2: Buffer.from('value2'), - }; - const response = await transaction.setTransient(transientMap).submit(); - t.pass('Got response: ' + response.toString('utf8')); - const result = JSON.parse(response.toString('utf8')); - - let success = true; - - if (Object.keys(transientMap).length !== Object.keys(result).length) { - success = false; - } - - Object.entries(transientMap).forEach((entry) => { - const key = entry[0]; - const value = entry[1].toString(); - if (value !== result[key]) { - t.fail(`Expected ${key} to be ${value} but was ${result[key]}`); - success = false; - } - }); - - if (success) { - t.pass('Got expected transaction response'); - } else { - t.fail('Unexpected transaction response: ' + response); - } - } catch (err) { - t.fail('Failed to invoke transaction chaincode on channel. ' + err.stack ? err.stack : err); - } finally { - gateway.disconnect(); - } - - t.end(); -}); - -test('\n\n***** Network End-to-end flow: invoke transaction with empty string response *****\n\n', async (t: tape.Test) => { - const gateway = new Gateway(); - - try { - const wallet = await getWallet(); - const contract = await createContract(t, gateway, { - clientTlsIdentity: tlsIdentityName, - discovery: { - enabled: false, - }, - identity: identityName, - wallet, - }); - - const response = await contract.submitTransaction('echo', ''); - - if (response && response.toString('utf8') === '') { - t.pass('Got expected transaction response'); - } else { - t.fail('Unexpected transaction response: ' + response); - } - } catch (err) { - t.fail('Failed to invoke transaction chaincode on channel. ' + err.stack ? err.stack : err); - } finally { - gateway.disconnect(); - } - - t.end(); -}); - -test('\n\n***** Network End-to-end flow: invoke transaction while channel\'s event hub is replaying blocks *****\n\n', async (t: tape.Test) => { - const gateway = new Gateway(); - - try { - const wallet = await getWallet(); - const contract = await createContract(t, gateway, { - clientTlsIdentity: tlsIdentityName, - discovery: { - enabled: false, - }, - identity: identityName, - wallet, - }); - - const network = await gateway.getNetwork(channelName); - const channel = network.getChannel(); - const peer = channel.getPeersForOrg('Org1MSP')[0]; - const eventHub = channel.getChannelEventHub(peer.getName()); - - let eventsReceived = 0; - let errorsReceived = 0; - - function onEvent(txId: string, code: string, blockNumber: number) { - eventsReceived++; - } - function onError(error: Error) { - errorsReceived++; - } - // Trigger replay of previous blocks, which will prevent any other listeners registering with this event hub - const registrationOptions: RegistrationOpts = { - disconnect: false, - startBlock: 0, - unregister: false, - }; - eventHub.registerTxEvent('all', onEvent, onError, registrationOptions); - eventHub.connect(); - - const response = await contract.submitTransaction('move', 'a', 'b', '100'); - - t.true(eventsReceived > 0, `Received replay events (eventsReceived = ${eventsReceived})`); - t.false(errorsReceived > 0, `No replay errors (errorsReceived = ${errorsReceived})`); - - if (response.toString() === expectedMoveResult) { - t.pass('Successfully invoked transaction chaincode on channel'); - } else { - t.fail('Unexpected response from transaction chaincode: ' + response); - } - } catch (err) { - t.fail('Failed to invoke transaction chaincode on channel. ' + err.stack ? err.stack : err); - } finally { - gateway.disconnect(); - } - - t.end(); -}); - -test('\n\n***** Network End-to-end flow: handle transaction error *****\n\n', async (t: tape.Test) => { - const gateway = new Gateway(); - try { - const wallet = await getWallet(); - const contract = await createContract(t, gateway, { - clientTlsIdentity: tlsIdentityName, - discovery: { - enabled: false, - }, - identity: identityName, - wallet, - }); - - await testErrorResponse(t, contract); - } finally { - gateway.disconnect(); - } - - t.end(); -}); - -test('\n\n***** Network End-to-end flow: invoke transaction to move money using in file system wallet *****\n\n', async (t: tape.Test) => { - const tmpdir = await createTempDir(); - const gateway = new Gateway(); - - try { - const fileSystemWallet: Wallet = await Wallets.newFileSystemWallet(tmpdir); - await identitySetup(fileSystemWallet); - - await gateway.connect(JSON.parse(ccp.toString()), { - clientTlsIdentity: tlsIdentityName, - discovery: { - enabled: false, - }, - identity: identityName, - wallet: fileSystemWallet, - }); - t.pass('Connected to the gateway'); - - const network = await gateway.getNetwork(channelName); - t.pass('Initialized the channel, ' + channelName); - - const contract = await network.getContract(chaincodeId); - t.pass('Got the contract, about to submit "move" transaction'); - - const response = await contract.submitTransaction('move', 'a', 'b', '100'); - - const expectedResult = 'move succeed'; - if (response.toString() === expectedResult) { - t.pass('Successfully invoked transaction chaincode on channel'); - } else { - t.fail('Unexpected response from transaction chaincode: ' + response); - } - - await testErrorResponse(t, contract); - } catch (err) { - t.fail('Failed to invoke transaction chaincode on channel. ' + err.stack ? err.stack : err); - } finally { - // delete the file system wallet. - const rimRafPromise: Promise = new Promise((resolve) => { - rimraf(tmpdir, (err: Error) => { - if (err) { - console.log(`failed to delete ${tmpdir}, error was ${err}`); // tslint:disable-line:no-console - resolve(); - } - resolve(); - }); - }); - await rimRafPromise; - gateway.disconnect(); - } - - t.end(); -}); - -test('\n\n***** Network End-to-end flow: invoke transaction to move money using CouchDB wallet *****\n\n', async (t: tape.Test) => { - const gateway = new Gateway(); - try { - const couchDBWallet = await Wallets.newCouchDBWallet({url: 'http://localhost:5984'}); - await identitySetup(couchDBWallet); - - await gateway.connect(JSON.parse(ccp.toString()), { - clientTlsIdentity: tlsIdentityName, - discovery: { - enabled: false, - }, - identity: identityName, - wallet: couchDBWallet, - }); - t.pass('Connected to the gateway'); - - const network = await gateway.getNetwork(channelName); - t.pass('Initialized the channel, ' + channelName); - - const contract = await network.getContract(chaincodeId); - t.pass('Got the contract, about to submit "move" transaction'); - - const response = await contract.submitTransaction('move', 'a', 'b', '100'); - - const expectedResult = 'move succeed'; - if (response.toString() === expectedResult) { - t.pass('Successfully invoked transaction chaincode on channel'); - } else { - t.fail('Unexpected response from transaction chaincode: ' + response); - } - - await testErrorResponse(t, contract); - } catch (err) { - t.fail('Failed to invoke transaction chaincode on channel. ' + err.stack ? err.stack : err); - } finally { - gateway.disconnect(); - } - - t.end(); -}); - -test('\n\n***** Network End-to-end flow: invoke multiple transactions concurrently *****\n\n', async (t: tape.Test) => { - const gateway = new Gateway(); - - try { - const wallet = await getWallet(); - const contract = await createContract(t, gateway, { - clientTlsIdentity: tlsIdentityName, - discovery: { - enabled: false, - }, - identity: identityName, - wallet, - }); - - const expected = 'RESULT'; - const promises: Array> = []; - for (let i = 0; i < 10; i++) { - promises.push(contract.submitTransaction('echo', expected)); - } - const results = await Promise.all(promises); - const resultStrings = results.map((buffer) => buffer.toString('utf8')); - - const badResults = resultStrings.filter((value) => value !== expected); - if (badResults.length > 0) { - t.fail('Got bad results: ' + badResults.join(', ')); - } else { - t.pass('Got expected results from all transactions'); - } - } catch (err) { - t.fail('Failed to invoke transaction chaincode on channel. ' + err.stack ? err.stack : err); - } finally { - gateway.disconnect(); - } - - t.end(); -}); - -test('\n\n***** Network End-to-end flow: specify endorsing peers *****\n\n', async (t: tape.Test) => { - const gateway = new Gateway(); - try { - const wallet = await getWallet(); - const contract = await createContract(t, gateway, { - clientTlsIdentity: tlsIdentityName, - discovery: { - enabled: false, - }, - identity: identityName, - wallet, - }); - - const network = await gateway.getNetwork(channelName); - const channel = network.getChannel(); - const endorsingPeer = channel.getChannelPeer('peer0.org1.example.com'); - - await contract.createTransaction('echo') - .setEndorsingPeers([endorsingPeer]) - .submit('RESULT'); - t.fail('Transaction was successfully submitted with insufficient endorsing peers'); - } catch (error) { - if (error.message.includes('ENDORSEMENT_POLICY_FAILURE')) { - t.pass('Transaction correctly failed endorsement with a single endorsing peer specified'); - } else { - const stacktrace = error.stack; - t.fail('Transaction failed with unexpected error: ' + stacktrace || error); - } - } finally { - gateway.disconnect(); - } - - t.end(); -}); - -test('\n\n***** Network End-to-end flow: specify endorsing peers when discovery is enabled *****\n\n', async (t: tape.Test) => { - const gateway = new Gateway(); - try { - const wallet = await getWallet(); - const gatewayOptions = { - clientTlsIdentity: 'tlsId', - discovery: { - asLocalhost: false, // leave false so that if use a discovered peer is used it will fail - enabled: true, - }, - identity: 'User1@org1.example.com', - wallet, - }; - - await gateway.connect(JSON.parse(ccp.toString()), gatewayOptions); - t.pass('Connected to the gateway'); - - const network = await gateway.getNetwork(channelName); - t.pass('Initialized the network, ' + channelName); - - const contract = network.getContract(chaincodeId); - t.pass('Got the contract'); - - // these peers will not have the same names as the discovered peers or - // the same URL because asLocalhost is false - const channel = network.getChannel(); - const endorsingPeer1 = channel.getChannelPeer('peer0.org1.example.com'); - const endorsingPeer2 = channel.getChannelPeer('peer0.org2.example.com'); - - await contract.createTransaction('echo') - .setEndorsingPeers([endorsingPeer1, endorsingPeer2]) - .submit('RESULT'); - t.pass('Transaction was successfully submitted with endorsing peers'); - } catch (error) { - const stacktrace = error.stack; - t.fail('Transaction failed with unexpected error: ' + stacktrace || error); - } finally { - gateway.disconnect(); - } - - t.end(); -}); - -test('\n\n***** Network End-to-end flow: invoke transaction to move money using in memory wallet and no event strategy *****\n\n', async (t: tape.Test) => { - const gateway = new Gateway(); - - try { - const wallet = await getWallet(); - const contract = await createContract(t, gateway, { - clientTlsIdentity: tlsIdentityName, - discovery: { - enabled: false, - }, - eventHandlerOptions: { - strategy: null, - }, - identity: identityName, - wallet, - }); - - const response = await contract.submitTransaction('move', 'a', 'b', '100'); - - const expectedResult = 'move succeed'; - if (response.toString() === expectedResult) { - t.pass('Successfully invoked transaction chaincode on channel'); - } else { - t.fail('Unexpected response from transaction chaincode: ' + response); - } - } catch (err) { - t.fail('Failed to invoke transaction chaincode on channel. ' + err.stack ? err.stack : err); - } finally { - gateway.disconnect(); - } - - t.end(); -}); diff --git a/test/typescript/integration/network-e2e/query.ts b/test/typescript/integration/network-e2e/query.ts deleted file mode 100644 index 56628c8c31..0000000000 --- a/test/typescript/integration/network-e2e/query.ts +++ /dev/null @@ -1,378 +0,0 @@ -/** - * Copyright 2018 IBM All Rights Reserved. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -// This is an end-to-end test that focuses on exercising all parts of the fabric APIs -// in a happy-path scenario -'use strict'; - -// tslint:disable:typedef -import fs = require('fs-extra'); -import os = require('os'); -import path = require('path'); -import tape = require('tape'); -import tapePromise = require('tape-promise'); -import util = require('util'); - -import { - Contract, - DefaultQueryHandlerStrategies, - Gateway, - TransientMap, - Wallet, - Wallets, - X509Identity, -} from 'fabric-network'; - -import sampleQueryStrategy = require('./sample-query-handler'); - -import e2eUtils = require('../../../integration/e2e/e2eUtils.js'); -import testUtils = require('../../../integration/util.js'); - -const test: any = tapePromise.default(tape); -const channelName: string = testUtils.NETWORK_END2END.channel; -const chaincodeId: string = testUtils.NETWORK_END2END.chaincodeId; - -const fixtures = process.cwd() + '/test/fixtures'; -const identityLabel = 'User1@org1.example.com'; -const tlsLabel = 'tlsId'; - -async function createWallet(t: tape.Test): Promise { - // define the identity to use - const credPath = fixtures + '/crypto-material/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp'; - const cert = fs.readFileSync(credPath + '/signcerts/User1@org1.example.com-cert.pem').toString(); - const key = fs.readFileSync(credPath + '/keystore/key.pem').toString(); - - const wallet = await Wallets.newInMemoryWallet(); - - // prep wallet and test it at the same time - const identity: X509Identity = { - credentials: { - certificate: cert, - privateKey: key, - }, - mspId: 'Org1MSP', - type: 'X.509', - }; - await wallet.put(identityLabel, identity); - const savedIdentity = await wallet.get(identityLabel); - t.ok(savedIdentity, 'Successfully imported User1@org1.example.com into wallet'); - - const tlsInfo = await e2eUtils.tlsEnroll('org1'); - const tlsIdentity: X509Identity = { - credentials: { - certificate: tlsInfo.certificate, - privateKey: tlsInfo.key, - }, - mspId: 'org1', - type: 'X.509', - }; - await wallet.put(tlsLabel, tlsIdentity); - - return wallet; -} - -async function testSuccessfulQuery(t: tape.Test, contract: Contract): Promise { - t.comment('Testing successful query'); - - const response = await contract.evaluateTransaction('query', 'a'); - - if (!isNaN(parseInt(response.toString(), 10))) { - t.pass('Successfully got back a value'); - } else { - t.fail('Unexpected response from transaction chaincode: ' + response); - } -} - -async function testQueryErrorResponse(t: tape.Test, contract: Contract): Promise { - t.comment('Testing query error response'); - - const errorMessage = 'QUERY_ERROR_RESPONSE_MESSAGE'; - try { - const response = await contract.evaluateTransaction('returnError', errorMessage); - t.fail('Transaction "returnError" should have thrown an error. Got response: ' + response.toString()); - } catch (expectedErr) { - if (expectedErr.isProposalResponse && expectedErr.message.includes(errorMessage)) { - t.pass('Successfully handled query error response'); - } else { - t.fail(util.format('Unexpected exception: %O', expectedErr)); - } - } -} - -async function testChaincodeRuntimeError(t: tape.Test, contract: Contract): Promise { - // No-op for now since chaincode runtime errors are not distinguishable from error responses and introduce a - // significant delay while the chaincode container times out waiting for the chaincide to supply a response - - // t.comment('Testing chaincode runtime error'); - - // const errorMessage = 'QUERY_ERROR_THROWN_MESSAGE'; - // try { - // const response = await contract.evaluateTransaction('throwError', errorMessage); - // t.fail('Transaction "throwError" should have thrown an error. Got response: ' + response.toString()); - // } catch (expectedErr) { - // if (expectedErr.isProposalResponse) { - // t.pass('Successfully handled chaincode runtime error'); - // } else { - // t.fail(util.format('Unexpected exception: %O', expectedErr)); - // } - // } -} - -test('\n\n***** Network End-to-end flow: evaluate transaction with default query handler *****\n\n', async (t: tape.Test) => { - const gateway = new Gateway(); - - try { - const wallet = await createWallet(t); - const ccp: Buffer = fs.readFileSync(fixtures + '/profiles/network.json'); - const ccpObject = JSON.parse(ccp.toString()); - - await gateway.connect(ccpObject, { - clientTlsIdentity: tlsLabel, - discovery: { - enabled: false, - }, - identity: identityLabel, - wallet, - }); - t.pass('Connected to the gateway'); - - const channel = await gateway.getNetwork(channelName); - t.pass('Initialized the channel, ' + channelName); - - const contract = await channel.getContract(chaincodeId); - t.pass('Got the contract, ' + chaincodeId); - - await testSuccessfulQuery(t, contract); - await testQueryErrorResponse(t, contract); - await testChaincodeRuntimeError(t, contract); - } catch (err) { - t.fail('Failed to invoke transaction chaincode on channel. ' + err.stack ? err.stack : err); - } finally { - gateway.disconnect(); - } - - t.end(); -}); - -test('\n\n***** Network End-to-end flow: evaluate transaction with MSPID_SCOPE_ROUND_ROBIN query handler *****\n\n', async (t: tape.Test) => { - const gateway = new Gateway(); - - try { - const wallet = await createWallet(t); - const ccp: Buffer = fs.readFileSync(fixtures + '/profiles/network.json'); - const ccpObject = JSON.parse(ccp.toString()); - - await gateway.connect(ccpObject, { - clientTlsIdentity: tlsLabel, - discovery: { - enabled: false, - }, - identity: identityLabel, - queryHandlerOptions: { - strategy: DefaultQueryHandlerStrategies.MSPID_SCOPE_ROUND_ROBIN, - }, - wallet, - }); - t.pass('Connected to the gateway'); - - const channel = await gateway.getNetwork(channelName); - t.pass('Initialized the channel, ' + channelName); - - const contract = await channel.getContract(chaincodeId); - t.pass('Got the contract, ' + chaincodeId); - - await testSuccessfulQuery(t, contract); - await testQueryErrorResponse(t, contract); - await testChaincodeRuntimeError(t, contract); - } catch (err) { - t.fail('Failed to invoke transaction chaincode on channel. ' + err.stack ? err.stack : err); - } finally { - gateway.disconnect(); - } - - t.end(); -}); - -test('\n\n***** Network End-to-end flow: evaluate transaction with MSPID_SCOPE_SINGLE query handler *****\n\n', async (t: tape.Test) => { - const gateway = new Gateway(); - - try { - const wallet = await createWallet(t); - const ccp: Buffer = fs.readFileSync(fixtures + '/profiles/network.json'); - const ccpObject = JSON.parse(ccp.toString()); - - await gateway.connect(ccpObject, { - clientTlsIdentity: tlsLabel, - discovery: { - enabled: false, - }, - identity: identityLabel, - queryHandlerOptions: { - strategy: DefaultQueryHandlerStrategies.MSPID_SCOPE_SINGLE, - }, - wallet, - }); - t.pass('Connected to the gateway'); - - const channel = await gateway.getNetwork(channelName); - t.pass('Initialized the channel, ' + channelName); - - const contract = await channel.getContract(chaincodeId); - t.pass('Got the contract, ' + chaincodeId); - - await testSuccessfulQuery(t, contract); - await testQueryErrorResponse(t, contract); - await testChaincodeRuntimeError(t, contract); - } catch (err) { - t.fail('Failed to invoke transaction chaincode on channel. ' + err.stack ? err.stack : err); - } finally { - gateway.disconnect(); - } - - t.end(); -}); - -test('\n\n***** Network End-to-end flow: evaluate transaction with sample query handler *****\n\n', async (t: tape.Test) => { - const gateway = new Gateway(); - - try { - const wallet = await createWallet(t); - const ccp: Buffer = fs.readFileSync(fixtures + '/profiles/network.json'); - const ccpObject = JSON.parse(ccp.toString()); - - await gateway.connect(ccpObject, { - clientTlsIdentity: tlsLabel, - discovery: { - enabled: false, - }, - identity: identityLabel, - queryHandlerOptions: { - strategy: sampleQueryStrategy, - }, - wallet, - }); - t.pass('Connected to the gateway'); - - const channel = await gateway.getNetwork(channelName); - t.pass('Initialized the channel, ' + channelName); - - const contract = await channel.getContract(chaincodeId); - t.pass('Got the contract, ' + chaincodeId); - - await testSuccessfulQuery(t, contract); - await testQueryErrorResponse(t, contract); - await testChaincodeRuntimeError(t, contract); - } catch (err) { - t.fail('Failed to invoke transaction chaincode on channel. ' + err.stack ? err.stack : err); - } finally { - gateway.disconnect(); - } - - t.end(); -}); - -test('\n\n***** Network End-to-end flow: evaluate transaction with transient data *****\n\n', async (t: tape.Test) => { - const gateway = new Gateway(); - - try { - const wallet = await createWallet(t); - const ccp: Buffer = fs.readFileSync(fixtures + '/profiles/network.json'); - const ccpObject = JSON.parse(ccp.toString()); - - await gateway.connect(ccpObject, { - clientTlsIdentity: tlsLabel, - discovery: { - enabled: false, - }, - identity: identityLabel, - wallet, - }); - t.pass('Connected to the gateway'); - - const channel = await gateway.getNetwork(channelName); - t.pass('Initialized the channel, ' + channelName); - - const contract = await channel.getContract(chaincodeId); - t.pass('Got the contract, about to evaluate (query) transaction'); - - const transaction = contract.createTransaction('getTransient'); - const transientMap: TransientMap = { - key1: Buffer.from('value1'), - key2: Buffer.from('value2'), - }; - const response = await transaction.setTransient(transientMap).evaluate(); - - t.pass('Got response: ' + response.toString('utf8')); - const result = JSON.parse(response.toString('utf8')); - - let success = true; - - if (Object.keys(transientMap).length !== Object.keys(result).length) { - success = false; - } - - Object.entries(transientMap).forEach((entry) => { - const key = entry[0]; - const value = entry[1].toString(); - if (value !== result[key]) { - t.fail(`Expected ${key} to be ${value} but was ${result[key]}`); - success = false; - } - }); - - if (success) { - t.pass('Got expected transaction response'); - } else { - t.fail('Unexpected transaction response: ' + response); - } - } catch (err) { - t.fail('Failed to invoke transaction chaincode on channel. ' + err.stack ? err.stack : err); - } finally { - gateway.disconnect(); - } - - t.end(); -}); - -test('\n\n***** Network End-to-end flow: evaluate transaction with empty string result *****\n\n', async (t: tape.Test) => { - const gateway = new Gateway(); - - try { - const wallet = await createWallet(t); - const ccp: Buffer = fs.readFileSync(fixtures + '/profiles/network.json'); - const ccpObject = JSON.parse(ccp.toString()); - - await gateway.connect(ccpObject, { - clientTlsIdentity: tlsLabel, - discovery: { - enabled: false, - }, - identity: identityLabel, - wallet, - }); - t.pass('Connected to the gateway'); - - const channel = await gateway.getNetwork(channelName); - t.pass('Initialized the channel, ' + channelName); - - const contract = await channel.getContract(chaincodeId); - t.pass('Got the contract, about to evaluate (query) transaction'); - - const response = await contract.evaluateTransaction('echo', ''); - - if (response && response.toString('utf8') === '') { - t.pass('Got expected transaction response'); - } else { - t.fail('Unexpected transaction response: ' + response); - } - } catch (err) { - t.fail('Failed to invoke transaction chaincode on channel. ' + err.stack ? err.stack : err); - } finally { - gateway.disconnect(); - } - - t.end(); -}); diff --git a/test/typescript/tsconfig.json b/test/typescript/tsconfig.json deleted file mode 100644 index fb70decb8c..0000000000 --- a/test/typescript/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "target": "es2017", - "sourceMap": true, - "strict": true - }, - "exclude": [ - "node_modules" - ] -}