'use strict' ; import * as vscode from 'vscode' ; import { FabricGatewayConnection } from 'ibm-blockchain-platform-gateway-v1' ; import { FabricRuntimeUtil , FabricGatewayRegistryEntry } from 'ibm-blockchain-platform-common' ; import * as chai from 'chai' ; import * as sinon from 'sinon' ; import * as sinonChai from 'sinon-chai' ; import * as path from 'path' ; import { TestUtil } from '../TestUtil' ; import { FabricGatewayConnectionManager } from '../../extension/fabric/FabricGatewayConnectionManager' ; import { UserInputUtil } from '../../extension/commands/UserInputUtil' ; import { BlockchainTreeItem } from '../../extension/explorer/model/BlockchainTreeItem' ; import { BlockchainGatewayExplorerProvider } from '../../extension/explorer/gatewayExplorer' ; import { ChannelTreeItem } from '../../extension/explorer/model/ChannelTreeItem' ; import { ExtensionCommands } from '../../ExtensionCommands' ; import { InstantiatedContractTreeItem } from '../../extension/explorer/model/InstantiatedContractTreeItem' ; import { ExtensionUtil } from '../../extension/util/ExtensionUtil' ; import { InstantiatedTreeItem } from '../../extension/explorer/model/InstantiatedTreeItem' ; import { VSCodeBlockchainOutputAdapter } from '../../extension/logging/VSCodeBlockchainOutputAdapter' ; import { FabricGatewayHelper } from '../../extension/fabric/FabricGatewayHelper' ; import { LogType } from 'ibm-blockchain-platform-common' ; import { TransactionView } from '../../extension/webview/TransactionView' ; import ITransaction from '../../extension/interfaces/ITransaction' ; import IAssociatedTxData from '../../extension/interfaces/IAssociatedTxData' ; import ISmartContract from '../../extension/interfaces/ISmartContract' ; import ITxDataFile from '../../extension/interfaces/ITxDataFile' ; import { ContractTreeItem } from '../../extension/explorer/model/ContractTreeItem' ; chai . use ( sinonChai ) ; chai . should ( ) ; interface IAppState { gatewayName : string ; smartContracts : ISmartContract [ ] ; associatedTxdata : IAssociatedTxData ; preselectedSmartContract : ISmartContract ; preselectedTransaction : ITransaction ; } const transactionOne = { name : 'transactionOne' , parameters : [ { description : '' , name : 'name' , schema : { } } ] , returns : { type : '' } , tag : [ 'submit' ] } ; const transactionTwo = { name : 'transactionTwo' , parameters : [ ] , returns : { type : '' } , tag : [ 'submit' ] } ; const transactionsInFiles = [ { transactionName : transactionOne . name , transactionLabel : transactionOne . name , arguments : [ ] , transientData : { } , txDataFile : 'file.txdata' , } , { transactionName : transactionTwo . name , transactionLabel : transactionTwo . name , arguments : [ ] , transientData : { } , txDataFile : 'file2.txdata' , } ] ; describe ( 'OpenTransactionViewCommand' , ( ) => { let mySandBox = sinon . createSandbox ( ) ; let instantiatedSmartContract ; let fabricClientConnectionMock ; let executeCommandStub ; let getConnectionStub ; let showInstantiatedSmartContractQuickPickStub ; let showChannelFromGatewayQuickPickBoxStub ; let fabricConnectionManager ; let logSpy ; let gatewayRegistryEntry ; let getGatewayRegistryStub ; let openViewStub ; before ( async ( ) => { await TestUtil . setupTests ( mySandBox ) ; } ) ; describe ( 'OpenTransactionView' , ( ) => { beforeEach ( async ( ) => { mySandBox = sinon . createSandbox ( ) ; logSpy = mySandBox . spy ( VSCodeBlockchainOutputAdapter . instance ( ) , 'log' ) ; executeCommandStub = mySandBox . stub ( vscode . commands , 'executeCommand' ) ; executeCommandStub . callThrough ( ) ; executeCommandStub . withArgs ( ExtensionCommands . CONNECT_TO_GATEWAY ) . resolves ( ) ; fabricClientConnectionMock = mySandBox . createStubInstance ( FabricGatewayConnection ) ; fabricClientConnectionMock . connect . resolves ( ) ; const map = new Map < string , Array < string > > ( ) ; map . set ( 'myChannel' , [ 'peerOne' ] ) ; map . set ( 'otherChannel' , [ 'otherPeer' ] ) ; fabricClientConnectionMock . createChannelMap . resolves ( map ) ; fabricClientConnectionMock . getChannelCapabilityFromPeer . resolves ( [ UserInputUtil . V2_0 ] ) ; fabricConnectionManager = FabricGatewayConnectionManager . instance ( ) ; getConnectionStub = mySandBox . stub ( fabricConnectionManager , 'getConnection' ) . returns ( fabricClientConnectionMock ) ; gatewayRegistryEntry = new FabricGatewayRegistryEntry ( ) ; gatewayRegistryEntry . name = 'myGateway' ; getGatewayRegistryStub = mySandBox . stub ( fabricConnectionManager , 'getGatewayRegistryEntry' ) ; getGatewayRegistryStub . resolves ( gatewayRegistryEntry ) ; mySandBox . stub ( FabricGatewayHelper , 'getConnectionProfilePath' ) . resolves ( path . join ( 'myPath' , 'connection.json' ) ) ; fabricClientConnectionMock . getAllPeerNames . returns ( [ 'peerOne' ] ) ; fabricClientConnectionMock . getAllChannelsForPeer . withArgs ( 'peerOne' ) . resolves ( [ 'myChannel' ] ) ; fabricClientConnectionMock . getChannelPeersInfo . resolves ( [ { name : 'peerOne' , mspID : 'org1msp' } , { name : 'peerTwo' , mspID : 'org1msp' } ] ) ; fabricClientConnectionMock . getInstantiatedChaincode . resolves ( [ { name : 'mySmartContract' , version : '0.0.1' } ] ) ; fabricClientConnectionMock . getMetadata . resolves ( { contracts : { 'my-contract' : { name : 'my-contract' , transactions : [ transactionOne , transactionTwo , ] , } , 'org.hyperledger.fabric' : { name : 'org.hyperledger.fabric' , transactions : [ { name : 'GetMetadata' } ] } } } ) ; fabricClientConnectionMock . getMetadata . withArgs ( 'MultiContract' , sinon . match . any ) . resolves ( { contracts : { 'my-contract' : { name : 'my-contract' , transactions : [ transactionOne , transactionTwo , ] , } , 'my-contract-2' : { name : 'my-contract-2' , transactions : [ transactionOne , ] , } } } ) ; showChannelFromGatewayQuickPickBoxStub = mySandBox . stub ( UserInputUtil , 'showChannelFromGatewayQuickPickBox' ) ; showChannelFromGatewayQuickPickBoxStub . resolves ( { label : 'myChannel' } ) ; showInstantiatedSmartContractQuickPickStub = mySandBox . stub ( UserInputUtil , 'showClientInstantiatedSmartContractsQuickPick' ) ; showInstantiatedSmartContractQuickPickStub . resolves ( { label : 'myContract' , data : { name : 'myContract' , channel : 'myChannel' , version : '0.0.1' } } ) ; openViewStub = mySandBox . stub ( TransactionView . prototype , 'openView' ) . resolves ( ) ; mySandBox . stub ( TransactionView , 'readTxdataFiles' ) . resolves ( transactionsInFiles ) ; } ) ; afterEach ( async ( ) => { mySandBox . restore ( ) ; } ) ; it ( 'should open the transaction web view through the tree' , async ( ) => { const blockchainGatewayExplorerProvider = ExtensionUtil . getBlockchainGatewayExplorerProvider ( ) ; const allChildren = await blockchainGatewayExplorerProvider . getChildren ( ) ; const channels = await blockchainGatewayExplorerProvider . getChildren ( allChildren [ 2 ] ) as Array < ChannelTreeItem > ; const contracts = await blockchainGatewayExplorerProvider . getChildren ( channels [ 0 ] ) as Array < InstantiatedTreeItem > ; instantiatedSmartContract = contracts [ 0 ] ; const appstate = await vscode . commands . executeCommand ( ExtensionCommands . OPEN_TRANSACTION_PAGE , instantiatedSmartContract ) ; const smartContract = { channel : 'myChannel' , label : 'mySmartContract@0.0.1' , name : 'mySmartContract' , namespace : 'my-contract' , contractName : 'my-contract' , peerNames : [ 'peerOne' , 'peerTwo' ] , version : '0.0.1' , transactions : [ transactionOne , transactionTwo , ] , } ; const want = { associatedTxdata : { } , gatewayName : 'myGateway' , smartContracts : [ smartContract ] , preselectedSmartContract : smartContract , preselectedTransaction : undefined , } ; appstate . should . deep . equal ( want ) ; logSpy . should . have . been . calledWith ( LogType . INFO , undefined , `Open Transaction View` ) ; openViewStub . should . have . been . calledOnce ; } ) ; it ( 'should open the transaction web view through the tree and handle set the preselectedTransaction when passed in' , async ( ) => { const blockchainGatewayExplorerProvider = ExtensionUtil . getBlockchainGatewayExplorerProvider ( ) ; const allChildren = await blockchainGatewayExplorerProvider . getChildren ( ) ; const channels = await blockchainGatewayExplorerProvider . getChildren ( allChildren [ 2 ] ) as Array < ChannelTreeItem > ; const contracts = await blockchainGatewayExplorerProvider . getChildren ( channels [ 0 ] ) as Array < InstantiatedTreeItem > ; instantiatedSmartContract = contracts [ 0 ] ; const appstate = await vscode . commands . executeCommand ( ExtensionCommands . OPEN_TRANSACTION_PAGE , instantiatedSmartContract , 'transactionOne' ) ; const smartContract = { channel : 'myChannel' , label : 'mySmartContract@0.0.1' , name : 'mySmartContract' , namespace : 'my-contract' , contractName : 'my-contract' , peerNames : [ 'peerOne' , 'peerTwo' ] , version : '0.0.1' , transactions : [ transactionOne , transactionTwo , ] , } ; const want = { associatedTxdata : { } , gatewayName : 'myGateway' , smartContracts : [ smartContract ] , preselectedSmartContract : smartContract , preselectedTransaction : transactionOne , } ; appstate . should . deep . equal ( want ) ; logSpy . should . have . been . calledWith ( LogType . INFO , undefined , `Open Transaction View` ) ; openViewStub . should . have . been . calledOnce ; } ) ; it ( 'should open the transaction web view through the tree and handle when the preselectedTransaction does not exist' , async ( ) => { const blockchainGatewayExplorerProvider = ExtensionUtil . getBlockchainGatewayExplorerProvider ( ) ; const allChildren = await blockchainGatewayExplorerProvider . getChildren ( ) ; const channels = await blockchainGatewayExplorerProvider . getChildren ( allChildren [ 2 ] ) as Array < ChannelTreeItem > ; const contracts = await blockchainGatewayExplorerProvider . getChildren ( channels [ 0 ] ) as Array < InstantiatedTreeItem > ; instantiatedSmartContract = contracts [ 0 ] ; const appstate = await vscode . commands . executeCommand ( ExtensionCommands . OPEN_TRANSACTION_PAGE , instantiatedSmartContract , 'does not exist' ) ; const smartContract = { channel : 'myChannel' , label : 'mySmartContract@0.0.1' , name : 'mySmartContract' , namespace : 'my-contract' , contractName : 'my-contract' , peerNames : [ 'peerOne' , 'peerTwo' ] , version : '0.0.1' , transactions : [ transactionOne , transactionTwo , ] , } ; const want = { associatedTxdata : { } , gatewayName : 'myGateway' , smartContracts : [ smartContract ] , preselectedSmartContract : smartContract , preselectedTransaction : undefined , } ; appstate . should . deep . equal ( want ) ; logSpy . should . have . been . calledWith ( LogType . INFO , undefined , `Open Transaction View` ) ; openViewStub . should . have . been . calledOnce ; } ) ; it ( 'should open the transaction web view through the tree when the chaincode contains multiple contracts and make the correct transaction active' , async ( ) => { const blockchainGatewayExplorerProvider = ExtensionUtil . getBlockchainGatewayExplorerProvider ( ) ; const allChildren = await blockchainGatewayExplorerProvider . getChildren ( ) ; const channels = await blockchainGatewayExplorerProvider . getChildren ( allChildren [ 2 ] ) as Array < ChannelTreeItem > ; fabricClientConnectionMock . getInstantiatedChaincode . resolves ( [ { name : 'MultiContract' , version : '0.0.1' } ] ) ; const chainCodeElement = { channels , collapsibleState : 1 , contextValue : 'blockchain-instantiated-multi-contract-item' , contracts : [ 'my-contract' , 'my-contract-2' ] , iconPath : { light : '' , dark : '' } , label : 'MultiContract@0.0.1' , name : 'MultiContract' , provider : blockchainGatewayExplorerProvider , showIcon : true , tooltip : '' , version : '0.0.1' , } ; const treeItem = new ContractTreeItem ( blockchainGatewayExplorerProvider , 'my-contract-2' , 1 , chainCodeElement , [ transactionOne . name , transactionTwo . name ] , chainCodeElement . channels [ 0 ] . label ) ; const appstate = await vscode . commands . executeCommand ( ExtensionCommands . OPEN_TRANSACTION_PAGE , treeItem , transactionOne . name ) ; const smartContract = { channel : 'myChannel' , label : 'MultiContract@0.0.1' , name : 'MultiContract' , namespace : 'my-contract' , contractName : 'my-contract' , peerNames : [ 'peerOne' , 'peerTwo' ] , version : '0.0.1' , transactions : [ transactionOne , transactionTwo , ] , } ; const smartContract2 = { ... smartContract , namespace : 'my-contract-2' , contractName : 'my-contract-2' , transactions : [ transactionOne , ] } ; const want = { associatedTxdata : { } , gatewayName : 'myGateway' , smartContracts : [ smartContract , smartContract2 ] , preselectedSmartContract : smartContract2 , preselectedTransaction : transactionOne , } ; appstate . should . deep . equal ( want ) ; logSpy . should . have . been . calledWith ( LogType . INFO , undefined , `Open Transaction View` ) ; openViewStub . should . have . been . calledOnce ; } ) ; it ( 'should open the transaction web view for a contract without metadata' , async ( ) => { const blockchainGatewayExplorerProvider = ExtensionUtil . getBlockchainGatewayExplorerProvider ( ) ; const allChildren = await blockchainGatewayExplorerProvider . getChildren ( ) ; const channels = await blockchainGatewayExplorerProvider . getChildren ( allChildren [ 2 ] ) as Array < ChannelTreeItem > ; const contracts = await blockchainGatewayExplorerProvider . getChildren ( channels [ 0 ] ) as Array < InstantiatedTreeItem > ; fabricClientConnectionMock . getInstantiatedChaincode . resolves ( [ { name : 'mySmartContract' , version : '0.0.1' } , { name : 'mySmartContract2' , version : '0.0.2' } ] ) ; instantiatedSmartContract = contracts [ 0 ] ; fabricClientConnectionMock . getMetadata . rejects ( 'Transaction function "org.hyperledger.fabric:GetMetadata" did not return any metadata' ) ; const appstate = await vscode . commands . executeCommand ( ExtensionCommands . OPEN_TRANSACTION_PAGE , instantiatedSmartContract ) ; const smartContract = { channel : 'myChannel' , label : 'mySmartContract@0.0.1' , name : 'mySmartContract' , contractName : undefined , namespace : undefined , peerNames : [ 'peerOne' , 'peerTwo' ] , version : '0.0.1' , transactions : [ ] } ; const want = { associatedTxdata : { } , gatewayName : 'myGateway' , smartContracts : [ smartContract ] , preselectedSmartContract : smartContract , preselectedTransaction : undefined , } ; appstate . should . deep . equal ( want ) ; logSpy . should . have . been . calledWith ( LogType . INFO , undefined , `Open Transaction View` ) ; openViewStub . should . have . been . calledOnce ; } ) ; it ( 'should open the transaction web view through the command' , async ( ) => { const appstate = await vscode . commands . executeCommand ( ExtensionCommands . OPEN_TRANSACTION_PAGE ) ; const want = { associatedTxdata : { } , gatewayName : 'myGateway' , smartContracts : [ ] , preselectedSmartContract : undefined , preselectedTransaction : undefined , } ; appstate . should . deep . equal ( want ) ; logSpy . should . have . been . calledWith ( LogType . INFO , undefined , `Open Transaction View` ) ; showInstantiatedSmartContractQuickPickStub . should . have . been . calledOnce ; openViewStub . should . have . been . calledOnce ; } ) ; it ( 'should open the transaction web view through the command' , async ( ) => { const chaincode = { chaincodeName : 'mySmartContract' , channelName : 'myChannel' , transactionDataPath : '/directory' , } ; showInstantiatedSmartContractQuickPickStub . resolves ( { label : 'mySmartContract' , data : { name : 'mySmartContract' , channel : 'myChannel' , version : '0.0.1' } } ) ; getGatewayRegistryStub . resolves ( { ... gatewayRegistryEntry , transactionDataDirectories : [ chaincode ] , } ) ; const appstate = await vscode . commands . executeCommand ( ExtensionCommands . OPEN_TRANSACTION_PAGE ) ; const associatedTxdata = { [ chaincode . chaincodeName ] : { channelName : chaincode . channelName , transactionDataPath : chaincode . transactionDataPath , transactions : transactionsInFiles , } } ; const smartContract = { channel : 'myChannel' , label : 'mySmartContract@0.0.1' , name : 'mySmartContract' , namespace : 'my-contract' , contractName : 'my-contract' , peerNames : [ 'peerOne' , 'peerTwo' ] , version : '0.0.1' , transactions : [ transactionOne , transactionTwo , ] , } ; const want = { associatedTxdata , gatewayName : 'myGateway' , smartContracts : [ smartContract ] , preselectedSmartContract : undefined , preselectedTransaction : undefined , } ; appstate . should . deep . equal ( want ) ; logSpy . should . have . been . calledWith ( LogType . INFO , undefined , `Open Transaction View` ) ; showInstantiatedSmartContractQuickPickStub . should . have . been . calledOnce ; openViewStub . should . have . been . calledOnce ; } ) ; it ( `should correctly display the ${ FabricRuntimeUtil . LOCAL_FABRIC } gateway name` , async ( ) => { const localGatewayRegistryEntry = new FabricGatewayRegistryEntry ( ) ; localGatewayRegistryEntry . name = `${ FabricRuntimeUtil . LOCAL_FABRIC } - Org1` ; localGatewayRegistryEntry . displayName = `Org1` ; getGatewayRegistryStub . resolves ( localGatewayRegistryEntry ) ; await vscode . commands . executeCommand ( ExtensionCommands . OPEN_TRANSACTION_PAGE ) ; logSpy . should . have . been . calledWith ( LogType . INFO , undefined , `Open Transaction View` ) ; showInstantiatedSmartContractQuickPickStub . should . have . been . calledOnce ; openViewStub . should . have . been . calledOnce ; } ) ; it ( 'should open the transaction web view through the command when not connected' , async ( ) => { getConnectionStub . onCall ( 0 ) . returns ( null ) ; await vscode . commands . executeCommand ( ExtensionCommands . OPEN_TRANSACTION_PAGE ) ; logSpy . should . have . been . calledWith ( LogType . INFO , undefined , `Open Transaction View` ) ; executeCommandStub . should . have . been . calledWith ( ExtensionCommands . CONNECT_TO_GATEWAY ) ; showInstantiatedSmartContractQuickPickStub . should . have . been . calledOnce ; openViewStub . should . have . been . calledOnce ; } ) ; it ( 'should handle cancellation when connecting to a gateway' , async ( ) => { getConnectionStub . onCall ( 0 ) . returns ( null ) ; getConnectionStub . onCall ( 1 ) . returns ( null ) ; await vscode . commands . executeCommand ( ExtensionCommands . OPEN_TRANSACTION_PAGE ) ; logSpy . should . have . been . calledWith ( LogType . INFO , undefined , `Open Transaction View` ) ; executeCommandStub . should . have . been . calledWith ( ExtensionCommands . CONNECT_TO_GATEWAY ) ; showInstantiatedSmartContractQuickPickStub . should . not . have . been . called ; openViewStub . should . not . have . been . called ; } ) ; it ( 'should handle cancellation when choosing a channel' , async ( ) => { showChannelFromGatewayQuickPickBoxStub . resolves ( ) ; await vscode . commands . executeCommand ( ExtensionCommands . OPEN_TRANSACTION_PAGE ) ; logSpy . should . have . been . calledWith ( LogType . INFO , undefined , `Open Transaction View` ) ; showChannelFromGatewayQuickPickBoxStub . should . have . been . calledOnce ; showInstantiatedSmartContractQuickPickStub . should . have . not . been . called ; openViewStub . should . not . have . been . called ; } ) ; it ( 'should handle cancellation when choosing a smart contract' , async ( ) => { showInstantiatedSmartContractQuickPickStub . resolves ( ) ; await vscode . commands . executeCommand ( ExtensionCommands . OPEN_TRANSACTION_PAGE ) ; logSpy . should . have . been . calledWith ( LogType . INFO , undefined , `Open Transaction View` ) ; showInstantiatedSmartContractQuickPickStub . should . have . been . calledOnce ; openViewStub . should . not . have . been . called ; } ) ; } ) ; } ) ;