From 07c2a1839ff68be26c523399b19b6b402f4b94df Mon Sep 17 00:00:00 2001 From: "nkl199@yahoo.co.uk" Date: Thu, 31 Oct 2019 09:26:50 +0000 Subject: [PATCH] [FABN-1396] convert base endorse scenario test - convert js-cucmber endorse sceanrio test - update typedefs Change-Id: I1ae84acd4a751a3a1409ce00e60b5951f008b6a6 Signed-off-by: nkl199@yahoo.co.uk --- fabric-ca-client/types/index.d.ts | 19 +- fabric-client/types/index.d.ts | 2 - fabric-common/package.json | 1 + fabric-common/types/index.d.ts | 5 +- fabric-common/{ => types}/tsconfig.json | 2 +- package.json | 2 +- test/README.md | 1 - test/ts-scenario/config/Org1.json | 21 + test/ts-scenario/config/Org2.json | 21 + test/ts-scenario/config/ccp-tls.json | 21 + test/ts-scenario/config/ccp.json | 21 + test/ts-scenario/features/base_api.feature | 28 + test/ts-scenario/steps/base_api.ts | 43 ++ test/ts-scenario/steps/constants.ts | 18 +- test/ts-scenario/steps/lib/gateway.ts | 3 +- .../steps/lib/utility/clientUtils.ts | 501 ++++++++++++++++++ .../utility/commonConnectionProfileHelper.ts | 1 - test/ts-scenario/tsconfig.json | 2 +- 18 files changed, 682 insertions(+), 30 deletions(-) rename fabric-common/{ => types}/tsconfig.json (95%) create mode 100644 test/ts-scenario/features/base_api.feature create mode 100644 test/ts-scenario/steps/base_api.ts create mode 100644 test/ts-scenario/steps/lib/utility/clientUtils.ts diff --git a/fabric-ca-client/types/index.d.ts b/fabric-ca-client/types/index.d.ts index e69400cebf..81b318af1c 100644 --- a/fabric-ca-client/types/index.d.ts +++ b/fabric-ca-client/types/index.d.ts @@ -1,18 +1,7 @@ -/* - Copyright 2018 IBM All Rights Reserved. +/** + * SPDX-License-Identifier: Apache-2.0 + */ - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ import { ICryptoSuite, ICryptoKeyStore, IKeyValueStore, User } from "fabric-client"; import { BaseClient } from 'fabric-client/types/base'; @@ -35,7 +24,7 @@ export = FabricCAServices; declare namespace FabricCAServices { export interface TLSOptions { - trustedRoots: Buffer; + trustedRoots: Buffer | string[]; verify: boolean; } diff --git a/fabric-client/types/index.d.ts b/fabric-client/types/index.d.ts index 89cb01b357..76c204a79d 100644 --- a/fabric-client/types/index.d.ts +++ b/fabric-client/types/index.d.ts @@ -1,6 +1,4 @@ /** - * Copyright 2017 Kapil Sachdeva All Rights Reserved. - * * SPDX-License-Identifier: Apache-2.0 */ diff --git a/fabric-common/package.json b/fabric-common/package.json index 8b7334ff3d..14c0fe9481 100644 --- a/fabric-common/package.json +++ b/fabric-common/package.json @@ -24,6 +24,7 @@ "node": "^8.9.0 || ^10.15.3", "npm": "^5.5.1 || ^6.4.1" }, + "types": "./types/index.d.ts", "dependencies": { "elliptic": "^6.2.3", "js-sha3": "^0.7.0", diff --git a/fabric-common/types/index.d.ts b/fabric-common/types/index.d.ts index 2c704b8a91..0b7ee94dba 100644 --- a/fabric-common/types/index.d.ts +++ b/fabric-common/types/index.d.ts @@ -1,6 +1,4 @@ /** - * Copyright 2017 Kapil Sachdeva All Rights Reserved. - * * SPDX-License-Identifier: Apache-2.0 */ @@ -57,7 +55,7 @@ export interface UserConfig { export interface ProposalResponse { errors: Error[], responses: EndorsementResponse[], - queryResponses: Buffer[] + queryResults: Buffer[] } export interface EndorsementResponse { @@ -83,6 +81,7 @@ export class User { public setRoles(roles: string[]): void; public getAffiliation(): string; public setAffiliation(affiliation: string): void; + public getEnrollmentSecret() : string; public getIdentity(): IIdentity; public getSigningIdentity(): ISigningIdentity; public setSigningIdentity(signingIdentity: ISigningIdentity): void; diff --git a/fabric-common/tsconfig.json b/fabric-common/types/tsconfig.json similarity index 95% rename from fabric-common/tsconfig.json rename to fabric-common/types/tsconfig.json index 7d0c38d823..5c219fba32 100644 --- a/fabric-common/tsconfig.json +++ b/fabric-common/types/tsconfig.json @@ -20,7 +20,7 @@ } }, "files": [ - "types/index.d.ts" + "index.d.ts" ], "formatCodeOptions": { "indentSize": 4, diff --git a/package.json b/package.json index 362f145a18..0800c6155a 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "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 @discovery", + "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 @base_api", "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/**'", diff --git a/test/README.md b/test/README.md index 438bb5b710..c5ab5d08cc 100644 --- a/test/README.md +++ b/test/README.md @@ -35,7 +35,6 @@ test - `fixtures` holds all the configuration files used by the integration and scenario tests - `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/ts-scenario/config/Org1.json b/test/ts-scenario/config/Org1.json index 2720c28290..75e6544962 100644 --- a/test/ts-scenario/config/Org1.json +++ b/test/ts-scenario/config/Org1.json @@ -13,6 +13,27 @@ } }, "channels":{ + "baseapichannel":{ + "orderers":[ + "orderer.example.com" + ], + "peers":{ + "peer0.org1.example.com":{ + "endorsingPeer":true, + "chaincodeQuery":true, + "ledgerQuery":true, + "eventSource":true + }, + "peer0.org2.example.com":{ + "endorsingPeer":true, + "chaincodeQuery":false, + "ledgerQuery":true, + "eventSource":false + } + }, + "chaincodes":[ + ] + }, "channelopschannel":{ "orderers":[ "orderer.example.com" diff --git a/test/ts-scenario/config/Org2.json b/test/ts-scenario/config/Org2.json index d71b451a86..70fea1de9f 100644 --- a/test/ts-scenario/config/Org2.json +++ b/test/ts-scenario/config/Org2.json @@ -13,6 +13,27 @@ } }, "channels":{ + "baseapichannel":{ + "orderers":[ + "orderer.example.com" + ], + "peers":{ + "peer0.org1.example.com":{ + "endorsingPeer":true, + "chaincodeQuery":true, + "ledgerQuery":true, + "eventSource":true + }, + "peer0.org2.example.com":{ + "endorsingPeer":true, + "chaincodeQuery":false, + "ledgerQuery":true, + "eventSource":false + } + }, + "chaincodes":[ + ] + }, "channelopschannel":{ "orderers":[ "orderer.example.com" diff --git a/test/ts-scenario/config/ccp-tls.json b/test/ts-scenario/config/ccp-tls.json index 2925fb1858..0fc25de6a6 100644 --- a/test/ts-scenario/config/ccp-tls.json +++ b/test/ts-scenario/config/ccp-tls.json @@ -13,6 +13,27 @@ } }, "channels":{ + "baseapichannel":{ + "orderers":[ + "orderer.example.com" + ], + "peers":{ + "peer0.org1.example.com":{ + "endorsingPeer":true, + "chaincodeQuery":true, + "ledgerQuery":true, + "eventSource":true + }, + "peer0.org2.example.com":{ + "endorsingPeer":true, + "chaincodeQuery":false, + "ledgerQuery":true, + "eventSource":false + } + }, + "chaincodes":[ + ] + }, "channelopschannel":{ "orderers":[ "orderer.example.com" diff --git a/test/ts-scenario/config/ccp.json b/test/ts-scenario/config/ccp.json index 49b4a8602b..2fa2e7a2f3 100644 --- a/test/ts-scenario/config/ccp.json +++ b/test/ts-scenario/config/ccp.json @@ -13,6 +13,27 @@ } }, "channels":{ + "baseapichannel": { + "orderers":[ + "orderer.example.com" + ], + "peers":{ + "peer0.org1.example.com":{ + "endorsingPeer":true, + "chaincodeQuery":true, + "ledgerQuery":true, + "eventSource":true + }, + "peer0.org2.example.com":{ + "endorsingPeer":true, + "chaincodeQuery":false, + "ledgerQuery":true, + "eventSource":false + } + }, + "chaincodes":[ + ] + }, "channelopschannel":{ "orderers":[ "orderer.example.com" diff --git a/test/ts-scenario/features/base_api.feature b/test/ts-scenario/features/base_api.feature new file mode 100644 index 0000000000..161be860b6 --- /dev/null +++ b/test/ts-scenario/features/base_api.feature @@ -0,0 +1,28 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# + +@base_api +Feature: Use base API to perform core operations + +Background: + Given I place a scenario start message BASE API FEATURE + Given I deploy a tls Fabric network + And I use the cli to create and join the channel named baseapichannel 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 baseapichannel with endorsement policy 1of and arguments ["initLedger"] + +Scenario: Using only fabric-base I can propose, endorse and commit a transaction on instantiated node chaincode + Given I have created a client named leon based on information in profile ccp-tls under organization Org1 + And I have used the client named leon to create a channel object for the channel named baseapichannel + When I build a new endorsement request named myFirstRequest for smart contract named fabcar with arguments [createCar, 2000, GMC, Savana, grey, Jones] as client leon on channel baseapichannel + And I commit the endorsement request named myFirstRequest as client leon on channel baseapichannel + Then the request named myFirstRequest for client leon has a general result matching "{\"result\":\"SUCCESS\"}" + And the request named myFirstRequest for client leon has a event result matching "{\"result\":\"Commit success\"}" + And the request named myFirstRequest for client leon has a commit result matching "{\"status\":\"SUCCESS\"}" + +Scenario: Using only fabric-base I can send a query request to peers and recieve a valid result + Given I have created a client named leon based on information in profile ccp-tls under organization Org1 + And I have used the client named leon to create a channel object for the channel named baseapichannel + When I submit a query named myFirstQuery with args [queryCar,CAR0] for contract fabcar as client leon on channel baseapichannel + Then the query named myFirstQuery for client leon has a general result matching "{\"result\":\"SUCCESS\"}" + And the query named myFirstQuery for client leon has a peer0 result matching "{\"color\":\"blue\",\"docType\":\"car\",\"make\":\"Toyota\",\"model\":\"Prius\",\"owner\":\"Tomoko\"}" diff --git a/test/ts-scenario/steps/base_api.ts b/test/ts-scenario/steps/base_api.ts new file mode 100644 index 0000000000..5aaf10e144 --- /dev/null +++ b/test/ts-scenario/steps/base_api.ts @@ -0,0 +1,43 @@ +/** + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict'; + +import * as BaseUtils from './lib/utility/baseUtils'; +import * as ClientHelper from './lib/utility/clientUtils'; +import { Constants } from './constants'; +import { CommonConnectionProfileHelper } from './lib/utility/commonConnectionProfileHelper'; + +import * as path from 'path'; +import { Given, When, Then } from 'cucumber'; + +Given(/^I have created a client named (.+?) based on information in profile (.+?) under organization (.+?)$/, { timeout: Constants.HUGE_TIME as number }, async (clientName: string, ccpName: string, userOrg: string) => { + + // Get a CCP Helper + const profilePath: string = path.join(__dirname, '../config', ccpName); + const ccp: CommonConnectionProfileHelper = new CommonConnectionProfileHelper(profilePath, true); + + // Create the user + await ClientHelper.createAdminClient(clientName, ccp, userOrg); +}); + +Given(/^I have used the client named (.+?) to create a channel object for the channel named (.+?)$/, { timeout: Constants.HUGE_TIME as number }, async (clientName: string, channelName: string) => { + await ClientHelper.createChannelWithClient(clientName, channelName); +}); + +When(/^I build a new endorsement request named (.+?) for smart contract named (.+?) with arguments (.+?) as client (.+?) on channel (.+?)$/, { timeout: Constants.HUGE_TIME as number }, async (requestName: string, contractName: string, requestArgs: string, clientName: string, channelName: string) => { + await ClientHelper.buildChannelRequest(requestName, contractName, requestArgs, clientName, channelName); +}); + +When(/^I commit the endorsement request named (.+?) as client (.+?) on channel (.+?)$/, { timeout: Constants.HUGE_TIME as number }, async (requestName: string, clientName: string, channelName: string) => { + await ClientHelper.commitChannelRequest(requestName, clientName, channelName); +}); + +When(/^I submit a query named (.+?) with args (.+?) for contract (.+?) as client (.+?) on channel (.+?)$/, { timeout: Constants.HUGE_TIME as number }, async (queryName: string, queryArgs: string, contractName: string, clientName: string, channelName: string) => { + await ClientHelper.submitChannelRequest(clientName, channelName, contractName, queryArgs, queryName); +}); + +Then(/^the (request|query) named (.+?) for client (.+?) has a (.+?) result matching (.+?)$/, { timeout: Constants.HUGE_TIME as number }, (responseType: string, requestName: string, clientName: string, fieldName: string, expectedResult: string) => { + ClientHelper.validateChannelRequestResponse(clientName, responseType === 'request', requestName, fieldName, expectedResult); +}); diff --git a/test/ts-scenario/steps/constants.ts b/test/ts-scenario/steps/constants.ts index 66ae819868..8953499240 100644 --- a/test/ts-scenario/steps/constants.ts +++ b/test/ts-scenario/steps/constants.ts @@ -4,6 +4,9 @@ 'use strict'; +// State store items (just in case you don't want to look at the code) +// - Users Map + export enum Constants { // Timeouts and steps HUGE_TIME = 1200000, @@ -52,11 +55,12 @@ export enum Constants { CLI_VERBOSITY = 'false', CLI_TIMEOUT = '120s', - // Constants for network model actions - WALLET = 'WALLET', // StateStore key to retrieve a wallet that contains users - GATEWAYS = 'GATEWAYS', // StateStore key to retrieve a Map(gatewayName, Gateway) of gateways that may be re-used + // State store items + WALLET = 'WALLET', // StateStore key to retrieve a wallet that contains users + GATEWAYS = 'GATEWAYS', // StateStore key to retrieve a Map of gateways that may be re-used LISTENERS = 'LISTENERS', TRANSACTIONS = 'TRANSACTIONS', + CLIENTS = 'CLIENTS', // Map } > // Wallet types MEMORY_WALLET = 'memory', @@ -88,6 +92,12 @@ export enum Constants { // CLI Policies ENDORSEMENT_POLICY_1OF_ANY = '"OR (\'Org1MSP.member\',\'Org2MSP.member\')"', - ENDORSEMENT_POLICY_2OF_ANY = '"AND (\'Org1MSP.member\',\'Org2MSP.member\')"' + ENDORSEMENT_POLICY_2OF_ANY = '"AND (\'Org1MSP.member\',\'Org2MSP.member\')"', + + // Admin name + ADMIN_NAME = 'admin', + ADMIN_PW = 'adminpw', + // Default Naming + EVENT_HUB_DEFAULT_NAME = 'myHub', } diff --git a/test/ts-scenario/steps/lib/gateway.ts b/test/ts-scenario/steps/lib/gateway.ts index d3b0249eb9..29830b86fc 100644 --- a/test/ts-scenario/steps/lib/gateway.ts +++ b/test/ts-scenario/steps/lib/gateway.ts @@ -474,7 +474,8 @@ export function getLastTransactionResult(gatewayName: string): any { /** * Compare the last gateway transaction response with a passed value * @param {String} gatewayName the gateway to get the response from - * @param {*} msg the message to compare against + * @param {String} msg the message to compare against + * @param {boolean} exactMatch boolean flag to indicate if an exact match is being performed */ export function lastTransactionResponseCompare(gatewayName: string, msg: string, exactMatch: boolean): boolean { const gatewayObj: any = getGatewayObject(gatewayName); diff --git a/test/ts-scenario/steps/lib/utility/clientUtils.ts b/test/ts-scenario/steps/lib/utility/clientUtils.ts new file mode 100644 index 0000000000..a9f450b40d --- /dev/null +++ b/test/ts-scenario/steps/lib/utility/clientUtils.ts @@ -0,0 +1,501 @@ +/** + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict'; + +import * as BaseUtils from './baseUtils'; +import { Constants } from '../../constants'; +import { CommonConnectionProfileHelper } from './commonConnectionProfileHelper'; +import { StateStore } from './stateStore'; + +import * as fs from 'fs'; +import * as FabricCAServices from 'fabric-ca-client'; +import { Client, User, Channel, Endorser, Committer, Eventer, IdentityContext, Endpoint, Endorsement, ProposalResponse, EventService, Commit, Query } from 'fabric-common'; +import { stringify } from 'querystring'; + +const stateStore: StateStore = StateStore.getInstance(); + +export async function createAdminClient(clientName: string, ccp: CommonConnectionProfileHelper, clientOrg: string): Promise { + + // check if the client already exists + const clientMap: Map | undefined = stateStore.get(Constants.CLIENTS); + + if (clientMap && clientMap.has(clientName)) { + BaseUtils.logMsg(`Client named ${clientName} already exists`, undefined); + } else { + BaseUtils.logMsg(`Creating client named ${clientName} for organization ${clientOrg}`, undefined); + + // Get a user + const user: User = createAdminUserForOrg(ccp, clientOrg); + + // Form client with user + const client: Client = Client.newClient(clientName); + const enrollResponse: FabricCAServices.IEnrollResponse = await getTlsEnrollmentResponseForOrgUser(user, clientOrg, ccp); + client.setTlsClientCertAndKey(enrollResponse.certificate, enrollResponse.key.toBytes()); + + // persist client in state store for use later + if (clientMap) { + clientMap.set(clientName, { client, user, ccp, clientOrg }); + } else { + const map: Map = new Map(); + map.set(clientName, { client, user, ccp, clientOrg }); + stateStore.set(Constants.CLIENTS, map); + } + BaseUtils.logMsg(`Created client named ${clientName} and persisted in state store`, undefined); + } +} + +export function createChannelWithClient(clientName: string, channelName: string): void { + const clientObject: any = retrieveClientObject(clientName); + // Does this client have a channel associated? + if (clientObject.channels && clientObject.channels.has(channelName)) { + BaseUtils.logMsg(`Client channel named ${channelName} already exists`, undefined); + } else { + // Build channel and append to client channels map + const channel: Channel = (clientObject.client as Client).newChannel(channelName); + + if (clientObject.channels) { + clientObject.channels.set(channelName, channel); + } else { + const channelMap: Map = new Map(); + channelMap.set(channelName, channel); + clientObject.channels = channelMap; + } + BaseUtils.logMsg(`Channel ${channelName} has been persisted with client ${clientName}`, undefined); + } +} + +export function retrieveClientObject(clientName: string): any { + const clientMap: Map | undefined = stateStore.get(Constants.CLIENTS); + if (clientMap && clientMap.has(clientName)) { + return clientMap.get(clientName); + } else { + const msg: string = `Required client named ${clientName} does not exist`; + BaseUtils.logMsg(msg, undefined); + throw new Error(msg); + } +} + +export async function buildChannelRequest(requestName: string, contractName: string, requestArgs: any, clientName: string, channelName: string): Promise { + + // Best have a client object ready and waiting + const clientObject: any = retrieveClientObject(clientName); + const client: Client = clientObject.client; + const ccp: CommonConnectionProfileHelper = clientObject.ccp; + const channel: Channel = clientObject.channels.get(channelName); + + // We have arguments + const argArray: string[] = requestArgs.slice(1, -1).split(','); + + // The peers and orderers will be built by the client so that common + // connection information will come from the client object, like the mutual TLS cert/key + const targets: Endorser[] = []; + + try { + // New object for NodeSDK-Base, "IdentityContext", this object + // combines the old "TransactionID" and the Clients "UserContext" + // into a single transaction based object needed for giving the + // endorsement transaction an unique transaction ID, and nonce, + // and also the user identity required when building the outbound + // request for the fabric network + // The user identity used must also be a signing identity unless + // the signing is being done external to the NodeSDK-Base. + const idx: IdentityContext = client.newIdentityContext(clientObject.user); + + // unique connection information will be used to build an endpoint + // used on connect() + const peerObjects: any = ccp.getPeersForChannel(channelName); + const endpoints: Endpoint[] = []; + + for (const peerName of Object.keys(peerObjects)) { + const peerObject: any = ccp.getPeer(peerName); + const endpoint: Endpoint = client.newEndpoint({ + 'url': peerObject.url, + 'pem': fs.readFileSync(peerObject.tlsCACerts.path).toString(), + 'ssl-target-name-override': peerObject.grpcOptions['ssl-target-name-override'], + }); + + endpoints.push(endpoint); + + const peer: Endorser = client.newEndorser(peerObject.grpcOptions['ssl-target-name-override']); + await peer.connect(endpoint, {}); + targets.push(peer); + + const connectionOk: boolean = await peer.checkConnection(); + if (connectionOk) { + BaseUtils.logMsg('Peer checkConnection test successfully', undefined); + } else { + BaseUtils.logAndThrow('Peer checkConnection test failed'); + } + } + + // This is a new object to NodeSDK-Base. This "Proposal" object will + // centralize the endorsement operation, including the endorsement results. + // Proposals must be built from channel and chaincode name + const endorsement: Endorsement = channel.newEndorsement(contractName); + + // ----- E N D O R S E ----- + // endorsement will have the values needed by the chaincode + // to perform the endorsement (invoke) + const endorsementRequest: any = { + args: [...argArray] + }; + + // The endorsement object has the building of the request, the signing + // and the sending steps broken out into separate API's that must + // be called individually. + endorsement.build(idx, endorsementRequest); + endorsement.sign(idx); + + // We now have an endorsement, save to the ever expanding clientObject + const request: any = { endorsement, targets, endpoints, idx }; + if (clientObject.requests) { + clientObject.requests.set(requestName, request); + } else { + const map: Map = new Map(); + map.set(requestName, request); + clientObject.requests = map; + } + } catch (error) { + BaseUtils.logMsg(`failure in buildChannelRequest: ${error.toString()}`, {}); + for (const target of targets) { + target.disconnect(); + } + throw error; + } +} + +export async function commitChannelRequest(requestName: string, clientName: string, channelName: string): Promise { + // Requires that the endorsement is already built and signed (performed by buildChannelRequest()) + const clientObject: any = retrieveClientObject(clientName); + const client: Client = clientObject.client; + + if (clientObject.requests && clientObject.requests.has(requestName) && clientObject.channels.has(channelName)) { + const channel: Channel = clientObject.channels.get(channelName); + const requestObject: any = clientObject.requests.get(requestName); + const ccp: CommonConnectionProfileHelper = clientObject.ccp; + const endorsement: Endorsement = requestObject.endorsement; + const targetPeers: Endorser[] = requestObject.targets; + const endpoints: Endpoint[] = requestObject.endpoints; + const eventer: Eventer = client.newEventer('peer1-events'); + const orderer: Committer = client.newCommitter('orderer'); + + try { + // Build an endorsement request + const endorsementRequest: any = { + targets: targetPeers, + requestTimeout: Constants.INC_LONG + }; + + // Send the signed endorsement to the requested peers. + const endorsementResponse: ProposalResponse = await endorsement.send(endorsementRequest); + if (endorsementResponse.errors) { + for (const error of endorsementResponse.errors) { + BaseUtils.logMsg(`Failed to get endorsement : ${error.message}`, undefined); + } + throw Error('failed endorsement'); + } + + // Connect to 'Eventer' + try { + await eventer.connect(endpoints[0], {}); + if (await eventer.checkConnection()) { + BaseUtils.logMsg('Eventer checkConnection test successfully', undefined); + } else { + BaseUtils.logAndThrow('Eventer checkConnection test failed'); + } + } catch (error) { + BaseUtils.logError(`Failed to connect to channel event hub ${eventer.toString()}`, undefined); + BaseUtils.logError(`Failed to connect ${error.stack}`, undefined); + throw error; + } + + // The event service is channel based + const eventService: EventService = channel.newEventService(Constants.EVENT_HUB_DEFAULT_NAME); + eventService.build(requestObject.idx, {}); + eventService.sign(requestObject.idx); + const eventRequest: any = { + targets: [eventer], + requestTimeout: Constants.INC_MED + }; + await eventService.send(eventRequest); + + const eventListener: any = new Promise((resolve: any, reject: any): any => { + const handle: NodeJS.Timeout = setTimeout(() => { + eventer.disconnect(); + // may want to unregister the tx event listener + reject(new Error('Test application has timed out waiting for tx event')); + }, Constants.INC_LONG); + + eventService.registerTransactionListener( + endorsement.getTransactionId(), + (error: any, event: any): any => { + clearTimeout(handle); + if (error) { + BaseUtils.logError(`Failed to receive transaction event for ${endorsement.getTransactionId()}`, {}); + reject(error); + } + BaseUtils.logMsg(`Successfully received the transaction event for ${event.transactionId} with status of ${event.status} in block number ${event.blockNumber}`, {}); + resolve('Commit success'); + }, + {} + ); + }); + + // ----- C O M M I T S T A G E ----- + // create an endpoint with all the connection information + const ordererName: string = ccp.getOrderersForChannel(channelName)[0]; + const ordererObject: any = ccp.getOrderer(ordererName); + const orderEndpoint: Endpoint = client.newEndpoint({ + 'url': ordererObject.url, + 'pem': fs.readFileSync(ordererObject.tlsCACerts.path).toString(), + 'ssl-target-name-override': ordererObject.grpcOptions['ssl-target-name-override'], + }); + + // Connect to orderer + await orderer.connect(orderEndpoint, {}); + if (await orderer.checkConnection()) { + BaseUtils.logMsg('Orderer checkConnection test successfully', {}); + } else { + BaseUtils.logAndThrow('Orderer checkConnection test failed'); + } + + // Create, build, sign the commit + // - The build returns the bytes that may be signed externally + // -When signing internally the idx must have a user with a signing identity. + const commit: Commit = endorsement.newCommit(); + commit.build(requestObject.idx, {}); + commit.sign(requestObject.idx); + + const commitRequest: any = { + targets: [orderer], // could also use the orderer names + requestTimeout: 3000 + }; + + try { + // Send commit, having started the event listener, wait for all + const commitSubmission: any = commit.send(commitRequest); + const commitResults: any[] = await Promise.all([eventListener, commitSubmission]); + + requestObject.results = { + general: JSON.stringify({ + result: 'SUCCESS' + }), + commit: JSON.stringify({ + status: commitResults[1].status + }), + event: JSON.stringify({ + result: commitResults[0] + }) + }; + } catch (error) { + // Swallow error as we might be testing a failure path, but modify request object with error msg and status + requestObject.results = { + general: JSON.stringify({ + result: 'FAILURE', + msg: error.toString(), + }) + }; + } + } catch (error) { + BaseUtils.logError('sendChannelRequest failed with error', error.stack); + throw error; + } finally { + for (const target of targetPeers) { + target.disconnect(); + } + orderer.disconnect(); + eventer.disconnect(); + BaseUtils.logMsg(`Disconnected all endpoints for client object ${clientName} and request ${requestName}`, undefined); + } + } else { + BaseUtils.logAndThrow(`Request named ${requestName} does not exits on client object for client ${clientName}`); + } +} + +export async function submitChannelRequest(clientName: string, channelName: string, contractName: string, contractArgs: string, queryName: string): Promise { + // Requires that the endorsement is already built and signed (performed by buildChannelRequest()) + const clientObject: any = retrieveClientObject(clientName); + const client: Client = clientObject.client; + const ccp: CommonConnectionProfileHelper = clientObject.ccp; + const clientOrg: string = clientObject.clientOrg; + + // need a queries object + if (!clientObject.queries) { + clientObject.queries = new Map(); + } + + // Split args into an array to feed into the buildQueryRequest + const argArray: string[] = contractArgs.slice(1, -1).split(','); + + if (clientObject.channels.has(channelName)) { + const channel: Channel = clientObject.channels.get(channelName); + const idx: IdentityContext = client.newIdentityContext(clientObject.user); + + const peerNames: string[] = ccp.getPeersForOrganization(clientOrg); + const targets: Endorser[] = []; + + for (const peerName of peerNames) { + const peerObject: any = ccp.getPeer(peerName); + const endpoint: Endpoint = client.newEndpoint({ + 'url': peerObject.url, + 'pem': fs.readFileSync(peerObject.tlsCACerts.path).toString(), + 'ssl-target-name-override': peerObject.grpcOptions['ssl-target-name-override'], + }); + + const peer: Endorser = client.newEndorser(peerObject.grpcOptions['ssl-target-name-override']); + await peer.connect(endpoint, {}); + targets.push(peer); + + const connectionOk: boolean = await peer.checkConnection(); + if (connectionOk) { + BaseUtils.logMsg('Peer checkConnection test successfully', undefined); + } else { + BaseUtils.logAndThrow('Peer checkConnection test failed'); + } + } + + try { + // New query with passed contract name + const query: Query = channel.newQuery(contractName); + + // Build a query request + const buildQueryRequest: any = { + args: [...argArray] + }; + + // Build and sign the query + query.build(idx, buildQueryRequest); + query.sign(idx); + + // Construct query request + const queryRequest: any = { + targets, + requestTimeout: Constants.INC_LONG + }; + + // Send query to target peers + const queryObject: any = {}; + try { + const queryResponse: ProposalResponse = await query.send(queryRequest); + + if (queryResponse.errors) { + // failure + BaseUtils.logMsg(`Query failure detected`, undefined); + queryObject.results = { + general: JSON.stringify({ + result: 'FAILURE' + }) + }; + let inc: number = 0; + for (const error of queryResponse.errors) { + queryObject.results[`peer${inc}`] = error.toString(); + inc++; + } + } else { + // Success + BaseUtils.logMsg(`Query success detected`, undefined); + queryObject.results = { + general: JSON.stringify({ + result: 'SUCCESS' + }) + }; + let inc: number = 0; + for (const result of queryResponse.queryResults) { + queryObject.results[`peer${inc}`] = JSON.parse(result.toString()); + inc++; + } + } + } catch (error) { + // Swallow error as we might be testing a failure path, but modify request object with error msg and status + queryObject.results = { + general: JSON.stringify({ + result: 'FAILURE', + msg: error.toString(), + }) + }; + } + clientObject.queries.set(queryName, queryObject); + } catch (error) { + BaseUtils.logError('query submission failed with error: ', error.stack); + throw error; + } finally { + for (const target of targets) { + target.disconnect(); + } + } + } else { + BaseUtils.logAndThrow(`Channel named ${channelName} does not exits on client object for client ${clientName}`); + } +} + +function createAdminUserForOrg(ccp: CommonConnectionProfileHelper, orgName: string): User { + + const org: any = ccp.getOrganization(orgName); + if (!org) { + throw new Error(`Could not find organization ${orgName} in configuration`); + } + + const keyPEM: Buffer = fs.readFileSync(org.adminPrivateKeyPEM.path); + const certPEM: Buffer = fs.readFileSync(org.signedCertPEM.path); + + // Create user and return + return User.createUser(Constants.ADMIN_NAME, Constants.ADMIN_PW, org.mspid, certPEM.toString(), keyPEM.toString()); +} + +async function getTlsEnrollmentResponseForOrgUser(user: User, orgName: string, ccp: CommonConnectionProfileHelper): Promise { + + const caName: string = ccp.getCertificateAuthoritiesForOrg(orgName)[0]; + const ca: any = ccp.getCertificateAuthority(caName); + const fabricCAPem: string = fs.readFileSync(ca.tlsCACerts.path).toString(); + const fabricCAEndpoint: string = ca.url; + + const tlsOptions: FabricCAServices.TLSOptions = { + trustedRoots: [fabricCAPem], + verify: false + }; + + const caService: FabricCAServices = new FabricCAServices(fabricCAEndpoint, tlsOptions, caName, user.getCryptoSuite()); + + const request: FabricCAServices.IEnrollmentRequest = { + enrollmentID: user.getName(), + enrollmentSecret: user.getEnrollmentSecret(), + profile: 'tls' + }; + + return await caService.enroll(request); +} + +export function validateChannelRequestResponse(clientName: string, isRequest: boolean, requestName: string, fieldName: string, expectedResult: string): boolean | void { + const clientObject: any = retrieveClientObject(clientName); + + let results: any; + if (isRequest) { + if (clientObject.requests && clientObject.requests.has(requestName)) { + const requestObject: any = clientObject.requests.get(requestName); + results = requestObject.results; + } else { + BaseUtils.logAndThrow(`Request named ${requestName} does not exits on client object for client ${clientName} for validation`); + } + } else { + if (clientObject.queries && clientObject.queries.has(requestName)) { + const queryObject: any = clientObject.queries.get(requestName); + results = queryObject.results; + } else { + BaseUtils.logAndThrow(`Query named ${requestName} does not exits on client object for client ${clientName} for validation`); + } + } + + if (results) { + const actualResult: string = results[fieldName]; + const isMatch: boolean = (actualResult.localeCompare(JSON.parse(expectedResult)) === 0); + if (isMatch) { + BaseUtils.logMsg(`Validated response ${requestName} of type ${fieldName}`, {}); + } else { + BaseUtils.logAndThrow(`Unexpected response for ${requestName} and type ${fieldName}. Expected ${expectedResult} but had ${actualResult}`); + } + } else { + BaseUtils.logAndThrow(`Response for ${requestName} does not have a results object for validation`); + } +} diff --git a/test/ts-scenario/steps/lib/utility/commonConnectionProfileHelper.ts b/test/ts-scenario/steps/lib/utility/commonConnectionProfileHelper.ts index 8577bd5549..76fc9c11f3 100644 --- a/test/ts-scenario/steps/lib/utility/commonConnectionProfileHelper.ts +++ b/test/ts-scenario/steps/lib/utility/commonConnectionProfileHelper.ts @@ -5,7 +5,6 @@ 'use strict'; import * as path from 'path'; -import { strict } from 'assert'; /** * CommonConnectionProfileHelper diff --git a/test/ts-scenario/tsconfig.json b/test/ts-scenario/tsconfig.json index 809438f500..05730fe831 100644 --- a/test/ts-scenario/tsconfig.json +++ b/test/ts-scenario/tsconfig.json @@ -8,5 +8,5 @@ }, "exclude": [ "node_modules" - ] + ], }