'use strict' ; import * as vscode from 'vscode' ; import * as path from 'path' ; import * as chai from 'chai' ; import * as sinon from 'sinon' ; import * as tmp from 'tmp' ; import * as sinonChai from 'sinon-chai' ; import * as fs_extra from 'fs-extra' ; import { CommandUtil } from '../../extension/util/CommandUtil' ; import { UserInputUtil , LanguageType , LanguageQuickPickItem } from '../../extension/commands/UserInputUtil' ; import { TestUtil } from '../TestUtil' ; import { VSCodeBlockchainOutputAdapter } from '../../extension/logging/VSCodeBlockchainOutputAdapter' ; import { Reporter } from '../../extension/util/Reporter' ; import { LogType } from 'ibm-blockchain-platform-common' ; import { ExtensionCommands } from '../../ExtensionCommands' ; import { YeomanUtil } from '../../extension/util/YeomanUtil' ; import { ExtensionUtil } from '../../extension/util/ExtensionUtil' ; chai . use ( sinonChai ) ; describe ( 'CreateSmartContractProjectCommand' , ( ) => { let mySandBox ; let logSpy ; let inputBoxSpy ; let showQuickPickItemStub ; let showLanguagesQuickPickStub ; let showFolderOptionsStub ; let showYesNoQuickPickStub ; let showInputBoxStub ; let browseStub ; let executeCommandStub ; let updateWorkspaceFoldersStub ; let uri ; let skipNpmInstallStub ; let sendTelemetryEventStub ; before ( async ( ) => { mySandBox = sinon . createSandbox ( ) ; await TestUtil . setupTests ( mySandBox ) ; } ) ; beforeEach ( async ( ) => { mySandBox . stub ( CommandUtil , 'sendCommandWithOutputAndProgress' ) ; showQuickPickItemStub = mySandBox . stub ( UserInputUtil , 'showQuickPickItem' ) ; logSpy = mySandBox . spy ( VSCodeBlockchainOutputAdapter . instance ( ) , 'log' ) ; inputBoxSpy = mySandBox . spy ( UserInputUtil , 'showInputBox' ) ; showLanguagesQuickPickStub = mySandBox . stub ( UserInputUtil , 'showLanguagesQuickPick' ) ; showFolderOptionsStub = mySandBox . stub ( UserInputUtil , 'showFolderOptions' ) ; showYesNoQuickPickStub = mySandBox . stub ( UserInputUtil , 'showQuickPickYesNo' ) ; showInputBoxStub = mySandBox . stub ( vscode . window , 'showInputBox' ) ; mySandBox . stub ( vscode . window , 'showOpenDialog' ) ; browseStub = mySandBox . stub ( UserInputUtil , 'browse' ) ; const originalExecuteCommand = vscode . commands . executeCommand ; executeCommandStub = mySandBox . stub ( vscode . commands , 'executeCommand' ) ; executeCommandStub . callsFake ( async function fakeExecuteCommand ( command ) : Promise < any > { if ( command !== 'vscode.openFolder' && command !== 'workbench.action.closeFolder' ) { return originalExecuteCommand . apply ( this , arguments ) ; } } ) ; updateWorkspaceFoldersStub = mySandBox . stub ( vscode . workspace , 'updateWorkspaceFolders' ) ; uri = vscode . Uri . file ( tmp . dirSync ( ) . name ) ; skipNpmInstallStub = mySandBox . stub ( ExtensionUtil , 'skipNpmInstall' ) ; skipNpmInstallStub . resolves ( true ) ; sendTelemetryEventStub = mySandBox . stub ( Reporter . instance ( ) , 'sendTelemetryEvent' ) ; showQuickPickItemStub . onFirstCall ( ) . resolves ( { label : UserInputUtil . GENERATE_DEFAULT_CONTRACT , description : UserInputUtil . GENERATE_DEFAULT_CONTRACT_DESCRIPTION , data : 'default' } ) ; } ) ; afterEach ( ( ) => { mySandBox . restore ( ) ; } ) ; const testLanguageItems = [ { label : 'javascript' , type : LanguageType . CONTRACT } , { label : 'typescript' , type : LanguageType . CONTRACT } , { label : 'java' , type : LanguageType . CONTRACT } , { label : 'go' , type : LanguageType . CHAINCODE } , ] ; for ( const testLanguageItem of testLanguageItems ) { async function checkNodeSmartContract ( fileExtension ) { const pathToCheck = path . join ( uri . fsPath , 'package.json' ) ; const smartContractExists = await fs_extra . pathExists ( pathToCheck ) ; const fileContents = await fs_extra . readFile ( pathToCheck , 'utf8' ) ; const packageJSON = JSON . parse ( fileContents ) ; smartContractExists . should . be . true ; packageJSON . name . should . equal ( path . basename ( uri . fsPath ) ) ; packageJSON . version . should . equal ( '0.0.1' ) ; packageJSON . description . should . equal ( 'My Smart Contract' ) ; packageJSON . author . should . equal ( 'John Doe' ) ; packageJSON . license . should . equal ( 'Apache-2.0' ) ; let contractPath ; if ( fileExtension === 'js' ) { contractPath = path . join ( uri . fsPath , 'lib' , `conga-contract.${ fileExtension }` ) ; } else if ( fileExtension === 'ts' ) { contractPath = path . join ( uri . fsPath , 'src' , `conga-contract.${ fileExtension }` ) ; } else { throw new Error ( 'Invalid file extension' ) ; } const contractExists = await fs_extra . pathExists ( contractPath ) ; contractExists . should . be . true ; } async function checkGoSmartContract ( ) { const pathToCheck = path . join ( uri . fsPath , 'main.go' ) ; const smartContractExists = await fs_extra . pathExists ( pathToCheck ) ; smartContractExists . should . be . true ; } async function checkJavaSmartContract ( ) { const gradlePath = path . join ( uri . fsPath , 'build.gradle' ) ; const gradleExists = await fs_extra . pathExists ( gradlePath ) ; gradleExists . should . be . true ; const contractPath = path . join ( uri . fsPath , 'src' , 'main' , 'java' , 'org' , 'example' , 'CongaContract.java' ) ; const contractExists = await fs_extra . pathExists ( contractPath ) ; contractExists . should . be . true ; } async function checkSmartContract ( ) { if ( testLanguageItem . label === 'javascript' ) { return checkNodeSmartContract ( 'js' ) ; } if ( testLanguageItem . label === 'typescript' ) { return checkNodeSmartContract ( 'ts' ) ; } else if ( testLanguageItem . label === 'go' ) { return checkGoSmartContract ( ) ; } else if ( testLanguageItem . label === 'java' ) { return checkJavaSmartContract ( ) ; } else { throw new Error ( `You must update this test to support the ${ testLanguageItem . label } language` ) ; } } it ( `should start a ${ testLanguageItem . label } smart contract project, in a new window` , async ( ) => { showLanguagesQuickPickStub . resolves ( testLanguageItem ) ; if ( testLanguageItem . type === LanguageType . CONTRACT ) { showInputBoxStub . onFirstCall ( ) . resolves ( 'Conga' ) ; } showFolderOptionsStub . resolves ( UserInputUtil . OPEN_IN_NEW_WINDOW ) ; browseStub . resolves ( uri ) ; await vscode . commands . executeCommand ( ExtensionCommands . CREATE_SMART_CONTRACT_PROJECT ) ; executeCommandStub . should . have . been . calledThrice ; executeCommandStub . should . have . been . calledWith ( 'workbench.files.action.focusFilesExplorer' ) ; executeCommandStub . should . have . been . calledWith ( 'vscode.openFolder' , uri , true ) ; logSpy . should . have . been . calledWith ( LogType . SUCCESS , 'Successfully generated Smart Contract Project' ) ; await checkSmartContract ( ) ; sendTelemetryEventStub . should . have . been . calledOnceWithExactly ( 'createSmartContractProject' , { contractLanguage : testLanguageItem . label , contractType : 'default' } ) ; } ) ; it ( `should start a ${ testLanguageItem . label } smart contract project, in current window` , async ( ) => { showLanguagesQuickPickStub . resolves ( testLanguageItem ) ; if ( testLanguageItem . type === LanguageType . CONTRACT ) { showInputBoxStub . onFirstCall ( ) . resolves ( 'Conga' ) ; } showFolderOptionsStub . resolves ( UserInputUtil . OPEN_IN_CURRENT_WINDOW ) ; browseStub . resolves ( uri ) ; await vscode . commands . executeCommand ( ExtensionCommands . CREATE_SMART_CONTRACT_PROJECT ) ; executeCommandStub . should . have . been . calledThrice ; executeCommandStub . should . have . been . calledWith ( 'workbench.files.action.focusFilesExplorer' ) ; executeCommandStub . should . have . been . calledWith ( 'vscode.openFolder' , uri , false ) ; logSpy . should . have . been . calledWith ( LogType . SUCCESS , 'Successfully generated Smart Contract Project' ) ; await checkSmartContract ( ) ; sendTelemetryEventStub . should . have . been . calledOnceWithExactly ( 'createSmartContractProject' , { contractLanguage : testLanguageItem . label , contractType : 'default' } ) ; } ) ; it ( `should start a ${ testLanguageItem . label } smart contract project, in current window with unsaved files and save` , async ( ) => { showLanguagesQuickPickStub . resolves ( testLanguageItem ) ; if ( testLanguageItem . type === LanguageType . CONTRACT ) { showInputBoxStub . onFirstCall ( ) . resolves ( 'Conga' ) ; } showFolderOptionsStub . resolves ( UserInputUtil . OPEN_IN_CURRENT_WINDOW ) ; showYesNoQuickPickStub . resolves ( UserInputUtil . YES ) ; browseStub . resolves ( uri ) ; const saveDialogStub = mySandBox . stub ( vscode . workspace , 'saveAll' ) . resolves ( true ) ; await vscode . workspace . openTextDocument ( { language : 'text' , content : 'my text file' } ) ; await vscode . commands . executeCommand ( ExtensionCommands . CREATE_SMART_CONTRACT_PROJECT ) ; executeCommandStub . should . have . been . calledThrice ; executeCommandStub . should . have . been . calledWith ( 'workbench.files.action.focusFilesExplorer' ) ; executeCommandStub . should . have . been . calledWith ( 'vscode.openFolder' , uri , false ) ; saveDialogStub . should . have . been . calledWith ( true ) ; logSpy . should . have . been . calledWith ( LogType . SUCCESS , 'Successfully generated Smart Contract Project' ) ; await checkSmartContract ( ) ; sendTelemetryEventStub . should . have . been . calledOnceWithExactly ( 'createSmartContractProject' , { contractLanguage : testLanguageItem . label , contractType : 'default' } ) ; } ) ; it ( `should start a ${ testLanguageItem . label } smart contract project, in current window with unsaved files and not save` , async ( ) => { showLanguagesQuickPickStub . resolves ( testLanguageItem ) ; if ( testLanguageItem . type === LanguageType . CONTRACT ) { showInputBoxStub . onFirstCall ( ) . resolves ( 'Conga' ) ; } showFolderOptionsStub . resolves ( UserInputUtil . OPEN_IN_CURRENT_WINDOW ) ; showYesNoQuickPickStub . resolves ( UserInputUtil . NO ) ; browseStub . resolves ( uri ) ; const saveDialogStub = mySandBox . stub ( vscode . workspace , 'saveAll' ) ; await vscode . workspace . openTextDocument ( { language : 'text' , content : 'my text file' } ) ; await vscode . commands . executeCommand ( ExtensionCommands . CREATE_SMART_CONTRACT_PROJECT ) ; executeCommandStub . should . have . been . calledThrice ; executeCommandStub . should . have . been . calledWith ( 'workbench.files.action.focusFilesExplorer' ) ; executeCommandStub . should . have . been . calledWith ( 'vscode.openFolder' , uri , false ) ; saveDialogStub . should . not . have . been . called ; logSpy . should . have . been . calledWith ( LogType . SUCCESS , 'Successfully generated Smart Contract Project' ) ; await checkSmartContract ( ) ; sendTelemetryEventStub . should . have . been . calledOnceWithExactly ( 'createSmartContractProject' , { contractLanguage : testLanguageItem . label , contractType : 'default' } ) ; } ) ; it ( `should start a ${ testLanguageItem . label } smart contract project, in a new workspace with no folders` , async ( ) => { showLanguagesQuickPickStub . resolves ( testLanguageItem ) ; if ( testLanguageItem . type === LanguageType . CONTRACT ) { showInputBoxStub . onFirstCall ( ) . resolves ( 'Conga' ) ; } showFolderOptionsStub . resolves ( UserInputUtil . ADD_TO_WORKSPACE ) ; browseStub . resolves ( uri ) ; mySandBox . stub ( vscode . workspace , 'workspaceFolders' ) . value ( undefined ) ; await vscode . commands . executeCommand ( ExtensionCommands . CREATE_SMART_CONTRACT_PROJECT ) ; executeCommandStub . should . have . been . calledTwice ; executeCommandStub . should . have . been . calledWith ( 'workbench.files.action.focusFilesExplorer' ) ; updateWorkspaceFoldersStub . should . have . been . calledWith ( sinon . match . number , 0 , { uri : uri } ) ; logSpy . should . have . been . calledWith ( LogType . SUCCESS , 'Successfully generated Smart Contract Project' ) ; await checkSmartContract ( ) ; sendTelemetryEventStub . should . have . been . calledOnceWithExactly ( 'createSmartContractProject' , { contractLanguage : testLanguageItem . label , contractType : 'default' } ) ; } ) ; it ( `should start a ${ testLanguageItem . label } smart contract project, in a new workspace with folders` , async ( ) => { showLanguagesQuickPickStub . resolves ( testLanguageItem ) ; if ( testLanguageItem . type === LanguageType . CONTRACT ) { showInputBoxStub . onFirstCall ( ) . resolves ( 'Conga' ) ; } showFolderOptionsStub . resolves ( UserInputUtil . ADD_TO_WORKSPACE ) ; browseStub . resolves ( uri ) ; await vscode . commands . executeCommand ( ExtensionCommands . CREATE_SMART_CONTRACT_PROJECT ) ; executeCommandStub . should . have . been . calledTwice ; executeCommandStub . should . have . been . calledWith ( 'workbench.files.action.focusFilesExplorer' ) ; updateWorkspaceFoldersStub . should . have . been . calledWith ( sinon . match . number , 0 , { uri : uri } ) ; logSpy . should . have . been . calledWith ( LogType . SUCCESS , 'Successfully generated Smart Contract Project' ) ; await checkSmartContract ( ) ; sendTelemetryEventStub . should . have . been . calledOnceWithExactly ( 'createSmartContractProject' , { contractLanguage : testLanguageItem . label , contractType : 'default' } ) ; } ) ; } it ( 'should start a typescript private data smart contract project, in a new window' , async ( ) => { showQuickPickItemStub . onFirstCall ( ) . resolves ( { label : UserInputUtil . GENERATE_PD_CONTRACT , description : UserInputUtil . GENERATE_PD_CONTRACT_DESCRIPTION , data : 'private' } ) ; showLanguagesQuickPickStub . resolves ( { label : 'TypeScript' , type : LanguageType . CONTRACT } ) ; showInputBoxStub . onFirstCall ( ) . resolves ( 'Conga' ) ; showFolderOptionsStub . resolves ( UserInputUtil . OPEN_IN_NEW_WINDOW ) ; browseStub . resolves ( uri ) ; await vscode . commands . executeCommand ( ExtensionCommands . CREATE_SMART_CONTRACT_PROJECT ) ; executeCommandStub . should . have . been . calledThrice ; executeCommandStub . should . have . been . calledWith ( 'workbench.files.action.focusFilesExplorer' ) ; executeCommandStub . should . have . been . calledWith ( 'vscode.openFolder' , uri , true ) ; logSpy . should . have . been . calledWith ( LogType . SUCCESS , 'Successfully generated Private Data Smart Contract Project' ) ; sendTelemetryEventStub . should . have . been . calledOnceWithExactly ( 'createSmartContractProject' , { contractLanguage : 'typescript' , contractType : 'private' } ) ; } ) ; it ( 'should have the correct default asset name when user selects private data smart contract' , async ( ) => { showQuickPickItemStub . onFirstCall ( ) . resolves ( { label : UserInputUtil . GENERATE_PD_CONTRACT , description : UserInputUtil . GENERATE_PD_CONTRACT_DESCRIPTION , data : 'private' } ) ; showLanguagesQuickPickStub . resolves ( { label : 'TypeScript' , type : LanguageType . CONTRACT } ) ; showInputBoxStub . onFirstCall ( ) . resolves ( 'MyPrivateAsset' ) ; showFolderOptionsStub . resolves ( UserInputUtil . OPEN_IN_NEW_WINDOW ) ; browseStub . resolves ( uri ) ; await vscode . commands . executeCommand ( ExtensionCommands . CREATE_SMART_CONTRACT_PROJECT ) ; inputBoxSpy . should . have . been . calledWith ( 'Name the type of asset managed by this smart contract' , 'MyPrivateAsset' ) ; showLanguagesQuickPickStub . should . have . been . calledWith ( sinon . match . string , [ ] , sinon . match . array ) ; executeCommandStub . should . have . been . calledThrice ; executeCommandStub . should . have . been . calledWith ( 'workbench.files.action.focusFilesExplorer' ) ; executeCommandStub . should . have . been . calledWith ( 'vscode.openFolder' , uri , true ) ; logSpy . should . have . been . calledWith ( LogType . SUCCESS , 'Successfully generated Private Data Smart Contract Project' ) ; sendTelemetryEventStub . should . have . been . calledOnceWithExactly ( 'createSmartContractProject' , { contractLanguage : 'typescript' , contractType : 'private' } ) ; } ) ; it ( 'should not report an error when focusFilesExplorer throws an error' , async ( ) => { executeCommandStub . withArgs ( 'workbench.files.action.focusFilesExplorer' ) . rejects ( new Error ( 'unsupported' ) ) ; showQuickPickItemStub . onFirstCall ( ) . resolves ( { label : UserInputUtil . GENERATE_PD_CONTRACT , description : UserInputUtil . GENERATE_PD_CONTRACT_DESCRIPTION , data : 'private' } ) ; showLanguagesQuickPickStub . resolves ( { label : 'TypeScript' , type : LanguageType . CONTRACT } ) ; showInputBoxStub . onFirstCall ( ) . resolves ( 'MyPrivateAsset' ) ; showFolderOptionsStub . resolves ( UserInputUtil . OPEN_IN_NEW_WINDOW ) ; browseStub . resolves ( uri ) ; await vscode . commands . executeCommand ( ExtensionCommands . CREATE_SMART_CONTRACT_PROJECT ) ; logSpy . should . have . been . calledWith ( LogType . SUCCESS , 'Successfully generated Private Data Smart Contract Project' ) ; logSpy . should . not . have . been . calledWithExactly ( LogType . ERROR , sinon . match . string , sinon . match . string ) ; } ) ; it ( 'should not do anything if the user cancels the type of smart contract' , async ( ) => { showQuickPickItemStub . onFirstCall ( ) . resolves ( undefined ) ; await vscode . commands . executeCommand ( ExtensionCommands . CREATE_SMART_CONTRACT_PROJECT ) ; showQuickPickItemStub . should . have . been . calledOnce ; browseStub . should . not . have . been . called ; showLanguagesQuickPickStub . should . not . have . been . called ; showInputBoxStub . should . not . have . been . called ; sendTelemetryEventStub . should . not . have . been . called ; } ) ; it ( 'should show error message if we fail to create a smart contract' , async ( ) => { mySandBox . stub ( YeomanUtil , 'run' ) . rejects ( new Error ( 'such error' ) ) ; showLanguagesQuickPickStub . resolves ( { label : 'JavaScript' , type : LanguageType . CONTRACT } ) ; showInputBoxStub . onFirstCall ( ) . resolves ( 'Conga' ) ; showFolderOptionsStub . resolves ( UserInputUtil . OPEN_IN_NEW_WINDOW ) ; browseStub . resolves ( uri ) ; await vscode . commands . executeCommand ( ExtensionCommands . CREATE_SMART_CONTRACT_PROJECT ) ; logSpy . should . have . been . calledWith ( LogType . ERROR , 'Issue creating Smart Contract Project: such error' ) ; sendTelemetryEventStub . should . not . have . been . called ; } ) ; it ( 'should not do anything if the user cancels the open dialog' , async ( ) => { showLanguagesQuickPickStub . resolves ( { label : 'JavaScript' , type : LanguageType . CONTRACT } ) ; showInputBoxStub . onFirstCall ( ) . resolves ( 'Conga' ) ; browseStub . resolves ( ) ; await vscode . commands . executeCommand ( ExtensionCommands . CREATE_SMART_CONTRACT_PROJECT ) ; browseStub . should . have . been . calledOnce ; executeCommandStub . should . have . been . calledOnce ; executeCommandStub . should . have . not . been . calledWith ( 'vscode.openFolder' ) ; } ) ; it ( 'should throw an error if the chosen folder has an invalid name' , async ( ) => { showLanguagesQuickPickStub . resolves ( { label : 'JavaScript' , type : LanguageType . CONTRACT } ) ; showInputBoxStub . onFirstCall ( ) . resolves ( 'Conga' ) ; const badUri = vscode . Uri . file ( ' Invalid Directory! ' ) ; browseStub . resolves ( badUri ) ; await vscode . commands . executeCommand ( ExtensionCommands . CREATE_SMART_CONTRACT_PROJECT ) ; browseStub . should . have . been . calledOnce ; executeCommandStub . should . have . been . calledOnce ; executeCommandStub . should . have . not . been . calledWith ( 'vscode.openFolder' ) ; logSpy . should . have . been . calledWith ( LogType . ERROR , `Please choose a folder which only includes alphanumeric, "_" and "-" characters.` ) ; } ) ; it ( 'should not do anything if the user cancels the open project ' , async ( ) => { showLanguagesQuickPickStub . resolves ( { label : 'JavaScript' , type : LanguageType . CONTRACT } ) ; showInputBoxStub . onFirstCall ( ) . resolves ( 'Conga' ) ; showFolderOptionsStub . resolves ( ) ; browseStub . resolves ( uri ) ; await vscode . commands . executeCommand ( ExtensionCommands . CREATE_SMART_CONTRACT_PROJECT ) ; browseStub . should . have . been . calledOnce ; executeCommandStub . should . have . been . calledOnce ; executeCommandStub . should . have . not . been . calledWith ( 'vscode.openFolder' ) ; } ) ; it ( 'should not do anything if the user cancels choosing a smart contract language' , async ( ) => { showLanguagesQuickPickStub . resolves ( undefined ) ; await vscode . commands . executeCommand ( ExtensionCommands . CREATE_SMART_CONTRACT_PROJECT ) ; showLanguagesQuickPickStub . should . have . been . calledOnce ; showQuickPickItemStub . should . have . been . calledOnce ; showInputBoxStub . should . not . have . been . called ; browseStub . should . not . have . been . called ; } ) ; it ( 'should not do anything if the user cancels specifying an asset type' , async ( ) => { showLanguagesQuickPickStub . resolves ( { label : 'JavaScript' , type : LanguageType . CONTRACT } ) ; showInputBoxStub . onCall ( 0 ) . resolves ( undefined ) ; await vscode . commands . executeCommand ( ExtensionCommands . CREATE_SMART_CONTRACT_PROJECT ) ; showInputBoxStub . should . have . been . calledOnce ; showQuickPickItemStub . should . have . been . calledOnce ; browseStub . should . not . have . been . called ; } ) ; it ( 'should throw an error if the user specifies an invalid asset type' , async ( ) => { showLanguagesQuickPickStub . resolves ( { label : 'JavaScript' , type : LanguageType . CONTRACT } ) ; showInputBoxStub . onCall ( 0 ) . resolves ( '@xyz/myAsset' ) ; await vscode . commands . executeCommand ( ExtensionCommands . CREATE_SMART_CONTRACT_PROJECT ) ; showQuickPickItemStub . should . have . been . calledOnce ; browseStub . should . not . have . been . called ; logSpy . should . have . been . calledWith ( LogType . ERROR , 'Invalid asset name, it should only contain lowercase and uppercase letters.' ) ; } ) ; } ) ;