import { FabricConnection } from '../src/FabricConnection' ; import * as chai from 'chai' ; import * as chaiAsPromised from 'chai-as-promised' ; import * as sinon from 'sinon' ; import * as sinonChai from 'sinon-chai' ; import * as fabricClientCA from 'fabric-ca-client' ; import { Gateway , Wallet } from 'fabric-network' ; import { OutputAdapter , LogType } from 'ibm-blockchain-platform-common' ; import { FabricWallet } from 'ibm-blockchain-platform-wallet' ; import * as path from 'path' ; import { Client , Channel } from 'fabric-common' ; import { LifecyclePeer , LifecycleChannel , Lifecycle } from 'ibm-blockchain-platform-fabric-admin' ; chai . should ( ) ; chai . use ( sinonChai ) ; chai . use ( chaiAsPromised ) ; describe ( 'FabricConnection' , ( ) => { class TestOutputAdapter extends OutputAdapter { public static instance ( ) { return TestOutputAdapter . _instance ; } private static _instance : TestOutputAdapter = new TestOutputAdapter ( ) ; private constructor ( ) { super ( ) ; } public log ( type , popupMessage , outputMessage ? , stackTrace ? ) { super . log ( type , popupMessage , outputMessage , stackTrace ) ; } } class TestFabricConnection extends FabricConnection { private connectionProfile : any ; constructor ( connectionProfilePath , connectionProfile , outputAdapter ) { super ( connectionProfilePath , outputAdapter ) ; this . connectionProfile = connectionProfile ; } async connect ( wallet , identityName , requestTimeout ) { this [ 'gateway' ] = fabricGatewayStub as unknown as Gateway ; await this . connectInner ( this . connectionProfile , wallet . getWallet ( ) , identityName , requestTimeout ) ; } } let mySandBox ; let fabricClientStub ; let fabricGatewayStub ; let fabricConnection ; let fabricChannelStub ; let fabricCAStub ; let mockWallet ; const mockIdentityName = 'admin' ; let fabricWallet ; const timeout = 120 ; beforeEach ( async ( ) => { mySandBox = sinon . createSandbox ( ) ; const connectionProfile = { peers : { 'peer0.org1.example.com' : { url : 'grpc://localhost:7051' } } , orderers : { 'orderer.example.com' : { url : 'grpc://localhost:7050' } } , channels : { 'channel-from-the-ccp-no-peers' : { } , 'channel-from-the-ccp' : { peers : { 'peer0.org1.example.com' : { } } } } , certificateAuthorities : { 'ca.org1.example.com' : { url : 'https://localhost:7054' } } } ; mockWallet = mySandBox . createStubInstance ( Wallet ) ; mockWallet . list . resolves ( [ { label : 'admin' } ] ) ; fabricWallet = await FabricWallet . newFabricWallet ( path . join ( __dirname , 'tmp' , 'wallet' ) ) ; mySandBox . stub ( fabricWallet , 'getWallet' ) . returns ( mockWallet ) ; fabricConnection = new TestFabricConnection ( '/tmp/somepath.json' , connectionProfile , null ) ; fabricClientStub = mySandBox . createStubInstance ( Client ) ; fabricClientStub . getEndorsers . returns ( [ { name : 'peer0.org1.example.com' , mspid : 'Org1MSP' , endpoint : { url : 'grpc://localhost:7051' , options : { 'grpc.default_authority' : 'batman.ibm.com' , 'somewhere_over_the_rainbow' : 'wat' } } } , { name : 'peer0.org2.example.com' , mspid : 'Org2MSP' , endpoint : { url : 'grpc://localhost:8051' , options : { } } } ] ) ; fabricGatewayStub = mySandBox . createStubInstance ( Gateway ) ; fabricCAStub = mySandBox . createStubInstance ( fabricClientCA ) ; fabricCAStub . enroll . returns ( { certificate : 'myCert' , key : { toBytes : mySandBox . stub ( ) . returns ( 'myKey' ) } } ) ; fabricCAStub . register . resolves ( 'its a secret' ) ; fabricGatewayStub [ 'client' ] = fabricClientStub ; fabricGatewayStub . connect . resolves ( ) ; fabricGatewayStub . getOptions . returns ( { wallet : mockWallet , identity : mockIdentityName } ) ; fabricChannelStub = mySandBox . createStubInstance ( Channel ) ; const fabricNetworkStub = { getChannel : mySandBox . stub ( ) . returns ( fabricChannelStub ) } ; fabricGatewayStub . getNetwork . returns ( fabricNetworkStub ) ; fabricGatewayStub . disconnect . returns ( null ) ; fabricConnection [ 'gateway' ] = fabricGatewayStub as unknown as Gateway ; fabricConnection [ 'outputAdapter' ] = TestOutputAdapter . instance ( ) ; await fabricConnection . connect ( fabricWallet , mockIdentityName , 120 ) ; } ) ; afterEach ( ( ) => { mySandBox . restore ( ) ; } ) ; describe ( 'constructor' , ( ) => { it ( 'should set output adapter' , async ( ) => { const adapter = TestOutputAdapter . instance ( ) ; const connectionProfile = { orderers : { 'orderer.example.com' : { url : 'grpc://localhost:7050' } } } ; const fabConnection = new TestFabricConnection ( '/tmp/somepath.json' , connectionProfile , adapter ) ; fabConnection [ 'outputAdapter' ] . should . deep . equal ( adapter ) ; } ) ; } ) ; describe ( 'connect' , ( ) => { it ( 'should use discovery as localhost for localhost orderer connections' , async ( ) => { const connectionProfile = { orderers : { 'orderer.example.com' : { url : 'grpc://localhost:7050' } } } ; fabricConnection = new TestFabricConnection ( '/tmp/somepath.json' , connectionProfile , null ) ; await fabricConnection . connect ( fabricWallet , mockIdentityName , timeout ) ; fabricGatewayStub . connect . should . have . been . calledWith ( connectionProfile , { wallet : mockWallet , identity : mockIdentityName , discovery : { asLocalhost : true , enabled : true } , eventHandlerOptions : { commitTimeout : timeout } , queryHandlerOptions : { strategy : sinon . match . any } } ) ; } ) ; it ( 'should not use discovery as localhost for remote orderer connections' , async ( ) => { const connectionProfile = { orderers : { 'orderer.example.com' : { url : 'grpc://192.168.1.1:7050' } } } ; fabricConnection = new TestFabricConnection ( '/tmp/somepath.json' , connectionProfile , null ) ; await fabricConnection . connect ( fabricWallet , mockIdentityName , timeout ) ; fabricGatewayStub . connect . should . have . been . calledWith ( connectionProfile , { wallet : mockWallet , identity : mockIdentityName , discovery : { asLocalhost : false , enabled : true } , eventHandlerOptions : { commitTimeout : timeout } , queryHandlerOptions : { strategy : sinon . match . any } } ) ; } ) ; it ( 'should not use discovery as localhost for dodgy orderer connections' , async ( ) => { const connectionProfile = { orderers : { 'orderer.example.com' : { missingUrl : 'grpc://192.168.1.1:7050' } } } ; fabricConnection = new TestFabricConnection ( '/tmp/somepath.json' , connectionProfile , null ) ; await fabricConnection . connect ( fabricWallet , mockIdentityName , timeout ) ; fabricGatewayStub . connect . should . have . been . calledWith ( connectionProfile , { wallet : mockWallet , identity : mockIdentityName , discovery : { asLocalhost : false , enabled : true } , eventHandlerOptions : { commitTimeout : timeout } , queryHandlerOptions : { strategy : sinon . match . any } } ) ; } ) ; it ( 'should use discovery as localhost for localhost peer connections' , async ( ) => { const connectionProfile = { peers : { 'peer0.org1.example.com' : { url : 'grpc://localhost:7051' } } } ; fabricConnection = new TestFabricConnection ( '/tmp/somepath.json' , connectionProfile , null ) ; await fabricConnection . connect ( fabricWallet , mockIdentityName , timeout ) ; fabricGatewayStub . connect . should . have . been . calledWith ( connectionProfile , { wallet : mockWallet , identity : mockIdentityName , discovery : { asLocalhost : true , enabled : true } , eventHandlerOptions : { commitTimeout : timeout } , queryHandlerOptions : { strategy : sinon . match . any } } ) ; } ) ; it ( 'should not use discovery as localhost for remote peer connections' , async ( ) => { const connectionProfile = { peers : { 'peer0.org1.example.com' : { url : 'grpc://192.168.1.1:7051' } } } ; fabricConnection = new TestFabricConnection ( '/tmp/somepath.json' , connectionProfile , null ) ; await fabricConnection . connect ( fabricWallet , mockIdentityName , timeout ) ; fabricGatewayStub . connect . should . have . been . calledWith ( connectionProfile , { wallet : mockWallet , identity : mockIdentityName , discovery : { asLocalhost : false , enabled : true } , eventHandlerOptions : { commitTimeout : timeout } , queryHandlerOptions : { strategy : sinon . match . any } } ) ; } ) ; it ( 'should not use discovery as localhost for dodgy peer connections' , async ( ) => { const connectionProfile = { peers : { 'peer0.org1.example.com' : { missingUrl : 'grpc://192.168.1.1:7051' } } } ; fabricConnection = new TestFabricConnection ( '/tmp/somepath.json' , connectionProfile , null ) ; await fabricConnection . connect ( fabricWallet , mockIdentityName , timeout ) ; fabricGatewayStub . connect . should . have . been . calledWith ( connectionProfile , { wallet : mockWallet , identity : mockIdentityName , discovery : { asLocalhost : false , enabled : true } , eventHandlerOptions : { commitTimeout : timeout } , queryHandlerOptions : { strategy : sinon . match . any } } ) ; } ) ; it ( 'should use discovery as localhost for localhost certificate authority connections' , async ( ) => { const connectionProfile = { certificateAuthorities : { 'ca.org1.example.com' : { url : 'http://localhost:7054' } } } ; fabricConnection = new TestFabricConnection ( '/tmp/somepath.json' , connectionProfile , null ) ; await fabricConnection . connect ( fabricWallet , mockIdentityName , timeout ) ; fabricGatewayStub . connect . should . have . been . calledWith ( connectionProfile , { wallet : mockWallet , identity : mockIdentityName , discovery : { asLocalhost : true , enabled : true } , eventHandlerOptions : { commitTimeout : timeout } , queryHandlerOptions : { strategy : sinon . match . any } } ) ; } ) ; it ( 'should not use discovery as localhost for remote certificate authority connections' , async ( ) => { const connectionProfile = { certificateAuthorities : { 'ca.org1.example.com' : { url : 'http://192.168.1.1:7054' } } } ; fabricConnection = new TestFabricConnection ( '/tmp/somepath.json' , connectionProfile , null ) ; await fabricConnection . connect ( fabricWallet , mockIdentityName , timeout ) ; fabricGatewayStub . connect . should . have . been . calledWith ( connectionProfile , { wallet : mockWallet , identity : mockIdentityName , discovery : { asLocalhost : false , enabled : true } , eventHandlerOptions : { commitTimeout : timeout } , queryHandlerOptions : { strategy : sinon . match . any } } ) ; } ) ; it ( 'should not use discovery as localhost for dodgy certificate authority connections' , async ( ) => { const connectionProfile = { certificateAuthorities : { 'ca.org1.example.com' : { missingUrl : 'http://192.168.1.1:7054' } } } ; fabricConnection = new TestFabricConnection ( '/tmp/somepath.json' , connectionProfile , null ) ; await fabricConnection . connect ( fabricWallet , mockIdentityName , timeout ) ; fabricGatewayStub . connect . should . have . been . calledWith ( connectionProfile , { wallet : mockWallet , identity : mockIdentityName , discovery : { asLocalhost : false , enabled : true } , eventHandlerOptions : { commitTimeout : timeout } , queryHandlerOptions : { strategy : sinon . match . any } } ) ; } ) ; } ) ; describe ( 'getAllPeerNames' , ( ) => { it ( 'should get all the names of the peer' , async ( ) => { const peerNames = fabricConnection . getAllPeerNames ( ) ; peerNames . should . deep . equal ( [ 'peer0.org1.example.com' , 'peer0.org2.example.com' ] ) ; } ) ; } ) ; describe ( 'getAllChannelsForPeer' , ( ) => { it ( 'should get all the channels a peer has joined' , async ( ) => { mySandBox . stub ( LifecyclePeer . prototype , 'getAllChannelNames' ) . resolves ( [ 'channel-one' , 'channel-two' ] ) ; await fabricConnection . connect ( fabricWallet , mockIdentityName , timeout ) ; const channelNames = await fabricConnection . getAllChannelsForPeer ( 'peer0.org1.example.com' ) ; channelNames . should . deep . equal ( [ 'channel-one' , 'channel-two' ] ) ; } ) ; it ( 'should use the connection profile for the list of channels if the peer says access is denied' , async ( ) => { mySandBox . stub ( LifecyclePeer . prototype , 'getAllChannelNames' ) . rejects ( new Error ( 'blah access denied blah' ) ) ; await fabricConnection . connect ( fabricWallet , mockIdentityName , timeout ) ; const channelNames = await fabricConnection . getAllChannelsForPeer ( 'peer0.org1.example.com' ) ; channelNames . should . deep . equal ( [ 'channel-from-the-ccp' ] ) ; } ) ; it ( 'should rethrow the error if the peer says access is denied and there are no matching channels in the connection profile' , async ( ) => { mySandBox . stub ( LifecyclePeer . prototype , 'getAllChannelNames' ) . rejects ( new Error ( 'blah access denied blah' ) ) ; await fabricConnection . connect ( fabricWallet , mockIdentityName , timeout ) ; await fabricConnection . getAllChannelsForPeer ( 'peer0.org2.example.com' ) . should . be . rejectedWith ( /blah access denied blah/ ) ; } ) ; it ( 'should rethrow any other error' , async ( ) => { mySandBox . stub ( LifecyclePeer . prototype , 'getAllChannelNames' ) . rejects ( new Error ( 'such error' ) ) ; await fabricConnection . connect ( fabricWallet , mockIdentityName , timeout ) ; await fabricConnection . getAllChannelsForPeer ( 'peer0.org1.example.com' ) . should . be . rejectedWith ( /such error/ ) ; } ) ; } ) ; describe ( 'getInstantiatedChaincode' , ( ) => { it ( 'should get the instantiated chaincode from a v2 channel in the connection profile' , async ( ) => { const getChannelCapabilitiesStub = mySandBox . stub ( LifecyclePeer . prototype , 'getChannelCapabilities' ) ; getChannelCapabilitiesStub . resolves ( [ 'V2_0' ] ) ; mySandBox . stub ( Lifecycle . prototype , 'getPeer' ) . returns ( { getChannelCapabilities : getChannelCapabilitiesStub } ) ; const getAllChannelsForPeerStub = mySandBox . stub ( fabricConnection , 'getAllChannelsForPeer' ) ; getAllChannelsForPeerStub . withArgs ( 'peerOne' ) . returns ( [ 'channel1' ] ) ; getAllChannelsForPeerStub . withArgs ( 'peerTwo' ) . returns ( [ 'channel2' ] ) ; mySandBox . stub ( fabricConnection , 'getAllPeerNames' ) . returns ( [ 'peerOne' , 'peerTwo' ] ) ; mySandBox . stub ( LifecycleChannel . prototype , 'getAllCommittedSmartContracts' ) . resolves ( [ { smartContractName : 'biscuit-network' , smartContractVersion : '0.7' , sequence : 2 } , { smartContractName : 'cake-network' , smartContractVersion : '0.8' , sequence : 3 } ] ) ; await fabricConnection . connect ( fabricWallet , mockIdentityName , timeout ) ; const instantiatedChaincodes = await fabricConnection . getInstantiatedChaincode ( 'channel1' ) ; instantiatedChaincodes . should . deep . equal ( [ { name : 'biscuit-network' , version : '0.7' , sequence : 2 } , { name : 'cake-network' , version : '0.8' , sequence : 3 } ] ) ; } ) ; it ( 'should get the instantiated chaincode from a v1 channel in the connection profile' , async ( ) => { const getChannelCapabilitiesStub = mySandBox . stub ( LifecyclePeer . prototype , 'getChannelCapabilities' ) ; getChannelCapabilitiesStub . resolves ( [ 'V1_4' ] ) ; mySandBox . stub ( Lifecycle . prototype , 'getPeer' ) . returns ( { getChannelCapabilities : getChannelCapabilitiesStub } ) ; const getAllChannelsForPeerStub = mySandBox . stub ( fabricConnection , 'getAllChannelsForPeer' ) ; getAllChannelsForPeerStub . withArgs ( 'peerOne' ) . returns ( [ 'channel1' ] ) ; getAllChannelsForPeerStub . withArgs ( 'peerTwo' ) . returns ( [ 'channel2' ] ) ; mySandBox . stub ( fabricConnection , 'getAllPeerNames' ) . returns ( [ 'peerOne' , 'peerTwo' ] ) ; mySandBox . stub ( LifecycleChannel . prototype , 'getAllInstantiatedSmartContracts' ) . resolves ( [ { smartContractName : 'grape-network' , smartContractVersion : '0.8' , sequence : - 1 } , { smartContractName : 'fruit-network' , smartContractVersion : '0.9' , sequence : - 1 } ] ) ; await fabricConnection . connect ( fabricWallet , mockIdentityName , timeout ) ; const instantiatedChaincodes = await fabricConnection . getInstantiatedChaincode ( 'channel1' ) ; instantiatedChaincodes . should . deep . equal ( [ { name : 'grape-network' , version : '0.8' , sequence : - 1 } , { name : 'fruit-network' , version : '0.9' , sequence : - 1 } ] ) ; } ) ; } ) ; describe ( 'disconnect' , ( ) => { it ( 'should disconnect from gateway' , async ( ) => { fabricConnection . disconnect ( ) ; fabricGatewayStub . disconnect . should . have . been . called ; } ) ; } ) ; describe ( 'createChannelMap' , ( ) => { it . skip ( 'should create channel map' , async ( ) => { mySandBox . stub ( fabricConnection , 'getAllPeerNames' ) . returns ( [ 'peerOne' , 'peerTwo' ] ) ; const getChannelCapabilitiesStub = mySandBox . stub ( LifecyclePeer . prototype , 'getChannelCapabilities' ) ; getChannelCapabilitiesStub . resolves ( [ 'V2_0' ] ) ; mySandBox . stub ( Lifecycle . prototype , 'getPeer' ) . returns ( { getChannelCapabilities : getChannelCapabilitiesStub } ) ; const getAllChannelsForPeerStub = mySandBox . stub ( fabricConnection , 'getAllChannelsForPeer' ) ; getAllChannelsForPeerStub . withArgs ( 'peerOne' ) . returns ( [ 'channel1' ] ) ; getAllChannelsForPeerStub . withArgs ( 'peerTwo' ) . returns ( [ 'channel2' ] ) ; const _map = new Map < string , Array < string > > ( ) ; _map . set ( 'channel1' , [ 'peerOne' ] ) ; _map . set ( 'channel2' , [ 'peerTwo' ] ) ; const map = await fabricConnection . createChannelMap ( ) ; getChannelCapabilitiesStub . should . have . been . calledTwice ; map . should . deep . equal ( _map ) ; } ) ; it ( 'should add any peers to channel map if the channel already exists' , async ( ) => { mySandBox . stub ( fabricConnection , 'getAllPeerNames' ) . returns ( [ 'peerOne' , 'peerTwo' ] ) ; const getChannelCapabilitiesStub = mySandBox . stub ( LifecyclePeer . prototype , 'getChannelCapabilities' ) ; getChannelCapabilitiesStub . resolves ( [ 'V2_0' ] ) ; mySandBox . stub ( Lifecycle . prototype , 'getPeer' ) . returns ( { getChannelCapabilities : getChannelCapabilitiesStub } ) ; const getAllChannelsForPeerStub = mySandBox . stub ( fabricConnection , 'getAllChannelsForPeer' ) ; getAllChannelsForPeerStub . withArgs ( 'peerOne' ) . returns ( [ 'channel1' , 'channel2' ] ) ; getAllChannelsForPeerStub . withArgs ( 'peerTwo' ) . returns ( [ 'channel2' ] ) ; const _map = new Map < string , Array < string > > ( ) ; _map . set ( 'channel1' , [ 'peerOne' ] ) ; _map . set ( 'channel2' , [ 'peerOne' , 'peerTwo' ] ) ; const map = await fabricConnection . createChannelMap ( ) ; map . should . deep . equal ( _map ) ; } ) ; it ( 'should handle gRPC connection errors' , async ( ) => { mySandBox . stub ( fabricConnection , 'getAllPeerNames' ) . returns ( [ 'peerOne' , 'peerTwo' ] ) ; const error = new Error ( 'Received http2 header with status: 503' ) ; mySandBox . stub ( fabricConnection , 'getAllChannelsForPeer' ) . throws ( error ) ; await fabricConnection . createChannelMap ( ) . should . be . rejectedWith ( `Cannot connect to Fabric: ${ error . message }` ) ; } ) ; it ( 'should handle no peers' , async ( ) => { mySandBox . stub ( fabricConnection , 'getAllPeerNames' ) . returns ( [ ] ) ; await fabricConnection . createChannelMap ( ) . should . be . rejectedWith ( 'Error querying channel list: Could not find any peers to query the list of channels from' ) ; } ) ; it . skip ( 'should throw error if channel is not using v2 capabilities ' , async ( ) => { mySandBox . stub ( fabricConnection , 'getAllPeerNames' ) . returns ( [ 'peerOne' , 'peerTwo' ] ) ; const getChannelCapabilitiesStub = mySandBox . stub ( LifecyclePeer . prototype , 'getChannelCapabilities' ) ; getChannelCapabilitiesStub . resolves ( [ 'V1_4_3' ] ) ; mySandBox . stub ( Lifecycle . prototype , 'getPeer' ) . returns ( { getChannelCapabilities : getChannelCapabilitiesStub } ) ; const getAllChannelsForPeerStub = mySandBox . stub ( fabricConnection , 'getAllChannelsForPeer' ) ; getAllChannelsForPeerStub . withArgs ( 'peerOne' ) . returns ( [ 'channel1' ] ) ; getAllChannelsForPeerStub . withArgs ( 'peerTwo' ) . returns ( [ 'channel2' ] ) ; await fabricConnection . createChannelMap ( ) . should . be . rejectedWith ( `Unable to connect to network, channel 'channel1' does not have V2_0 capabilities enabled.` ) ; getChannelCapabilitiesStub . should . have . been . calledOnce ; } ) ; it ( 'should handle any other errors' , async ( ) => { const error = new Error ( 'some error' ) ; mySandBox . stub ( fabricConnection , 'getAllPeerNames' ) . throws ( error ) ; await fabricConnection . createChannelMap ( ) . should . be . rejectedWith ( `Error querying channel list: ${ error . message }` ) ; } ) ; } ) ; describe ( 'getChannelPeersInfo' , ( ) => { it ( 'should get the channel peer names' , async ( ) => { const channelName = 'theChannelName' ; const peerNames = [ 'peerName1' , 'peerName2' ] ; const mspIds = [ 'Org1MSP' , 'Org2MSP' ] ; fabricChannelStub . getEndorsers . returns ( [ { name : peerNames [ 0 ] , mspid : mspIds [ 0 ] } , { name : peerNames [ 1 ] , mspid : mspIds [ 1 ] } ] ) ; const channelPeersInfo = await fabricConnection . getChannelPeersInfo ( channelName ) ; fabricChannelStub . getEndorsers . should . have . been . calledOnce ; channelPeersInfo . length . should . equal ( 2 ) ; channelPeersInfo . should . deep . equal ( [ { name : peerNames [ 0 ] , mspID : mspIds [ 0 ] } , { name : peerNames [ 1 ] , mspID : mspIds [ 1 ] } ] ) ; } ) ; it ( 'should handle any errors' , async ( ) => { const channelName = 'theChannelName' ; const error = new Error ( 'Could not get channel' ) ; fabricChannelStub . getEndorsers . throws ( error ) ; await fabricConnection . getChannelPeersInfo ( channelName ) . should . be . rejectedWith ( `Unable to get channel peers info: ${ error . message }` ) ; } ) ; } ) ; describe ( 'getChannelCapabilityFromPeer' , ( ) => { let mockPeer ; let channelName ; let peerName ; beforeEach ( ( ) => { channelName = 'mychannel' ; peerName = 'Org1 Peer1' ; mockPeer = mySandBox . createStubInstance ( LifecyclePeer ) ; fabricConnection [ 'lifecycle' ] [ 'peers' ] . clear ( ) ; fabricConnection [ 'lifecycle' ] [ 'peers' ] . set ( peerName , mockPeer as unknown as LifecyclePeer ) ; mockPeer . getChannelCapabilities . resolves ( [ 'V2_0' ] ) ; } ) ; it ( 'should get the capabilities of channel' , async ( ) => { const result = await fabricConnection . getChannelCapabilityFromPeer ( channelName , peerName ) ; result . should . deep . equal ( [ 'V2_0' ] ) ; mockPeer . getChannelCapabilities . should . have . been . calledOnce ; } ) ; it ( 'should handle errors' , async ( ) => { const error = new Error ( 'some error' ) ; mockPeer . getChannelCapabilities . rejects ( error ) ; await fabricConnection . getChannelCapabilityFromPeer ( channelName , peerName ) . should . be . rejectedWith ( `Unable to determine channel capabilities of channel ${ channelName }: ${ error . message }` ) ; mockPeer . getChannelCapabilities . should . have . been . calledOnce ; } ) ; } ) ; } ) ;