This commit is contained in:
Chris Segura 2019-06-24 10:52:28 -07:00
Родитель f7ded2d574
Коммит 6b42c802be
84 изменённых файлов: 6794 добавлений и 568 удалений

1
.gitignore поставляемый
Просмотреть файл

@ -3,6 +3,7 @@
### Common
out/
coverage/
### Node template
package-lock.json

Просмотреть файл

@ -1,11 +1,21 @@
.idea/**
.vscode/**
.vscode-test/**
out/test/**
coverage/**
drizzleUI/**
node_modules/**
out/**
!out/src/mscorlib.js
!out/src/Nethereum.Generators.DuoCode.js
!out/src/extension.js
src/**
test/**
.editorconfig
.gitignore
vsc-extension-quickstart.md
.npmignore
**/tsconfig.json
**/tslint.json
**/*.map
**/*.ts
node_modules/**
**/webpack.config.js
**/coverconfig.json

Просмотреть файл

@ -4,6 +4,18 @@ All notable changes to the "azureblockchain" extension will be documented in thi
## 0.1.5
- Backend test coverage
- Cleanup of packaging output, optimization
- Truffle installation fails on new installs fix
- Improved support for multiple ganache instances
- Preflight validation for ABS deployments
- Cleanup of code generation output
- Ganache commands cleanup
- Add command to export private key from mnemonic
- Better error handling ABS deployments
## 0.1.4
- bug fixes

15
coverconfig.json Normal file
Просмотреть файл

@ -0,0 +1,15 @@
{
"enabled": true,
"relativeSourcePath": "../src",
"relativeCoverageDir": "../../coverage",
"ignorePatterns": [
"**/node_modules/**"
],
"includePid": false,
"reports": [
"html",
"lcov",
"text-summary"
],
"verbose": false
}

Просмотреть файл

@ -5,7 +5,7 @@
"publisher": "AzBlockchain",
"preview": true,
"icon": "images/blockchain-service-logo.png",
"version": "0.1.4",
"version": "0.1.5",
"repository": {
"type": "git",
"url": "https://github.com/Microsoft/vscode-azure-blockchain-ethereum"
@ -30,7 +30,7 @@
"Programming Languages"
],
"aikey": "INSERTAIKEY",
"main": "./out/extension.js",
"main": "./out/src/extension.js",
"activationEvents": [
"onView:AzureBlockchain",
"onCommand:azureBlockchainService.showWelcomePage",
@ -146,6 +146,11 @@
"command": "azureBlockchainService.stopGanacheServer",
"title": "Stop Ganache Server",
"category": "Azure Blockchain"
},
{
"command": "azureBlockchainService.getPrivateKey",
"title": "Retrieve private key",
"category": "Azure Blockchain"
}
],
"menus": {
@ -230,12 +235,12 @@
},
{
"command": "azureBlockchainService.startGanacheServer",
"when": "view == AzureBlockchain && viewItem == localconsortium && azureBlockchainService:isGanacheRunning == false",
"when": "view == AzureBlockchain && viewItem == localconsortium",
"group": "azureBlockchain-1@0"
},
{
"command": "azureBlockchainService.stopGanacheServer",
"when": "view == AzureBlockchain && viewItem == localconsortium && azureBlockchainService:isGanacheRunning == true",
"when": "view == AzureBlockchain && viewItem == localconsortium",
"group": "azureBlockchain-1@0"
},
{
@ -324,22 +329,36 @@
"webpack:prod": "webpack --mode production",
"watch": "tsc -watch -p ./",
"postinstall": "node ./node_modules/vscode/bin/install",
"tslint": "tslint -t verbose src/**/*.ts test/**/*.ts --exclude src/**/*.d.ts",
"tslint": "tslint -t verbose src/**/*.ts test/**/*.ts",
"tslint:fix": "npm run tslint -- --fix",
"version": "tsc -v",
"test": "npm run compile && node ./node_modules/vscode/bin/test"
},
"devDependencies": {
"@machinomy/types-ethereumjs-wallet": "0.0.12",
"@types/astring": "^1.3.0",
"@types/estree": "^0.0.39",
"@types/fs-extra": "^5.0.5",
"@types/hdkey": "^0.7.0",
"@types/istanbul": "^0.4.30",
"@types/mocha": "^5.2.6",
"@types/node": "^10.12.21",
"@types/request": "^2.48.1",
"@types/request-promise": "^4.1.44",
"@types/semver": "^6.0.0",
"@types/uuid": "^3.4.4",
"@types/glob": "^7.1.1",
"@types/rewire": "^2.5.28",
"@types/sinon": "^7.0.11",
"copy-webpack-plugin": "^5.0.3",
"decache": "^4.5.1",
"sinon": "^7.3.2",
"husky": "^2.2.0",
"glob": "^7.1.4",
"istanbul": "^0.4.5",
"lint-staged": "^8.1.6",
"remap-istanbul": "^0.13.0",
"rewire": "^4.0.1",
"ts-loader": "^5.4.3",
"tslint": "^5.12.1",
"tslint-microsoft-contrib": "^6.1.1",
@ -354,9 +373,13 @@
"astring": "^1.4.0",
"azure-arm-resource": "^7.3.0",
"bip39": "^3.0.1",
"ethereumjs-wallet": "^0.6.3",
"fs-extra": "^7.0.1",
"hdkey": "^1.1.1",
"ms-rest": "^2.5.0",
"ms-rest-azure": "^2.6.0",
"request": "^2.88.0",
"request-promise": "^4.2.4",
"semver": "^6.0.0",
"uuid": "^3.3.2",
"vscode-azureextensionui": "^0.20.5",

Просмотреть файл

@ -4,13 +4,14 @@
import { HttpMethods, IncomingMessage, ServiceClientCredentials, ServiceError, WebResource } from 'ms-rest';
import { AzureServiceClient, AzureServiceClientOptions, UserTokenCredentials } from 'ms-rest-azure';
import * as uuid from 'uuid';
import { env, Uri, window } from 'vscode';
import { Uri, window } from 'vscode';
import { Constants } from '../Constants';
import { vscodeEnvironment} from '../helpers';
import { Output } from '../Output';
import { ConsortiumResource } from './Operations/ConsortiumResource';
import { MemberResource } from './Operations/MemberResource';
import { TransactionNodeResource } from './Operations/TransactionNodeResource';
import { SkuResource } from './Operations/SkuResources';
import { TransactionNodeResource } from './Operations/TransactionNodeResource';
export class AzureBlockchainServiceClient extends AzureServiceClient {
public memberResource: MemberResource;
@ -20,19 +21,21 @@ export class AzureBlockchainServiceClient extends AzureServiceClient {
constructor(
credentials: ServiceClientCredentials | UserTokenCredentials,
private readonly subscriptionId: string,
private readonly resourceGroup: string,
private readonly baseUri: string,
private readonly apiVersion: string,
private readonly options: AzureServiceClientOptions,
public readonly subscriptionId: string,
public readonly resourceGroup: string,
public readonly location: string,
public readonly baseUri: string,
public readonly apiVersion: string,
public readonly options: AzureServiceClientOptions,
) {
super(credentials, options);
if (credentials === null || credentials === undefined) {
throw new Error('\'credentials\' cannot be null.');
if (!credentials) {
throw new Error(Constants.errorMessageStrings.VariableDoesNotExist(Constants.serviceClientVariables.credentials));
}
if (subscriptionId === null || subscriptionId === undefined) {
throw new Error('\'subscriptionId\' cannot be null.');
if (!subscriptionId) {
throw new Error(
Constants.errorMessageStrings.VariableDoesNotExist(Constants.serviceClientVariables.subscriptionId));
}
const packageInfo = this.getPackageJsonInfo(__dirname);
@ -48,15 +51,24 @@ export class AzureBlockchainServiceClient extends AzureServiceClient {
const urlDetailsOfConsortium = `subscriptions/${this.subscriptionId}/resourceGroups/${this.resourceGroup}/` +
`providers/Microsoft.Blockchain/blockchainMembers/${memberName}`;
const url = `${this.baseUri}/${urlDetailsOfConsortium}?api-version=${this.apiVersion}`;
const httpRequest = this._getHttpRequest(url, 'PUT', body);
const httpRequest = this.getHttpRequest(url, 'PUT', body);
// @ts-ignore
await this.pipeline(httpRequest, (err: ServiceError) => {
await this.pipeline(httpRequest, (err: ServiceError, response: IncomingMessage, responseBody: string) => {
if (err) {
Output.outputLine(Constants.outputChannel.azureBlockchainServiceClient, err.message);
}
} else if (response.statusCode! < 200 || response.statusCode! > 299) {
Output.outputLine(
Constants.outputChannel.azureBlockchainServiceClient,
`${response.statusMessage}(${response.statusCode}): ${responseBody}`,
);
env.openExternal(Uri.parse(`${Constants.azureResourceExplorer.portalBasUri}/resource/${urlDetailsOfConsortium}`));
window.showErrorMessage(Constants.executeCommandMessage.failedToRunCommand('CreateConsortium'));
} else {
vscodeEnvironment.openExternal(
Uri.parse(`${Constants.azureResourceExplorer.portalBasUri}/resource/${urlDetailsOfConsortium}`),
);
}
});
}
@ -66,9 +78,9 @@ export class AzureBlockchainServiceClient extends AzureServiceClient {
const url = `${this.baseUri}/subscriptions/${this.subscriptionId}/resourceGroups/${this.resourceGroup}` +
`/providers/Microsoft.Blockchain/blockchainMembers?api-version=${this.apiVersion}`;
const httpRequest = this._getHttpRequest(url, 'GET');
const httpRequest = this.getHttpRequest(url, 'GET');
return this._sendRequestToAzure(httpRequest, callback);
return this.sendRequestToAzure(httpRequest, callback);
}
public getTransactionNodes(
@ -79,9 +91,9 @@ export class AzureBlockchainServiceClient extends AzureServiceClient {
const url = `${this.baseUri}/subscriptions/${this.subscriptionId}/resourceGroups/${this.resourceGroup}/` +
`providers/Microsoft.Blockchain/blockchainMembers/${memberName}/transactionNodes?api-version=${this.apiVersion}`;
const httpRequest = this._getHttpRequest(url, 'GET');
const httpRequest = this.getHttpRequest(url, 'GET');
return this._sendRequestToAzure(httpRequest, callback);
return this.sendRequestToAzure(httpRequest, callback);
}
public getMemberAccessKeys(
@ -91,9 +103,9 @@ export class AzureBlockchainServiceClient extends AzureServiceClient {
const url = `${this.baseUri}/subscriptions/${this.subscriptionId}/resourceGroups/${this.resourceGroup}/` +
`providers/Microsoft.Blockchain/blockchainMembers/${memberName}/listApikeys?api-version=${this.apiVersion}`;
const httpRequest = this._getHttpRequest(url, 'POST');
const httpRequest = this.getHttpRequest(url, 'POST');
return this._sendRequestToAzure(httpRequest, callback);
return this.sendRequestToAzure(httpRequest, callback);
}
public getSkus(callback: (error: Error | null, result?: any) => void,
@ -101,17 +113,17 @@ export class AzureBlockchainServiceClient extends AzureServiceClient {
const url = `${this.baseUri}/subscriptions/${this.subscriptionId}` +
`/providers/Microsoft.Blockchain/skus?api-version=${this.apiVersion}`;
const httpRequest = this._getHttpRequest(url, 'GET');
const httpRequest = this.getHttpRequest(url, 'GET');
return this._sendRequestToAzure(httpRequest, callback);
return this.sendRequestToAzure(httpRequest, callback);
}
private async _sendRequestToAzure(
public async sendRequestToAzure(
httpRequest: WebResource,
callback: (error: Error | null, result?: any) => void,
): Promise<void> {
// @ts-ignore
return this.pipeline(httpRequest, (err: ServiceError, response: IncomingMessage, responseBody: string) => {
return this.pipeline(httpRequest, (err: ServiceError | null, response: IncomingMessage, responseBody: string) => {
if (err) {
window.showErrorMessage(err.message);
return callback(err);
@ -131,18 +143,18 @@ export class AzureBlockchainServiceClient extends AzureServiceClient {
});
}
private _getHttpRequest(url: string, method: HttpMethods, body?: string): WebResource {
public getHttpRequest(url: string, method: HttpMethods, body?: string): WebResource {
const httpRequest = new WebResource();
httpRequest.method = method;
httpRequest.url = url;
httpRequest.headers = {};
httpRequest.headers['Content-Type'] = 'application/json';
if (this.options.generateClientRequestId) {
httpRequest.headers['Content-Type'] = Constants.azureResourceExplorer.contentType;
if (this.options && this.options.generateClientRequestId) {
httpRequest.headers['x-ms-client-request-id'] = uuid.v4();
}
if (this.options.acceptLanguage) {
if (this.options && this.options.acceptLanguage) {
httpRequest.headers['accept-language'] = this.options.acceptLanguage;
}
@ -150,4 +162,33 @@ export class AzureBlockchainServiceClient extends AzureServiceClient {
return httpRequest;
}
public async checkExistence(name: string, type: string): Promise<{
message: string | null,
nameAvailable: boolean,
reason: string,
}> {
const requestUrl = `${this.baseUri}/subscriptions/${this.subscriptionId}` +
`/providers/Microsoft.Blockchain/locations/${this.location}` +
`/checkNameAvailability?api-version=${this.apiVersion}`;
const request = this.getHttpRequest(
requestUrl,
'POST',
JSON.stringify({
name,
type,
}),
);
return new Promise((resolve, reject) => {
this.sendRequestToAzure(request, (err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
}
}

Просмотреть файл

@ -0,0 +1,6 @@
export interface ISkuDto {
resourceType: string;
name: string;
tier: string;
locations: string[];
}

Просмотреть файл

@ -5,7 +5,7 @@ import { AzureBlockchainServiceClient } from '../AzureBlockchainServiceClient';
import { ConsortiumMapper, ICreateQuorumMember } from '../Mapper/ConsortiumMapper';
export class ConsortiumResource {
constructor(public readonly client: AzureBlockchainServiceClient) {}
constructor(private readonly client: AzureBlockchainServiceClient) {}
public createConsortium(memberName: string, bodyParams: ICreateQuorumMember): Promise<void> {
const body = ConsortiumMapper.getBodyForCreateQuorumMember(bodyParams);
@ -13,4 +13,12 @@ export class ConsortiumResource {
// TODO: need receive result
return this.client.createConsortium(memberName, JSON.stringify(body));
}
public async checkExistence(name: string): Promise<{
message: string | null,
nameAvailable: boolean,
reason: string,
}> {
return this.client.checkExistence(name, 'Microsoft.Blockchain/consortiums');
}
}

Просмотреть файл

@ -6,7 +6,7 @@ import { AzureBlockchainServiceClient } from '../AzureBlockchainServiceClient';
import { IAzureMemberAccessKeysDto } from '../AzureDto/AccessKeysDto';
export class MemberResource {
constructor(public readonly client: AzureBlockchainServiceClient) {}
constructor(private readonly client: AzureBlockchainServiceClient) {}
public getListMember(): Promise<IAzureMemberDto[]> {
return new Promise((resolve, reject) => {
@ -34,4 +34,12 @@ export class MemberResource {
});
});
}
public async checkExistence(name: string): Promise<{
message: string | null,
nameAvailable: boolean,
reason: string,
}> {
return await this.client.checkExistence(name, 'Microsoft.Blockchain/blockchainMembers');
}
}

Просмотреть файл

@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { ISkuDto } from '..';
import { AzureBlockchainServiceClient } from '../AzureBlockchainServiceClient';
export class SkuResource {
constructor(public readonly client: AzureBlockchainServiceClient) {}
public getListSkus(): Promise<ISkuDto[]> {
return new Promise((resolve, reject) => {
return this.client.getSkus((error: Error | null, result?: any) => {
if (error) {
reject(error);
} else {
resolve(Object.assign([], result.value));
}
});
});
}
}

Просмотреть файл

@ -1,9 +1,13 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { AzureBlockchainServiceClient } from './AzureBlockchainServiceClient';
import { IAzureConsortiumDto } from './AzureDto/ConsortiumDto';
import { IAzureMemberDto } from './AzureDto/MemberDto';
import { ISkuDto } from './AzureDto/SkuDto';
import { IAzureTransactionNodeDto } from './AzureDto/TransactionNodeDto';
import { ICreateQuorumMember } from './Mapper/ConsortiumMapper';
import { ConsortiumResource } from './Operations/ConsortiumResource';
import { MemberResource } from './Operations/MemberResource';
import { SkuResource } from './Operations/SkuResources';
@ -14,6 +18,7 @@ export {
IAzureTransactionNodeDto,
ICreateQuorumMember,
ISkuDto,
ConsortiumResource,
MemberResource,
SkuResource,
};

Просмотреть файл

@ -1,8 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { env, window } from 'vscode';
import { window } from 'vscode';
import { Constants } from './Constants';
import { vscodeEnvironment } from './helpers';
import { ConsortiumView } from './ViewItems';
export namespace AzureBlockchain {
@ -13,7 +14,7 @@ export namespace AzureBlockchain {
export async function addDataInClipboard(typeOfData: string, data?: string | null) {
if (data) {
await env.clipboard.writeText(data);
await vscodeEnvironment.writeToClipboard(data);
window.showInformationMessage(typeOfData + Constants.dataCopied);
}
}

Просмотреть файл

@ -5,7 +5,15 @@ import { ProgressLocation, QuickPickItem, window } from 'vscode';
import { AzureBlockchainServiceClient, IAzureMemberDto, ICreateQuorumMember, ISkuDto } from './ARMBlockchain';
import { Constants } from './Constants';
import { showInputBox, showQuickPick } from './helpers';
import { AzureConsortium, Member, ResourceGroupItem, SkuItem, SubscriptionItem, TransactionNode, LocationItem } from './Models';
import {
AzureConsortium,
LocationItem,
Member,
ResourceGroupItem,
SkuItem,
SubscriptionItem,
TransactionNode,
} from './Models';
import { ConsortiumItem } from './Models/ConsortiumItem';
import { ResourceExplorerAndGenerator } from './ResourceExplorerAndGenerator';
import { AzureBlockchainServiceValidator } from './validators/AzureBlockchainServiceValidator';
@ -34,22 +42,23 @@ export class ConsortiumResourceExplorer extends ResourceExplorerAndGenerator {
throw new Error(Constants.errorMessageStrings.NoSubscriptionFoundClick);
}
const client = await this.getClient(
const azureClient = await this.getAzureClient(
new SubscriptionItem(consortium.label, subscriptionId, subscription.session),
new ResourceGroupItem(resourceGroup),
);
const accessKeys = await client.memberResource.getMemberAccessKeys(consortium.getMemberName());
const accessKeys = await azureClient.memberResource.getMemberAccessKeys(consortium.getMemberName());
return accessKeys.keys.map((key) => key.value);
}
private async getClient(subscriptionItem: SubscriptionItem, resourceGroupItem: ResourceGroupItem)
private async getAzureClient(subscriptionItem: SubscriptionItem, resourceGroupItem: ResourceGroupItem)
: Promise<AzureBlockchainServiceClient> {
return new AzureBlockchainServiceClient(
subscriptionItem.session.credentials,
subscriptionItem.subscriptionId,
resourceGroupItem.label,
resourceGroupItem.description,
Constants.azureResourceExplorer.requestBaseUri,
Constants.azureResourceExplorer.requestApiVersion,
{
@ -101,8 +110,8 @@ export class ConsortiumResourceExplorer extends ResourceExplorerAndGenerator {
resourceGroupItem: ResourceGroupItem,
excludedItems: string[] = [],
): Promise<ConsortiumItem[]> {
const client = await this.getClient(subscriptionItem, resourceGroupItem);
const members: IAzureMemberDto[] = await client.memberResource.getListMember();
const azureClient = await this.getAzureClient(subscriptionItem, resourceGroupItem);
const members: IAzureMemberDto[] = await azureClient.memberResource.getListMember();
return members
.map((member) => new ConsortiumItem(
member.properties.consortium,
@ -120,11 +129,13 @@ export class ConsortiumResourceExplorer extends ResourceExplorerAndGenerator {
subscriptionItem: SubscriptionItem,
resourceGroupItem: ResourceGroupItem,
): Promise<AzureConsortium> {
const client = await this.getClient(subscriptionItem, resourceGroupItem);
const transactionNodes = await client.transactionNodeResource.getListTransactionNode(consortiumItems.memberName);
const azureClient = await this.getAzureClient(subscriptionItem, resourceGroupItem);
const transactionNodes = await azureClient
.transactionNodeResource
.getListTransactionNode(consortiumItems.memberName);
const memberItem = new Member(consortiumItems.memberName);
const azureConsortium = new AzureConsortium(
const azureConsortium = new AzureConsortium(
consortiumItems.consortiumName,
consortiumItems.subscriptionId,
consortiumItems.resourcesGroup,
@ -144,7 +155,7 @@ export class ConsortiumResourceExplorer extends ResourceExplorerAndGenerator {
private async getSkus(
client: AzureBlockchainServiceClient,
location: LocationItem)
: Promise<SkuItem[]> {
: Promise<SkuItem[]> {
const skus: ISkuDto[] = await client.skuResource.getListSkus();
const skuItems: SkuItem[] = [];
@ -160,18 +171,32 @@ export class ConsortiumResourceExplorer extends ResourceExplorerAndGenerator {
private async createAzureConsortium(subscriptionItem: SubscriptionItem, resourceGroupItem: ResourceGroupItem)
: Promise<AzureConsortium> {
const client = await this.getClient(subscriptionItem, resourceGroupItem);
const azureClient = await this.getAzureClient(subscriptionItem, resourceGroupItem);
const { consortiumResource, memberResource } = azureClient;
const consortiumName = await showInputBox({
ignoreFocusOut: true,
prompt: Constants.paletteWestlakeLabels.enterConsortiumName,
validateInput: AzureBlockchainServiceValidator.validateNames,
validateInput: async (name) => {
return await window.withProgress({
location: ProgressLocation.Notification,
title: Constants.informationMessage.consortiumNameValidating,
}, async () => {
return await AzureBlockchainServiceValidator.validateConsortiumName(name, consortiumResource);
});
},
});
const memberName = await showInputBox({
ignoreFocusOut: true,
prompt: Constants.paletteWestlakeLabels.enterConsortiumMemberName,
validateInput: AzureBlockchainServiceValidator.validateNames,
validateInput: async (name) => {
return await window.withProgress({
location: ProgressLocation.Notification,
title: Constants.informationMessage.memberNameValidating,
},
async () => await AzureBlockchainServiceValidator.validateMemberName(name, memberResource));
},
});
const protocol = await showQuickPick(
@ -202,7 +227,7 @@ export class ConsortiumResourceExplorer extends ResourceExplorerAndGenerator {
);
const sku = await showQuickPick(
this.getSkus(client, region),
this.getSkus(azureClient, region),
{ placeHolder: Constants.paletteWestlakeLabels.selectConsortiumSku, ignoreFocusOut: true },
);
@ -222,7 +247,7 @@ export class ConsortiumResourceExplorer extends ResourceExplorerAndGenerator {
location: ProgressLocation.Window,
title: Constants.statusBarMessages.creatingConsortium,
}, async () => {
await client.consortiumResource.createConsortium(memberName, bodyParams);
await azureClient.consortiumResource.createConsortium(memberName, bodyParams);
return new AzureConsortium(consortiumName, subscriptionItem.subscriptionId, resourceGroupItem.label, memberName);
});

Просмотреть файл

@ -21,8 +21,9 @@ export class Constants {
azureBlockchainServiceClient: 'Azure Blockchain Service Client',
consortiumTreeManager: 'Consortium Tree Manager',
executeCommand: 'Execute command',
ganacheCommands: 'Ganache Commands',
ganacheCommands: 'Ganache Server',
logicAppGenerator: 'Logic App Generator',
requirements: 'Requirements',
telemetryClient: 'Telemetry Client',
};
@ -32,11 +33,16 @@ export class Constants {
public static defaultTruffleBox = 'Azure-Samples/Blockchain-Ethereum-Template';
public static tempPath = 'tempPath';
public static defaultCounter = 10;
public static defaultDebounceTimeout = 300;
public static localhost = '127.0.0.1';
public static localhostName = 'localhost';
public static defaultLocalhostPort = 8545;
public static defaultAzureBSPort = 3200;
public static ganacheRetryTimeout = 2000; // milliseconds
public static ganacheRetryAttempts = 5;
public static requiredVersions: {[key: string]: string | { min: string, max: string }} = {
ganache: {
max: '7.0.0',
@ -97,6 +103,8 @@ export class Constants {
gasPrice: 100000000000,
};
public static consortiumTreeResourceKey = 'treeContent';
public static networkName = {
azure: 'Azure Blockchain Service',
local: 'Local Network',
@ -132,30 +140,37 @@ export class Constants {
};
public static validationRegexps = {
digits: /(?=.*\d)/g,
isLowerCase: /^[a-z0-9_\-!@$^&()+=?\/<>|[\]{}:.\\~ #`*"'%;,]+$/g,
isUrl: /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w.-]+)+[\w\-._~:/?#[\]@!$&'()*+,;=]+$/gm,
lowerCaseLetter: /(?=.*[a-z])/g,
// tslint:disable-next-line: max-line-length
port: /^([1-9]|[1-8][0-9]|9[0-9]|[1-8][0-9]{2}|9[0-8][0-9]|99[0-9]|[1-8][0-9]{3}|9[0-8][0-9]{2}|99[0-8][0-9]|999[0-9]|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/,
specialChars: /[!@$^&()+=?\/<>|[\]{}_:.\\~]/g,
unallowedChars: /[#`*"'\-%;,]/g,
digits: /(?=.*\d)/g,
lowerCaseLetter: /(?=.*[a-z])/g,
upperCaseLetter: /(?=.*[A-Z])/g,
};
public static validationMessages = {
incorrectHostAddress: 'Incorrect host address',
incorrectName: 'Incorrect name. Name can contain only lowercase letters and numbers. ' +
invalidAzureName: 'Invalid name. Name can contain only lowercase letters and numbers. ' +
'The first character must be a letter. The value must be between 2 and 20 characters long.',
incorrectPassword: 'Incorrect password. Password should contain 1 lower case character, ' +
'1 upper case character, 1 number, 1 special character that is NOT #, `, *, ", \', -, % or ;. ' +
'The value must be between 12 and 72 characters long.',
incorrectPortNumber: 'Incorrect port number',
invalidConfirmationResult: '\'yes\' or \'no\'',
lengthRange: 'Length must be between 12 and 72 characters',
invalidHostAddress: 'Invalid host address',
invalidPortNumber: 'Invalid port number',
invalidPort: 'Invalid port.',
invalidResourceGroupName: 'Resource group names only allow alphanumeric characters, periods,' +
'underscores, hyphens and parenthesis and cannot end in a period.',
lengthRange: Constants.getMessageLengthRange,
networkAlreadyExists: 'Network already exists.',
noDigits: 'Password should have at least one digit.',
noLowerCaseLetter: 'Password should have at least one lowercase letter from a to z.',
noSpecialChars: 'Password must have 1 special character.',
noUpperCaseLetter: 'Password should have at least one uppercase letter from A to Z.',
onlyLowerCaseAllowed: 'Only lower case allowed.',
onlyNumberAllowed: 'Value after \':\' should be a number.',
projectAlreadyExist: Constants.getMessageProjectAlreadyExist,
unallowedChars: "Password must not have '#', '`', '*', '\"', ''', '-', '%', ',' or ';' chars.",
portAlreadyInUse: 'This port is already in use. Choose another one.',
resourceGroupAlreadyExists: Constants.getMessageResourceGroupAlreadyExist,
unallowedChars: "Input value must not have '#', '`', '*', '\"', ''', '-', '%', ',', ';' or others chars.",
unallowedSymbols: 'Provided name has unallowed symbols.',
undefinedVariable: Constants.getMessageUndefinedVariable,
valueCannotBeEmpty: 'Value cannot be empty.',
@ -165,12 +180,15 @@ export class Constants {
public static placeholders = {
confirmPaidOperation: 'This operation will cost Ether, type \'yes\' to continue',
deployedUrlStructure: 'host:port',
emptyLineText: '<empty line>',
generateMnemonic: 'Generate mnemonic',
pasteMnemonic: 'Paste mnemonic',
resourceGroupName: 'Resource Group Name',
selectConsortium: 'Select consortium',
selectDeployDestination: 'Select deploy destination',
selectGanacheServer: 'Select Ganache server',
selectLedgerEventsDestination: 'Select ledger events destination',
selectMnemonicExtractKey: 'Select mnemonic to extract key',
selectNewProjectPath: 'Select new project path',
selectResourceGroup: 'Select a resource group',
selectRgLocation: 'Select a location to create your Resource Group in...',
@ -242,11 +260,17 @@ export class Constants {
public static dataCopied = ' copied to clipboard';
public static rpcEndpointAddress = 'RPCEndpointAddress';
public static rpcGanacheMethod = 'net_listening';
public static ganacheCommandStrings = {
cannotStartServer: 'Cannot start ganache server',
invalidGanachePort: 'Couldn\'t start Ganache server. Invalid port',
serverAlreadyRunning: 'Ganache server already running',
serverCanNotRunWithoutGanache: 'To start a local server, installed ganache-cli is required',
serverCanNotStop: 'Ganache stop server was failed because it was started from another application',
serverSuccessfullyRunning: 'Ganache server successfully running',
serverCanNotStop: 'Ganache stop server was failed because is not ganache application',
serverNoGanacheAvailable: 'No Ganache network settings available',
serverNoGanacheInstance: 'No Ganache instance running',
serverSuccessfullyStarted: 'Ganache server successfully started',
serverSuccessfullyStopped: 'Ganache server successfully stopped',
};
@ -262,25 +286,43 @@ export class Constants {
};
public static errorMessageStrings = {
// TODO names to lower case
ActionAborted: 'Action aborted',
BuildContractsBeforeGenerating: 'Please build contracts before generating',
BuildContractsDirIsEmpty: Constants.getMessageContractsBuildDirectoryIsEmpty,
BuildContractsDirIsNotExist: Constants.getMessageContractsBuildDirectoryIsNotExist,
DirectoryIsNotEmpty: 'Directory is not empty. Open another one?',
GitIsNotInstalled: 'Git is not installed',
InvalidContract: 'This file is not a valid contract.',
InvalidMnemonic: 'Invalid mnemonic',
InvalidServiceType: 'Invalid service type.',
MnemonicFileHaveNoText: 'Mnemonic file have no text',
NetworkAlreadyExist: Constants.getMessageNetworkAlreadyExist,
NewProjectCreationFailed: 'Command createProject has failed.',
NoSubscriptionFound: 'No subscription found.',
NoSubscriptionFoundClick: 'No subscription found, click an Azure account ' +
'at the bottom left corner and choose Select All',
RequiredAppsAreNotInstalled: 'To run command you should install required apps',
ThereAreNoMnemonics: 'There are no mnemonics',
TruffleConfigIsNotExist: 'Truffle configuration file not found',
VariableDoesNotExist: Constants.getMessageVariableDoesNotExist,
WaitForLogin: 'You should sign-in on Azure Portal',
WorkflowTypeDoesNotMatch: 'workflowType does not match any available workflows',
WorkspaceShouldBeOpened: 'Workspace should be opened',
};
public static informationMessage = {
cancelButton: 'Cancel',
consortiumDoesNotHaveMemberWithUrl: 'Consortium does not have member with url',
consortiumNameValidating: 'Consortium name validating...',
detailsButton: 'Details',
generatedLogicApp: 'Generated the logic app!',
invalidRequiredVersion: 'Required app is not installed or has an old version.',
memberNameValidating: 'Consortium Member name validating...',
newProjectCreationFinished: 'New project was created successfully',
newProjectCreationStarted: 'New project creation is started',
openButton: 'Open',
privateKeyWasCopiedToClipboard: 'Private key was copied to clipboard',
seeDetailsRequirementsPage: 'Please see details on the Requirements Page',
};
@ -300,7 +342,13 @@ export class Constants {
public static gitCommand = 'git';
public static truffleCommand = 'truffle';
public static serviceClientVariables = {
credentials: 'credentials',
subscriptionId: 'subscriptionId',
};
public static azureResourceExplorer = {
contentType: 'application/json',
portalBasUri: 'https://portal.azure.com/#@microsoft.onmicrosoft.com',
providerName: 'Microsoft.Blockchain',
requestAcceptLanguage: 'en-US',
@ -344,15 +392,35 @@ export class Constants {
+ 'Press Enter for default.';
}
private static getMessageProjectAlreadyExist(projectName: string): string {
return `A project with the same name: ${projectName} already exists. Please select other name`;
private static getMessageResourceGroupAlreadyExist(resourceGroupName: string): string {
return `A resource group with the same name: ${resourceGroupName} already exists. Please select other name`;
}
private static getMessageContractsBuildDirectoryIsEmpty(buildDirPath: string): string {
return `Contracts build directory "${buildDirPath}" is empty.`;
}
private static getMessageContractsBuildDirectoryIsNotExist(buildDirPath: string): string {
return `Contracts build directory "${buildDirPath}" is not exist.`;
}
private static getMessageNetworkAlreadyExist(networkName: string): string {
return `Network with name "${networkName}" already existed in truffle-config.js`;
}
private static getMessageUndefinedVariable(variable: string): string {
return `${variable} cannot be undefined`;
}
private static getMessageLengthRange(min: number, max: number): string {
return `Length must be between ${min} and ${max} characters`;
}
private static getMessageFailedCommand(command: string): string {
return `Failed to run command - ${command}. More details in output`;
}
private static getMessageVariableDoesNotExist(variable: string): string {
return `${variable} cannot be null.`;
}
}

Просмотреть файл

@ -0,0 +1,90 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { ChildProcess, spawn } from 'child_process';
import { OutputChannel, window } from 'vscode';
import { Constants } from '../Constants';
import { shell } from '../helpers';
import { UrlValidator } from '../validators/UrlValidator';
import { isGanacheServer, waitGanacheStarted } from './GanacheServiceClient';
export namespace GanacheService {
export interface IGanacheProcess {
process: ChildProcess;
output: OutputChannel;
}
export const ganacheProcesses: { [port: string]: IGanacheProcess } = {};
export async function startGanacheServer(port: number | string)
: Promise<IGanacheProcess | null> {
if (UrlValidator.validatePort(port)) {
throw new Error(`${Constants.ganacheCommandStrings.invalidGanachePort}: ${port}.`);
}
if (!isNaN(await shell.findPid(port))) {
if (await isGanacheServer(port)) {
return null;
} else {
throw new Error(Constants.ganacheCommandStrings.cannotStartServer);
}
}
const process = spawn('npx', ['ganache-cli', `-p ${port}`], { shell: true });
const output = window.createOutputChannel(`${Constants.outputChannel.ganacheCommands}:${port}`);
output.show();
process.stdout.on('data', (data: string | Buffer) => {
output.appendLine(data.toString());
});
process.stderr.on('data', (data: string | Buffer) => {
output.appendLine(data.toString());
});
process.on('close', () => {
delete ganacheProcesses[port];
output.dispose();
});
try {
await waitGanacheStarted(port, Constants.ganacheRetryAttempts);
} catch (e) {
process.removeAllListeners();
output.dispose();
shell.killPort(port);
throw e;
}
const ganacheProcess = { process, output };
ganacheProcesses[port] = ganacheProcess;
return ganacheProcess;
}
export async function stopGanacheServer(port: number | string): Promise<void> {
await shell.killPort(port);
const ganacheProcess = ganacheProcesses[port];
if (ganacheProcess) {
ganacheProcess.process.removeAllListeners();
ganacheProcess.output.dispose();
delete ganacheProcesses[port];
}
}
export async function dispose(): Promise<void[]> {
const shouldBeFree: Array<Promise<void>> = [];
Object.keys(ganacheProcesses).forEach((port) => {
delete ganacheProcesses[port];
shouldBeFree.push(shell.killPort(port));
});
return Promise.all(shouldBeFree);
}
export function getPortFromUrl(url: string): string {
const result = url.match(/(:\d{2,4})/);
return result ? result[0].slice(1) : Constants.defaultLocalhostPort.toString();
}
}

Просмотреть файл

@ -0,0 +1,44 @@
import * as rp from 'request-promise';
import { Constants } from '../Constants';
export async function isGanacheServer(port: number | string): Promise<boolean> {
try {
const response = await sendRPCRequest(port, Constants.rpcGanacheMethod);
return response && !!response.result || false;
} catch (error) {
return false;
}
}
export async function waitGanacheStarted(port: number | string, maxRetries: number = 1): Promise<void> {
const retry = async (retries: number) => {
if (retries < maxRetries) {
if (await isGanacheServer(port)) {
return;
}
await new Promise((resolve) => setTimeout(resolve, Constants.ganacheRetryTimeout));
await retry(retries + 1);
} else {
throw new Error(Constants.ganacheCommandStrings.cannotStartServer);
}
};
await retry(0);
}
export async function sendRPCRequest(
port: number | string,
methodName: string,
): Promise<{ result?: any } | undefined> {
return rp.post(
`http://localhost:${port}`,
{
body: {
jsonrpc: '2.0',
method: methodName,
params: [],
},
json: true,
})
.then((result) => result)
.catch(() => undefined);
}

Просмотреть файл

@ -1,11 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { mkdirp, readdir, readJson, writeFile } from 'fs-extra';
import * as fs from 'fs-extra';
import * as path from 'path';
import { QuickPickItem, Uri, window } from 'vscode';
import { Uri, window } from 'vscode';
import { Constants } from '../Constants';
import { getWorkspaceRoot } from '../helpers';
import { getWorkspaceRoot, TruffleConfiguration } from '../helpers';
import { showInputBox, showQuickPick } from '../helpers/userInteraction';
import { ResourceGroupItem, SubscriptionItem } from '../Models';
import { Output } from '../Output';
@ -13,252 +13,121 @@ import { ResourceExplorerAndGenerator } from '../ResourceExplorerAndGenerator';
import { buildContract } from './AbiDeserialiser';
import './Nethereum.Generators.DuoCode';
interface ILogicAppData {
contractAddress: string;
messagingType?: number;
outputDir: string;
resourceGroup: string;
serviceType: number;
subscriptionId: string;
topicName?: string;
workflowType: string;
}
export class LogicAppGenerator extends ResourceExplorerAndGenerator {
public async generateMicroservicesWorkflows(filePath: Uri | undefined): Promise<void> {
public async generateMicroservicesWorkflows(filePath?: Uri): Promise<void> {
return this.generateWorkflows(Constants.microservicesWorkflows.Service, filePath);
}
public async generateDataPublishingWorkflows(filePath: Uri | undefined): Promise<void> {
public async generateDataPublishingWorkflows(filePath?: Uri): Promise<void> {
return this.generateWorkflows(Constants.microservicesWorkflows.Data, filePath);
}
public async generateEventPublishingWorkflows(filePath: Uri | undefined): Promise<void> {
public async generateEventPublishingWorkflows(filePath?: Uri): Promise<void> {
return this.generateWorkflows(Constants.microservicesWorkflows.Messaging, filePath);
}
public async generateReportPublishingWorkflows(filePath: Uri | undefined): Promise<void> {
public async generateReportPublishingWorkflows(filePath?: Uri): Promise<void> {
return this.generateWorkflows(Constants.microservicesWorkflows.Reporting, filePath);
}
private async generateWorkflows(workflowType: string, filePath: Uri | undefined): Promise<void> {
const dirPath: string = path.join(getWorkspaceRoot(), 'build/contracts');
private async generateWorkflows(workflowType: string, filePath?: Uri): Promise<void> {
try {
const filePaths = await this.getContractsPath(filePath);
const logicAppData = await this.getLogicAppData(workflowType);
for (const file of filePaths) {
const contract = await fs.readJson(file, { encoding: 'utf8' });
const generatedFiles: any[] = this.getGenerator(contract, logicAppData).GenerateAll();
for (const generatedFile of generatedFiles) {
await this.writeFile(generatedFile);
}
}
window.showInformationMessage(Constants.informationMessage.generatedLogicApp);
} catch (err) {
window.showErrorMessage(err.toString());
}
}
private async getContractsPath(filePath?: Uri): Promise<string[]> {
const truffleConfigPath = TruffleConfiguration.getTruffleConfigUri();
const truffleConfig = new TruffleConfiguration.TruffleConfig(truffleConfigPath);
const configuration = truffleConfig.getConfiguration();
const buildDir = path.join(getWorkspaceRoot()!, configuration.contracts_build_directory);
const files: string[] = [];
if (!fs.pathExistsSync(buildDir)) {
throw new Error(Constants.errorMessageStrings.BuildContractsDirIsNotExist(buildDir));
}
if (filePath) {
const fileName = path.basename(filePath.fsPath);
const contractName = fileName.Remove(fileName.length - 4);
const compiledContractPath = path.join(dirPath, `${contractName}.json`);
const picks = this.getLogicAppItems(workflowType);
const serviceTypeSelection: string = (await showQuickPick(picks, { })).label;
const serviceType: int = this.getServiceTypeFromString(serviceTypeSelection);
const contractAddress: string = await showInputBox({ value: 'contract address' });
const [subscriptionItem, resourceGroupItem] = await this.selectSubscriptionAndResourceGroup();
readJson(compiledContractPath, {encoding: 'utf8'},
async (err2: Error, contents: any) => await this.handleContractJsonFile(
err2,
contents,
contractAddress,
subscriptionItem,
resourceGroupItem,
workflowType,
serviceType,
));
// TODO: what should we do if solidity file without constructor?
const contractName = path.basename(filePath.fsPath, Constants.contractExtension.sol);
files.push(`${contractName}${Constants.contractExtension.json}`);
} else {
readdir(dirPath, (err: Error, files: string[]) =>
this.iterateFilesInDirectory(err, files, dirPath, workflowType));
files.push(...await fs.readdir(buildDir));
}
const filePaths = files
.map((file) => path.join(buildDir, file))
.filter((file) => fs.lstatSync(file).isFile());
if (files.length === 0) {
throw new Error(
Constants.errorMessageStrings.BuildContractsDirIsEmpty(buildDir) + ' ' +
Constants.errorMessageStrings.BuildContractsBeforeGenerating,
);
}
return filePaths;
}
private async iterateFilesInDirectory(err: Error, files: string[], dirPath: string, workflowType: string) {
if (err) {
Output.outputLine(Constants.outputChannel.logicAppGenerator, err.toString());
return;
}
const picks = this.getLogicAppItems(workflowType);
const serviceTypeSelection: string = (await showQuickPick(picks, { })).label;
const serviceType: int = this.getServiceTypeFromString(serviceTypeSelection);
const contractAddress: string = await showInputBox({ value: 'contract address' });
private async getLogicAppData(workflowType: string): Promise<ILogicAppData> {
const serviceType = await this.getServiceType(workflowType);
const outputDir = await this.getOutputDir(serviceType);
const contractAddress = await showInputBox({ ignoreFocusOut: true, value: 'contract address' });
const [subscriptionItem, resourceGroupItem] = await this.selectSubscriptionAndResourceGroup();
files.forEach((file) => {
readJson(dirPath + file, {encoding: 'utf8'},
async (err2: Error, contents: any) => await this.handleContractJsonFile(
err2,
contents,
contractAddress,
subscriptionItem,
resourceGroupItem,
workflowType,
serviceType,
));
});
window.showInformationMessage('Generated the logic app!');
}
private getLogicAppItems(workflowType: string): QuickPickItem[] {
if (workflowType === Constants.microservicesWorkflows.Service) {
return [
{ label: Constants.logicApp.LogicApp },
{ label: Constants.logicApp.FlowApp },
{ label: Constants.logicApp.AzureFunction },
];
} else {
return [
{ label: Constants.logicApp.LogicApp },
{ label: Constants.logicApp.FlowApp },
];
}
}
private getServiceTypeFromString(serviceTypeSelection: string): int {
if (serviceTypeSelection === Constants.logicApp.LogicApp) {
return 1;
} else if (serviceTypeSelection === Constants.logicApp.FlowApp) {
return 0;
} else if (serviceTypeSelection === Constants.logicApp.AzureFunction) {
return 2;
} else {
throw new Error('Service type string not valid');
}
}
private async handleContractJsonFile(
err: Error,
contents: any,
contractAddress: string,
subscriptionItem: SubscriptionItem,
resourceGroupItem: ResourceGroupItem,
workflowType: string,
serviceType: int) {
if (err) {
Output.outputLine(Constants.outputChannel.logicAppGenerator, err.toString());
return;
}
await this.createLogicAppFromAbi(contents,
this.getOutputDir(serviceType),
const logicAppData: ILogicAppData = {
contractAddress,
subscriptionItem,
resourceGroupItem.description,
outputDir,
resourceGroup: resourceGroupItem.description,
serviceType,
subscriptionId: subscriptionItem.subscriptionId,
workflowType,
serviceType);
}
};
private async createLogicAppFromAbi(
contract: any,
dirPath: string,
contractAddress: string,
subscription: SubscriptionItem,
location: string,
workflowType: string,
serviceType: int) {
let generator;
if (workflowType === Constants.microservicesWorkflows.Service) {
generator = new Nethereum.Generators.ServiceWorkflow.ServiceWorkflowProjectGenerator(
buildContract(JSON.stringify(contract.abi)),
contract.contractName,
contract.bytecode,
contract.contractName,
contract.contractName + `.${workflowType}`,
dirPath,
'/',
serviceType,
0,
JSON.stringify(contract.abi),
contractAddress,
subscription.subscriptionId || String.Empty,
location,
'',
);
} else if (workflowType === Constants.microservicesWorkflows.Data) {
generator = new Nethereum.Generators.DataWorkflow.DataWorkflowProjectGenerator(
buildContract(JSON.stringify(contract.abi)),
contract.contractName,
contract.bytecode,
contract.contractName,
contract.contractName + `.${workflowType}`,
dirPath,
'/',
serviceType,
0,
contractAddress,
subscription.subscriptionId || String.Empty,
location,
JSON.stringify(contract.abi),
);
} else if (workflowType === Constants.microservicesWorkflows.Messaging) {
const topicName: string = await showInputBox({ value: 'topic name' });
const picks: QuickPickItem[] = [
{ label: 'Service Bus' },
{ label: 'Event Grid' },
];
const messagingType: string = (await showQuickPick(picks, {})).label;
generator = new Nethereum.Generators.MessagingWorkflow.MessagingWorkflowProjectGenerator(
buildContract(JSON.stringify(contract.abi)),
contract.contractName,
contract.bytecode,
contract.contractName,
contract.contractName + `.${workflowType}`,
dirPath,
'/',
serviceType,
0,
contractAddress,
subscription.subscriptionId || String.Empty,
location,
JSON.stringify(contract.abi),
topicName,
this.getMessagingType(messagingType),
);
} else if (workflowType === Constants.microservicesWorkflows.Reporting) {
generator = new Nethereum.Generators.ReportingWorkflow.ReportingWorkflowProjectGenerator(
buildContract(JSON.stringify(contract.abi)),
contract.contractName,
contract.bytecode,
contract.contractName,
contract.contractName + `.${workflowType}`,
dirPath,
'/',
serviceType,
0,
contractAddress,
subscription.subscriptionId || String.Empty,
location,
JSON.stringify(contract.abi),
);
}
if (generator) {
const files: any[] = generator.GenerateAll();
files.forEach(this.writeFile);
} else {
throw new Error('workflowType does not match any available workflows');
if (workflowType === Constants.microservicesWorkflows.Messaging) {
logicAppData.topicName = await showInputBox({ ignoreFocusOut: true, value: 'topic name' });
logicAppData.messagingType = await this.getMessagingType();
}
return logicAppData;
}
private getMessagingType(messagingType: string): any {
if (messagingType === 'Service Bus') {
return 1;
} else if (messagingType === 'Event Grid') {
return 0;
} else {
throw new Error('messaging type not defined');
}
}
private getGenerator(contract: any, logicAppData: ILogicAppData) {
const { Service, Data, Messaging, Reporting } = Constants.microservicesWorkflows;
private writeFile(file: Nethereum.Generators.Core.GeneratedFile): void {
const filePath = file.get_OutputFolder() + '/' + file.get_FileName();
mkdirp(path.dirname(filePath), (err: any) => {
if (err) { throw err; }
writeFile(filePath, file.get_GeneratedCode(), (err2: any) => {
if (err2) {
throw err2;
}
Output.outputLine(Constants.outputChannel.logicAppGenerator, 'Saved file to ' + filePath);
});
});
}
private getOutputDir(serviceType: int): string {
switch (serviceType) {
case 0:
return path.join(getWorkspaceRoot(), Constants.flowAppOutputDir);
case 1:
return path.join(getWorkspaceRoot(), Constants.logicAppOutputDir);
case 2:
return path.join(getWorkspaceRoot(), Constants.azureFunctionOutputDir);
switch (logicAppData.workflowType) {
case Service:
return this.getServiceWorkflowProjectGenerator(contract, logicAppData);
case Data:
return this.getDataWorkflowProjectGenerator(contract, logicAppData);
case Messaging:
return this.getMessagingWorkflowProjectGenerator(contract, logicAppData);
case Reporting:
return this.getReportingWorkflowProjectGenerator(contract, logicAppData);
default:
throw new Error('Invalid service type.');
throw new Error(Constants.errorMessageStrings.WorkflowTypeDoesNotMatch);
}
}
@ -270,4 +139,125 @@ export class LogicAppGenerator extends ResourceExplorerAndGenerator {
return [subscriptionItem, resourceGroupItem];
}
private async getServiceType(workflowType: string): Promise<number> {
const items = [
{ label: Constants.logicApp.LogicApp, serviceType: 1 },
{ label: Constants.logicApp.FlowApp, serviceType: 0 },
];
if (workflowType === Constants.microservicesWorkflows.Service) {
items.push({ label: Constants.logicApp.AzureFunction, serviceType: 2 });
}
const item = await showQuickPick(items, { ignoreFocusOut: true });
return item.serviceType;
}
private getOutputDir(serviceType: int): string {
switch (serviceType) {
case 0:
return path.join(getWorkspaceRoot()!, Constants.flowAppOutputDir);
case 1:
return path.join(getWorkspaceRoot()!, Constants.logicAppOutputDir);
case 2:
return path.join(getWorkspaceRoot()!, Constants.azureFunctionOutputDir);
default:
throw new Error(Constants.errorMessageStrings.InvalidServiceType);
}
}
private async getMessagingType(): Promise<number> {
const items = [
{ label: 'Service Bus', messagingType: 0 },
{ label: 'Event Grid', messagingType: 1 },
];
const item = await showQuickPick(items, { ignoreFocusOut: true });
return item.messagingType;
}
private getServiceWorkflowProjectGenerator(contract: any, logicAppData: ILogicAppData) {
return new Nethereum.Generators.ServiceWorkflow.ServiceWorkflowProjectGenerator(
buildContract(JSON.stringify(contract.abi)),
contract.contractName,
contract.bytecode,
contract.contractName,
`${contract.contractName}.${logicAppData.workflowType}`,
logicAppData.outputDir,
path.sep,
logicAppData.serviceType,
0,
JSON.stringify(contract.abi),
logicAppData.contractAddress,
logicAppData.subscriptionId,
logicAppData.resourceGroup,
'',
);
}
private getDataWorkflowProjectGenerator(contract: any, logicAppData: ILogicAppData) {
return new Nethereum.Generators.DataWorkflow.DataWorkflowProjectGenerator(
buildContract(JSON.stringify(contract.abi)),
contract.contractName,
contract.bytecode,
contract.contractName,
`${contract.contractName}.${logicAppData.workflowType}`,
logicAppData.outputDir,
path.sep,
logicAppData.serviceType,
0,
logicAppData.contractAddress,
logicAppData.subscriptionId,
logicAppData.resourceGroup,
JSON.stringify(contract.abi),
);
}
private getMessagingWorkflowProjectGenerator(contract: any, logicAppData: ILogicAppData) {
return new Nethereum.Generators.MessagingWorkflow.MessagingWorkflowProjectGenerator(
buildContract(JSON.stringify(contract.abi)),
contract.contractName,
contract.bytecode,
contract.contractName,
`${contract.contractName}.${logicAppData.workflowType}`,
logicAppData.outputDir,
path.sep,
logicAppData.serviceType,
0,
logicAppData.contractAddress,
logicAppData.subscriptionId,
logicAppData.resourceGroup,
JSON.stringify(contract.abi),
logicAppData.topicName!,
logicAppData.messagingType!,
);
}
private getReportingWorkflowProjectGenerator(contract: any, logicAppData: ILogicAppData) {
return new Nethereum.Generators.ReportingWorkflow.ReportingWorkflowProjectGenerator(
buildContract(JSON.stringify(contract.abi)),
contract.contractName,
contract.bytecode,
contract.contractName,
`${contract.contractName}.${logicAppData.workflowType}`,
logicAppData.outputDir,
path.sep,
logicAppData.serviceType,
0,
logicAppData.contractAddress,
logicAppData.subscriptionId,
logicAppData.resourceGroup,
JSON.stringify(contract.abi),
);
}
private async writeFile(file: Nethereum.Generators.Core.GeneratedFile): Promise<void> {
const filePath = path.join(file.get_OutputFolder(), file.get_FileName());
await fs.mkdirp(path.dirname(filePath));
await fs.writeFile(filePath, file.get_GeneratedCode());
Output.outputLine(Constants.outputChannel.logicAppGenerator, 'Saved file to ' + filePath);
}
}

Просмотреть файл

@ -16,11 +16,21 @@ export class MnemonicRepository {
return MnemonicRepository.globalState.get(Constants.mnemonicConstants.mnemonicStorage) as string[] || [];
}
public static getExistedMnemonicPaths(): string[] {
return MnemonicRepository.getAllMnemonicPaths().filter((path) => fs.existsSync(path));
}
public static saveMnemonicPath(filePath: string): void {
const storage = MnemonicRepository.globalState.get(Constants.mnemonicConstants.mnemonicStorage) as string[] || [];
storage.push(filePath);
MnemonicRepository.globalState.update(Constants.mnemonicConstants.mnemonicStorage, storage);
}
public static MaskMnemonic(mnemonic: string) {
return mnemonic
? `${mnemonic.slice(0, 3)} ... ${mnemonic.slice(-3)}`
: Constants.placeholders.emptyLineText;
}
private static globalState: Memento;
}

Просмотреть файл

@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { QuickPickItem } from 'vscode';
export class ConsortiumItem implements QuickPickItem {
public readonly consortiumName: string;
public readonly memberName: string;
public readonly resourcesGroup: string;
public readonly subscriptionId: string;
public readonly url?: string;
public readonly label: string;
constructor(
consortiumName: string,
subscriptionId: string,
resourcesGroup: string,
memberName: string,
url?: string,
) {
this.subscriptionId = subscriptionId;
this.resourcesGroup = resourcesGroup;
this.memberName = memberName;
this.url = url;
this.consortiumName = consortiumName;
this.label = consortiumName;
}
}

Просмотреть файл

@ -27,6 +27,12 @@ export class LocalNetworkConsortium extends Consortium {
return network;
}
public async getPort(): Promise<number | undefined> {
const network = await this.getTruffleNetwork();
return network.options.port;
}
protected defaultProtocol(): string {
return Constants.networkProtocols.http;
}

Просмотреть файл

@ -1,7 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as fs from 'fs';
import { Constants } from '../Constants';
import { saveTextInFile, showInputBox, showQuickPick, TruffleConfiguration } from '../helpers';
import { MnemonicRepository } from '../MnemonicService/MnemonicRepository';
@ -18,10 +17,10 @@ export abstract class ProtectedConsortium extends Consortium {
const truffleConfigPath = TruffleConfiguration.getTruffleConfigUri();
const config = new TruffleConfiguration.TruffleConfig(truffleConfigPath);
const network = await super.getTruffleNetwork();
const targetURL = await this.getRPCAddress();
const mnemonic = await this.getMnemonic();
await config.importFs();
config.importFs();
network.options.provider = {
mnemonic: mnemonic.path,
@ -55,11 +54,10 @@ export abstract class ProtectedConsortium extends Consortium {
},
];
const savedMnemonics = MnemonicRepository.getAllMnemonicPaths()
.filter((path) => fs.existsSync(path))
const savedMnemonics = MnemonicRepository.getExistedMnemonicPaths()
.map((path) => {
const mnemonic = MnemonicRepository.getMnemonic(path);
const label = `${mnemonic.split(' ')[0].slice(0, 3)} ... ${mnemonic.split(' ')[11].slice(-3)}`;
const label = MnemonicRepository.MaskMnemonic(mnemonic);
return {
cmd: async () => ({mnemonic, path}),
detail: path,

14
src/Models/SkuItem.ts Normal file
Просмотреть файл

@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { QuickPickItem } from 'vscode';
export class SkuItem implements QuickPickItem {
public readonly label: string;
public readonly description: string;
constructor(tier: string, name: string) {
this.label = tier;
this.description = name;
}
}

Просмотреть файл

@ -0,0 +1,184 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { ResourceManagementClient, SubscriptionClient } from 'azure-arm-resource';
import { commands, extensions, ProgressLocation, QuickPickItem, window } from 'vscode';
import { AzureAccount } from './azure-account.api';
import { Constants } from './Constants';
import { showInputBox, showQuickPick } from './helpers';
import { LocationItem, ResourceGroupItem, SubscriptionItem } from './Models';
import { AzureBlockchainServiceValidator } from './validators/AzureBlockchainServiceValidator';
interface ICachedLocationItems {
locationItems: LocationItem[];
providerLocationItems: LocationItem[];
}
export class ResourceExplorerAndGenerator {
private static cache: { [subscriptionId: string]: ICachedLocationItems } = {};
protected readonly _accountApi: AzureAccount;
constructor() {
this._accountApi = extensions.getExtension<AzureAccount>('ms-vscode.azure-account')!.exports;
}
protected async getOrSelectSubscriptionItem(): Promise<SubscriptionItem> {
return showQuickPick(
await this.getSubscriptionItems(),
{ placeHolder: Constants.placeholders.selectSubscription, ignoreFocusOut: true },
);
}
protected async getOrCreateResourceGroupItem(subscriptionItem: SubscriptionItem): Promise<ResourceGroupItem> {
const pick = await showQuickPick(
this.getResourceGroupItems(subscriptionItem),
{ placeHolder: Constants.placeholders.selectResourceGroup, ignoreFocusOut: true },
);
if (pick instanceof ResourceGroupItem) {
return pick;
} else {
return this.createResourceGroup(subscriptionItem);
}
}
protected async getLocationItems(subscriptionItem: SubscriptionItem): Promise<LocationItem[]> {
const cachedLocationItems = await this.getCachedLocationItems(subscriptionItem);
const locationItems = cachedLocationItems.locationItems;
const providerLocationItems = cachedLocationItems.providerLocationItems;
return providerLocationItems.length !== 0 ? providerLocationItems : locationItems;
}
protected async waitForLogin(): Promise<boolean> {
let result = await this._accountApi.waitForLogin();
if (!result) {
await commands.executeCommand('azure-account.askForLogin');
result = await this._accountApi.waitForLogin();
if (!result) {
throw new Error(Constants.errorMessageStrings.WaitForLogin);
}
}
return true;
}
private async getSubscriptionItems(): Promise<SubscriptionItem[]> {
await this._accountApi.waitForFilters();
const subscriptionItems = this._accountApi.filters
.map((filter) => new SubscriptionItem(
filter.subscription.displayName || '',
filter.subscription.subscriptionId || '',
filter.session,
));
if (subscriptionItems.length === 0) {
throw new Error(Constants.errorMessageStrings.NoSubscriptionFoundClick);
}
return subscriptionItems;
}
private async getResourceGroupItems(subscriptionItem: SubscriptionItem): Promise<QuickPickItem[]> {
const createGroupItem: QuickPickItem = { label: '$(plus) Create Resource Group' };
const items: QuickPickItem[] = [];
const resourceManagementClient = await this.getResourceClient(subscriptionItem);
const resourceGroups = await resourceManagementClient.resourceGroups.list();
const cachedLocationItems = await this.getCachedLocationItems(subscriptionItem);
const locationItems = cachedLocationItems.locationItems;
const resourceItems = resourceGroups.map((resourceGroup) => {
const location = locationItems.find((locationItem) => locationItem.description === resourceGroup.location);
return new ResourceGroupItem(
resourceGroup.name,
location ? location.description : resourceGroup.location,
);
});
items.push(createGroupItem);
items.push(...resourceItems);
return items;
}
private async getSubscriptionClient(subscriptionItem: SubscriptionItem)
: Promise<SubscriptionClient.SubscriptionClient> {
return new SubscriptionClient.SubscriptionClient(
subscriptionItem.session.credentials,
subscriptionItem.session.environment.resourceManagerEndpointUrl,
);
}
private async getResourceClient(subscriptionItem: SubscriptionItem)
: Promise<ResourceManagementClient.ResourceManagementClient> {
return new ResourceManagementClient.ResourceManagementClient(
subscriptionItem.session.credentials,
subscriptionItem.subscriptionId,
subscriptionItem.session.environment.resourceManagerEndpointUrl,
);
}
private async createResourceGroup(subscriptionItem: SubscriptionItem): Promise<ResourceGroupItem> {
const { resourceGroups } = await this.getResourceClient(subscriptionItem);
const resourceGroupName = await showInputBox({
ignoreFocusOut: true,
placeHolder: Constants.placeholders.resourceGroupName,
prompt: Constants.paletteWestlakeLabels.provideResourceGroupName,
validateInput: (name) => AzureBlockchainServiceValidator.validateResourceGroupName(name, resourceGroups),
});
const locationItem = await showQuickPick(
this.getLocationItems(subscriptionItem),
{ placeHolder: Constants.placeholders.selectRgLocation, ignoreFocusOut: true },
);
return window.withProgress({
location: ProgressLocation.Notification,
title: `Creating resource group '${resourceGroupName}'`,
}, async () => {
if (subscriptionItem.subscriptionId === undefined) {
throw new Error(Constants.errorMessageStrings.NoSubscriptionFound);
} else {
const resourceManagementClient = await this.getResourceClient(subscriptionItem);
const resourceGroup = await resourceManagementClient.resourceGroups.createOrUpdate(
resourceGroupName,
{ location: locationItem.description },
);
return new ResourceGroupItem(resourceGroup.name, resourceGroup.location);
}
});
}
private async getCachedLocationItems(subscriptionItem: SubscriptionItem): Promise<ICachedLocationItems> {
const cache = ResourceExplorerAndGenerator.cache;
if (cache[subscriptionItem.subscriptionId]) {
return cache[subscriptionItem.subscriptionId];
}
const subscriptionClient = await this.getSubscriptionClient(subscriptionItem);
const resourceManagementClient = await this.getResourceClient(subscriptionItem);
const locations = await subscriptionClient.subscriptions.listLocations(subscriptionItem.subscriptionId);
const blockchain = await resourceManagementClient.providers.get(Constants.azureResourceExplorer.providerName);
const locationItems = locations.map((location) => new LocationItem(location.displayName, location.name));
const providerLocationItems: LocationItem[] = [];
const blockchainMember = blockchain.resourceTypes && blockchain.resourceTypes.find((resourceType) => {
return resourceType.resourceType === Constants.azureResourceExplorer.resourceType;
});
if (blockchainMember && blockchainMember.locations) {
providerLocationItems.push(...locationItems
.filter((item) => blockchainMember.locations!.includes(item.label))
.sort((a, b) => a.label.localeCompare(b.label)));
}
const cachedLocationItems = {
locationItems,
providerLocationItems,
};
cache[subscriptionItem.subscriptionId] = cachedLocationItems;
return cachedLocationItems;
}
}

Просмотреть файл

@ -3,17 +3,18 @@
import * as path from 'path';
import * as vscode from 'vscode';
import { vscodeEnvironment } from './helpers';
export class UiServer {
public static launchWebview() {
const workspacePath = vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders[0].uri.fsPath : '';
const htmlPath = path.join(workspacePath, 'drizzle', 'index.html');
const workspacePath = vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders[0].uri.fsPath : '';
const htmlPath = path.join(workspacePath, 'drizzle', 'index.html');
vscode.env.openExternal(vscode.Uri.parse(`file://${htmlPath}`));
vscodeEnvironment.openExternal(vscode.Uri.parse(`file://${htmlPath}`));
}
public static async startServer(): Promise<void> {
this.launchWebview();
this.launchWebview();
}
}

Просмотреть файл

@ -3,7 +3,9 @@
import { ConsortiumResourceExplorer } from '../ConsortiumResourceExplorer';
import { Constants } from '../Constants';
import { GanacheService } from '../GanacheService/GanacheService';
import { showInputBox, showQuickPick } from '../helpers';
import { findPid } from '../helpers/shell';
import {
AzureConsortium,
Consortium,
@ -16,10 +18,9 @@ import {
import { ConsortiumTreeManager } from '../treeService/ConsortiumTreeManager';
import { UrlValidator } from '../validators/UrlValidator';
import { ConsortiumView } from '../ViewItems';
import { GanacheCommands } from './GanacheCommands';
interface IConsortiumDestination {
cmd: (excludedItems?: string[]) => Promise<Consortium>;
cmd: (network?: Network) => Promise<Consortium>;
itemType: ItemType;
label: string;
}
@ -36,9 +37,8 @@ export namespace ConsortiumCommands {
const destination = await selectDestination(createConsortiumDestination);
const networkItem = await getNetwork(consortiumTreeManager, destination.itemType);
const childrenFilters = await getChildrenFilters(networkItem);
return destination.cmd(childrenFilters);
return destination.cmd(networkItem);
}
export async function connectConsortium(consortiumTreeManager: ConsortiumTreeManager): Promise<Consortium> {
@ -71,7 +71,11 @@ export namespace ConsortiumCommands {
export async function disconnectConsortium(consortiumTreeManager: ConsortiumTreeManager, viewItem: ConsortiumView)
: Promise<void> {
if (viewItem.extensionItem instanceof LocalNetworkConsortium) {
await GanacheCommands.stopGanacheServer();
const port = await viewItem.extensionItem.getPort();
if (port) {
await GanacheService.stopGanacheServer(port);
}
}
return consortiumTreeManager.removeItem(viewItem.extensionItem);
}
@ -83,15 +87,14 @@ async function execute(
): Promise<Consortium> {
const destination = await selectDestination(consortiumDestination);
const networkItem = await getNetwork(consortiumTreeManager, destination.itemType);
const childrenFilters = await getChildrenFilters(networkItem);
const consortiumItem = await destination.cmd(childrenFilters);
const consortiumItem = await destination.cmd(networkItem);
await networkItem.addChild(consortiumItem);
return consortiumItem;
}
function getChildrenFilters(networkItem: Network): string[] {
function getConnectedAbsConsortiums(networkItem: Network): string[] {
return networkItem
.getChildren()
.filter((e) => e.label)
@ -118,31 +121,41 @@ async function getNetwork(consortiumTreeManager: ConsortiumTreeManager, itemType
return networkItem;
}
async function selectOrCreateConsortium(excludedItems?: string[]): Promise<AzureConsortium> {
async function selectOrCreateConsortium(network?: Network): Promise<AzureConsortium> {
const excludedItems = network ? getConnectedAbsConsortiums(network) : [];
const azureResourceExplorer = new ConsortiumResourceExplorer();
return azureResourceExplorer.selectOrCreateConsortium(excludedItems);
}
async function connectLocalNetwork(): Promise<LocalNetworkConsortium> {
await GanacheCommands.startGanacheServer();
async function connectLocalNetwork(network?: Network): Promise<LocalNetworkConsortium> {
const ports = await getExistingLocalPorts(network);
const port = await showInputBox({
ignoreFocusOut: true,
prompt: Constants.paletteWestlakeLabels.enterLocalNetworkLocation,
validateInput: (value: string) => {
if (!value) {
return Constants.validationMessages.valueCannotBeEmpty;
validateInput: async (value: string) => {
const validationError = UrlValidator.validatePort(value);
if (validationError) {
return validationError;
}
if (value.match(new RegExp(/^\d+$/g))) {
value = `${Constants.networkProtocols.http}${Constants.localhost}:${value}`;
return UrlValidator.validateHostUrl(value);
if (ports.some( (existPort) => existPort === value)) {
return Constants.validationMessages.networkAlreadyExists;
}
return;
}});
if (!isNaN(await findPid(value))) {
return Constants.validationMessages.portAlreadyInUse;
}
const label = `localhost:${port}`;
return null;
},
});
await GanacheService.startGanacheServer(port);
const label = `${Constants.localhostName}:${port}`;
const url = `${Constants.networkProtocols.http}${Constants.localhost}:${port}`;
return new LocalNetworkConsortium(label, url);
}
@ -169,6 +182,7 @@ async function getConsortiumName() {
if (!value) {
return Constants.validationMessages.valueCannotBeEmpty;
}
return;
},
});
@ -182,3 +196,9 @@ async function getConsortiumUrl() {
return UrlValidator.validateHostUrl(value);
}});
}
async function getExistingLocalPorts(network?: Network): Promise<string[]> {
const localNetworks = network ? await network.getChildren() : [];
return await Promise.all((localNetworks as LocalNetworkConsortium[])
.map(async (item) => `${await item.getPort()}`));
}

Просмотреть файл

@ -1,79 +1,72 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { ChildProcess, spawn } from 'child_process';
import { OutputChannel, window } from 'vscode';
import { commands, QuickPickItem, window } from 'vscode';
import { Constants } from '../Constants';
import { CommandContext, required, setCommandContext, shell } from '../helpers';
let server: ChildProcess | undefined;
import { GanacheService } from '../GanacheService/GanacheService';
import { isGanacheServer } from '../GanacheService/GanacheServiceClient';
import { required, showQuickPick } from '../helpers';
import { ItemType, LocalNetworkConsortium } from '../Models';
import { Output } from '../Output';
import { ConsortiumTreeManager } from '../treeService/ConsortiumTreeManager';
import { ConsortiumView } from '../ViewItems';
export namespace GanacheCommands {
const ganacheOutputChannel: OutputChannel = window.createOutputChannel(Constants.outputChannel.ganacheCommands);
// Command to bind to UI commands
export async function startGanacheCmd(): Promise<void> {
if (server) {
export async function startGanacheCmd(
consortiumTreeManager: ConsortiumTreeManager,
viewItem?: ConsortiumView): Promise<void> {
if (!await required.checkApps(required.Apps.node)) {
commands.executeCommand('azureBlockchainService.showRequirementsPage');
return;
}
const port = await getGanachePort(consortiumTreeManager, viewItem);
const ganacheProcess = await GanacheService.startGanacheServer(port);
if (!ganacheProcess) {
window.showInformationMessage(Constants.ganacheCommandStrings.serverAlreadyRunning);
return;
}
await startGanacheServer();
if (server) {
(server as ChildProcess).on('error',
() => window.showErrorMessage(Constants.ganacheCommandStrings.serverAlreadyRunning));
}
window.showInformationMessage(Constants.ganacheCommandStrings.serverSuccessfullyRunning);
window.showInformationMessage(Constants.ganacheCommandStrings.serverSuccessfullyStarted);
}
// Command to bind to UI commands
export async function stopGanacheCmd(): Promise<void> {
if (!server) {
window.showInformationMessage(Constants.ganacheCommandStrings.serverCanNotStop);
return;
}
export async function stopGanacheCmd(
consortiumTreeManager: ConsortiumTreeManager,
viewItem?: ConsortiumView): Promise<void> {
const port = await getGanachePort(consortiumTreeManager, viewItem);
return stopGanacheServer();
if (isGanacheServer(port)) {
await GanacheService.stopGanacheServer(port);
window.showInformationMessage(Constants.ganacheCommandStrings.serverSuccessfullyStopped);
} else {
window.showWarningMessage(Constants.ganacheCommandStrings.serverCanNotStop);
}
Output.show();
}
export async function startGanacheServer(): Promise<void> {
if (!server) {
if (!await required.checkAppsSilent(required.Apps.ganache)) {
await required.installGanache(required.Scope.locally);
export async function getGanachePort(
consortiumTreeManager: ConsortiumTreeManager,
viewItem?: ConsortiumView,
): Promise<number | string> {
if (viewItem && viewItem.extensionItem instanceof LocalNetworkConsortium) {
return await viewItem.extensionItem.getPort() || '';
} else {
const hosts = consortiumTreeManager.getItem(ItemType.LOCAL_NETWORK);
if (!hosts || !hosts.getChildren()) {
throw new Error(Constants.ganacheCommandStrings.serverNoGanacheAvailable);
}
server = spawn('npx', ['ganache-cli'], { shell: true });
server.stdout.on('data', (data: string | Buffer) => {
ganacheOutputChannel.appendLine(data.toString());
});
server.stderr.on('data', (data: string | Buffer) => {
ganacheOutputChannel.appendLine(data.toString());
});
setCommandContext(
CommandContext.IsGanacheRunning,
true,
);
}
}
export async function stopGanacheServer(): Promise<void> {
if (server) {
await shell.freePort(Constants.defaultLocalhostPort);
server.removeAllListeners();
server = undefined;
window.showInformationMessage(Constants.ganacheCommandStrings.serverSuccessfullyStopped);
}
setCommandContext(CommandContext.IsGanacheRunning, false);
}
export async function dispose(): Promise<void> {
if (server) {
server = undefined;
return shell.freePort(Constants.defaultLocalhostPort);
const options = hosts.getChildren();
const pick = await showQuickPick(
options as QuickPickItem[],
{ placeHolder: Constants.placeholders.selectGanacheServer, ignoreFocusOut: true });
return await (pick as LocalNetworkConsortium).getPort() || '';
}
}
}

Просмотреть файл

@ -1,11 +1,15 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as bip39 from 'bip39';
import * as fs from 'fs-extra';
// @ts-ignore
import * as hdkey from 'hdkey';
import * as path from 'path';
import { format } from 'url';
import { env, ProgressLocation, Uri, window } from 'vscode';
import { ProgressLocation, QuickPickItem, Uri, window } from 'vscode';
import { Constants } from '../Constants';
import { GanacheService } from '../GanacheService/GanacheService';
import {
getWorkspaceRoot,
outputCommandHelper,
@ -14,12 +18,17 @@ import {
showQuickPick,
TruffleConfig,
TruffleConfiguration,
vscodeEnvironment,
} from '../helpers';
import { Consortium, MainNetworkConsortium } from '../Models';
import { MnemonicRepository } from '../MnemonicService/MnemonicRepository';
import {
Consortium,
LocalNetworkConsortium,
MainNetworkConsortium,
} from '../Models';
import { Output } from '../Output';
import { ConsortiumTreeManager } from '../treeService/ConsortiumTreeManager';
import { ConsortiumCommands } from './ConsortiumCommands';
import { GanacheCommands } from './GanacheCommands';
interface IDeployDestination {
cmd: () => Promise<void>;
@ -31,6 +40,13 @@ interface IDeployDestination {
consortiumId?: number;
}
interface IExtendedQuickPickItem extends QuickPickItem {
/**
* Additional field for storing non-displayed information
*/
extended: string;
}
const localGanacheRegexp = new RegExp(`127\.0\.0\.1\:${Constants.defaultLocalhostPort}`, 'g');
export namespace TruffleCommands {
@ -39,16 +55,16 @@ export namespace TruffleCommands {
location: ProgressLocation.Window,
title: Constants.statusBarMessages.buildingContracts,
}, async () => {
if (!await required.checkAppsSilent(required.Apps.truffle)) {
await required.installTruffle(required.Scope.locally);
}
if (!await required.checkAppsSilent(required.Apps.truffle)) {
await required.installTruffle(required.Scope.locally);
}
try {
Output.show();
await outputCommandHelper.executeCommand(getWorkspaceRoot(), 'npx', 'truffle', 'compile');
} catch (error) {
throw Error(error);
}
Output.show();
await outputCommandHelper.executeCommand(getWorkspaceRoot(), 'npx', 'truffle', 'compile');
} catch (error) {
throw Error(error);
}
});
}
@ -58,12 +74,9 @@ export namespace TruffleCommands {
}
const truffleConfigsUri = TruffleConfiguration.getTruffleConfigUri();
const defaultDeployDestinations = await getDefaultDeployDestinations(truffleConfigsUri, consortiumTreeManager);
const truffleDeployDestinations = await getTruffleDeployDestinations(truffleConfigsUri);
const consortiumDeployDestinations = await getConsortiumDeployDestinations(
truffleConfigsUri,
consortiumTreeManager,
);
const defaultDeployDestinations = getDefaultDeployDestinations(truffleConfigsUri, consortiumTreeManager);
const truffleDeployDestinations = getTruffleDeployDestinations(truffleConfigsUri);
const consortiumDeployDestinations = getConsortiumDeployDestinations(truffleConfigsUri, consortiumTreeManager);
const deployDestinations: IDeployDestination[] = [];
deployDestinations.push(...defaultDeployDestinations);
@ -81,26 +94,66 @@ export namespace TruffleCommands {
export async function writeAbiToBuffer(uri: Uri): Promise<void> {
const contract = await readCompiledContract(uri);
env.clipboard.writeText(JSON.stringify(contract[Constants.contractProperties.abi]));
vscodeEnvironment.writeToClipboard(JSON.stringify(contract[Constants.contractProperties.abi]));
}
export async function writeBytecodeToBuffer(uri: Uri): Promise<void> {
const contract = await readCompiledContract(uri);
env.clipboard.writeText(contract[Constants.contractProperties.bytecode]);
vscodeEnvironment.writeToClipboard(contract[Constants.contractProperties.bytecode]);
}
export async function acquireCompiledContractUri(uri: Uri): Promise<Uri> {
if (path.extname(uri.fsPath) === Constants.contractExtension.json) {
return uri;
} else {
throw new Error('This file is not a valid contract.');
throw new Error(Constants.errorMessageStrings.InvalidContract);
}
}
export async function getPrivateKeyFromMnemonic(): Promise<void> {
const mnemonicItems: IExtendedQuickPickItem[] = MnemonicRepository
.getExistedMnemonicPaths()
.map((mnemonicPath) => {
const savedMnemonic = MnemonicRepository.getMnemonic(mnemonicPath);
return {
detail: mnemonicPath,
extended: savedMnemonic,
label: MnemonicRepository.MaskMnemonic(savedMnemonic),
};
});
if (mnemonicItems.length === 0) {
window.showErrorMessage(Constants.errorMessageStrings.ThereAreNoMnemonics);
return;
}
const mnemonicItem = await showQuickPick(
mnemonicItems,
{ placeHolder: Constants.placeholders.selectMnemonicExtractKey, ignoreFocusOut: true },
);
const mnemonic = mnemonicItem.extended;
if (!mnemonic) {
window.showErrorMessage(Constants.errorMessageStrings.MnemonicFileHaveNoText);
return;
}
try {
const buffer = await bip39.mnemonicToSeed(mnemonic);
const key = hdkey.fromMasterSeed(buffer);
const childKey = key.derive('m/44\'/60\'/0\'/0/0');
const privateKey = childKey.privateKey.toString('hex');
await vscodeEnvironment.writeToClipboard(privateKey);
window.showInformationMessage(Constants.informationMessage.privateKeyWasCopiedToClipboard);
} catch (error) {
window.showErrorMessage(Constants.errorMessageStrings.InvalidMnemonic);
}
}
}
async function getDefaultDeployDestinations(truffleConfigsUri: string, consortiumTreeManager: ConsortiumTreeManager)
: Promise<IDeployDestination[]> {
function getDefaultDeployDestinations(truffleConfigsUri: string, consortiumTreeManager: ConsortiumTreeManager)
: IDeployDestination[] {
return [
{
cmd: createNewDeploymentNetwork.bind(undefined, consortiumTreeManager, truffleConfigsUri),
@ -110,11 +163,10 @@ async function getDefaultDeployDestinations(truffleConfigsUri: string, consortiu
];
}
async function getTruffleDeployDestinations(truffleConfigsUri: string): Promise<IDeployDestination[]> {
function getTruffleDeployDestinations(truffleConfigsUri: string): IDeployDestination[] {
const deployDestination: IDeployDestination[] = [];
const truffleConfig = new TruffleConfig(truffleConfigsUri);
const networksFromConfig = await truffleConfig.getNetworks();
const networksFromConfig = truffleConfig.getNetworks();
networksFromConfig.forEach((network: TruffleConfiguration.INetwork) => {
const options = network.options;
@ -135,8 +187,8 @@ async function getTruffleDeployDestinations(truffleConfigsUri: string): Promise<
return deployDestination;
}
async function getConsortiumDeployDestinations(truffleConfigsUri: string, consortiumTreeManager: ConsortiumTreeManager)
: Promise<IDeployDestination[]> {
function getConsortiumDeployDestinations(truffleConfigsUri: string, consortiumTreeManager: ConsortiumTreeManager)
: IDeployDestination[] {
const deployDestination: IDeployDestination[] = [];
const networks = consortiumTreeManager.getItems(true);
@ -164,7 +216,7 @@ function getTruffleDeployFunction(url: string, name: string, truffleConfigPath:
// At this moment ganache-cli start only on port 8545.
// Refactor this after the build
if (url.match(localGanacheRegexp)) {
return deployToLocalGanache.bind(undefined, name, truffleConfigPath);
return deployToLocalGanache.bind(undefined, name, truffleConfigPath, url);
}
// 1 - is the marker of main network
if (networkId === 1 || networkId === '1') {
@ -178,7 +230,7 @@ function getConsortiumCreateFunction(url: string, consortium: Consortium, truffl
// At this moment ganache-cli start only on port 8545.
// Refactor this after the build
if (url.match(localGanacheRegexp)) {
return createLocalGanacheNetwork.bind(undefined, consortium, truffleConfigPath);
return createLocalGanacheNetwork.bind(undefined, consortium as LocalNetworkConsortium, truffleConfigPath);
}
if (consortium instanceof MainNetworkConsortium) {
return createMainNetwork.bind(undefined, consortium, truffleConfigPath);
@ -207,7 +259,7 @@ async function createNewDeploymentNetwork(consortiumTreeManager: ConsortiumTreeM
async function createNetwork(consortium: Consortium, truffleConfigPath: string): Promise<void> {
const network = await consortium.getTruffleNetwork();
const truffleConfig = new TruffleConfig(truffleConfigPath);
await truffleConfig.setNetworks(network);
truffleConfig.setNetworks(network);
await deployToNetwork(network.name, truffleConfigPath);
}
@ -234,14 +286,17 @@ async function deployToNetwork(networkName: string, truffleConfigPath: string):
});
}
async function createLocalGanacheNetwork(consortium: Consortium, truffleConfigPath: string): Promise<void> {
await GanacheCommands.startGanacheServer();
async function createLocalGanacheNetwork(consortium: LocalNetworkConsortium, truffleConfigPath: string): Promise<void> {
const port = await consortium.getPort();
await GanacheService.startGanacheServer(port!);
await createNetwork(consortium, truffleConfigPath);
}
async function deployToLocalGanache(networkName: string, truffleConfigPath: string): Promise<void> {
await GanacheCommands.startGanacheServer();
async function deployToLocalGanache(networkName: string, truffleConfigPath: string, url: string): Promise<void> {
const port = GanacheService.getPortFromUrl(url);
await GanacheService.startGanacheServer(port!);
await deployToNetwork(networkName, truffleConfigPath);
}

Просмотреть файл

@ -10,6 +10,7 @@ import { LogicAppCommands } from './commands/LogicAppCommands';
import { ProjectCommands } from './commands/ProjectCommands';
import { TruffleCommands } from './commands/TruffleCommands';
import { Constants } from './Constants';
import { GanacheService } from './GanacheService/GanacheService';
import { CommandContext, isWorkspaceOpen, required, setCommandContext } from './helpers';
import { MnemonicRepository } from './MnemonicService/MnemonicRepository';
import { CancellationEvent } from './Models';
@ -45,24 +46,24 @@ export async function activate(context: ExtensionContext) {
});
const showRequirementsPage = commands.registerCommand('azureBlockchainService.showRequirementsPage',
async (checkShowOnStartup: boolean) => {
return checkShowOnStartup ? requirementsPage.checkAndShow() : requirementsPage.show();
});
return checkShowOnStartup ? requirementsPage.checkAndShow() : requirementsPage.show();
});
const copyRPCEndpointAddress = commands.registerCommand('azureBlockchainService.copyRPCEndpointAddress',
async (viewItem: ConsortiumView) => {
await tryExecute(() => AzureBlockchain.copyRPCEndpointAddress(viewItem));
});
await tryExecute(() => AzureBlockchain.copyRPCEndpointAddress(viewItem));
});
//#endregion
//#region Ganache extension commands
const startGanacheServer = commands.registerCommand('azureBlockchainService.startGanacheServer',
async () => {
await tryExecute(() => GanacheCommands.startGanacheCmd());
});
async (viewItem?: ConsortiumView) => {
await tryExecute(() => GanacheCommands.startGanacheCmd(consortiumTreeManager, viewItem));
});
const stopGanacheServer = commands.registerCommand('azureBlockchainService.stopGanacheServer',
async () => {
await tryExecute(() => GanacheCommands.stopGanacheCmd());
});
async (viewItem?: ConsortiumView) => {
await tryExecute(() => GanacheCommands.stopGanacheCmd(consortiumTreeManager, viewItem));
});
//#endregion
//#region truffle commands
@ -81,6 +82,9 @@ export async function activate(context: ExtensionContext) {
const copyABI = commands.registerCommand('contract.copyABI', async (uri: Uri) => {
await tryExecute(() => TruffleCommands.writeAbiToBuffer(uri));
});
const getPrivateKeyFromMnemonic = commands.registerCommand('azureBlockchainService.getPrivateKey', async () => {
await tryExecute(() => TruffleCommands.getPrivateKeyFromMnemonic());
});
//#endregion
//#region commands with dialog
@ -91,9 +95,9 @@ export async function activate(context: ExtensionContext) {
await tryExecute(() => ConsortiumCommands.connectConsortium(consortiumTreeManager));
});
const disconnectConsortium = commands.registerCommand('azureBlockchainService.disconnectConsortium',
async (viewItem: ConsortiumView) => {
await tryExecute(() => ConsortiumCommands.disconnectConsortium(consortiumTreeManager, viewItem));
});
async (viewItem: ConsortiumView) => {
await tryExecute(() => ConsortiumCommands.disconnectConsortium(consortiumTreeManager, viewItem));
});
//#endregion
//#region remix commands
@ -106,23 +110,23 @@ export async function activate(context: ExtensionContext) {
const generateMicroservicesWorkflows = commands.registerCommand(
'azureBlockchainService.generateMicroservicesWorkflows',
async (filePath: Uri | undefined) => {
await tryExecute(() => LogicAppCommands.generateMicroservicesWorkflows(filePath));
});
await tryExecute(() => LogicAppCommands.generateMicroservicesWorkflows(filePath));
});
const generateDataPublishingWorkflows = commands.registerCommand(
'azureBlockchainService.generateDataPublishingWorkflows',
async (filePath: Uri | undefined) => {
await tryExecute(() => LogicAppCommands.generateDataPublishingWorkflows(filePath));
});
async (filePath: Uri | undefined) => {
await tryExecute(() => LogicAppCommands.generateDataPublishingWorkflows(filePath));
});
const generateEventPublishingWorkflows = commands.registerCommand(
'azureBlockchainService.generateEventPublishingWorkflows',
async (filePath: Uri | undefined) => {
await tryExecute(() => LogicAppCommands.generateEventPublishingWorkflows(filePath));
});
async (filePath: Uri | undefined) => {
await tryExecute(() => LogicAppCommands.generateEventPublishingWorkflows(filePath));
});
const generateReportPublishingWorkflows = commands.registerCommand(
'azureBlockchainService.generateReportPublishingWorkflows',
async (filePath: Uri | undefined) => {
await tryExecute(() => LogicAppCommands.generateReportPublishingWorkflows(filePath));
});
await tryExecute(() => LogicAppCommands.generateReportPublishingWorkflows(filePath));
});
//#endregion
context.subscriptions.push(showWelcomePage);
@ -144,6 +148,7 @@ export async function activate(context: ExtensionContext) {
context.subscriptions.push(generateDataPublishingWorkflows);
context.subscriptions.push(generateEventPublishingWorkflows);
context.subscriptions.push(generateReportPublishingWorkflows);
context.subscriptions.push(getPrivateKeyFromMnemonic);
return required.checkAllApps();
}
@ -152,7 +157,7 @@ export async function deactivate(): Promise<void> {
// this method is called when your extension is deactivated
await Output.dispose();
return GanacheCommands.dispose();
await GanacheService.dispose();
}
async function tryExecute(func: () => Promise<any>, errorMessage: string | null = null): Promise<void> {

Просмотреть файл

@ -12,15 +12,18 @@ export interface ICommandResult {
cmdOutputIncludingStderr: string;
}
export async function executeCommand(workingDirectory: string | undefined, commands: string, ...args: string[])
: Promise<string> {
export async function executeCommand(
workingDirectory: string | undefined,
commands: string,
...args: string[]
): Promise<string> {
Output.outputLine(
Constants.outputChannel.executeCommand,
`
Working dir: ${workingDirectory}
${Constants.executeCommandMessage.runningCommand}
${[commands, ...args].join(' ')}`,
);
'\n' +
`Working dir: ${workingDirectory}\n` +
`${Constants.executeCommandMessage.runningCommand}\n` +
`${[commands, ...args].join(' ')}`,
);
const result: ICommandResult = await tryExecuteCommand(workingDirectory, commands, ...args);
@ -37,13 +40,24 @@ ${[commands, ...args].join(' ')}`,
return result.cmdOutput;
}
export function startProcess(
workingDirectory: string | undefined,
commands: string,
args: string[],
): cp.ChildProcess {
const options: cp.SpawnOptions = { cwd: workingDirectory || os.tmpdir(), shell: true };
const process = cp.spawn(commands, args, options);
return process;
}
export async function tryExecuteCommand(workingDirectory: string | undefined, commands: string, ...args: string[])
: Promise<ICommandResult> {
return await new Promise((resolve: (res: any) => void, reject: (error: Error) => void): void => {
return new Promise((resolve: (res: any) => void, reject: (error: Error) => void): void => {
let cmdOutput: string = '';
let cmdOutputIncludingStderr: string = '';
const options: cp.SpawnOptions = { cwd: workingDirectory || os.tmpdir(), shell: true};
const options: cp.SpawnOptions = { cwd: workingDirectory || os.tmpdir(), shell: true };
const childProcess: cp.ChildProcess = cp.spawn(commands, args, options);
childProcess.stdout.on('data', (data: string | Buffer) => {

Просмотреть файл

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { Constants } from '../Constants';
import { executeCommand } from './command';
import { required } from './required';

Просмотреть файл

@ -8,6 +8,7 @@ import { required } from './required';
import * as shell from './shell';
import { TruffleConfiguration } from './truffleConfig';
import * as userInteractionHelper from './userInteraction';
import * as vscodeEnvironment from './vscodeEnvironment';
import * as workspaceHelpers from './workspace';
const saveTextInFile = userInteractionHelper.saveTextInFile;
@ -37,4 +38,5 @@ export {
showConfirmPaidOperationDialog,
TruffleConfig,
TruffleConfiguration,
vscodeEnvironment,
};

Просмотреть файл

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as semver from 'semver';
import { commands, window } from 'vscode';
import { Constants } from '../Constants';
@ -130,28 +133,23 @@ export namespace required {
export async function getTruffleVersion(): Promise<string> {
const majorVersion = (Constants.requiredVersions.truffle as { max: string, min: string }).min.split('.')[0];
let localVersion;
try {
localVersion = (await executeCommand(getWorkspaceRoot(), `npm list --depth 0 truffle@${majorVersion}`))
.match(/truffle@(\d+.\d+.\d+)/);
} catch (e) {
// ignore
}
const localVersion = (await tryExecuteCommand(getWorkspaceRoot(true), `npm list --depth 0 truffle@${majorVersion}`))
.cmdOutput
.match(/truffle@(\d+.\d+.\d+)/);
return (localVersion && localVersion[1]) || getVersion('truffle', 'version', /(?<=Truffle v)(\d+.\d+.\d+)/);
}
export async function getGanacheVersion(): Promise<string> {
const majorVersion = (Constants.requiredVersions.ganache as { max: string, min: string }).min.split('.')[0];
let localVersion;
try {
localVersion = (await executeCommand(getWorkspaceRoot(), `npm list --depth 0 ganache-cli@${majorVersion}`))
.match(/ganache-cli@(\d+.\d+.\d+)/);
} catch (e) {
console.log(e);
}
const localVersion = (await tryExecuteCommand(
getWorkspaceRoot(true),
`npm list --depth 0 ganache-cli@${majorVersion}`,
))
.cmdOutput
.match(/truffle@(\d+.\d+.\d+)/);
return (localVersion && localVersion[1]) || getVersion('ganache-cli', '--version', /v(\d+.\d+.\d+)/);
}
@ -160,7 +158,7 @@ export namespace required {
try {
await installUsingNpm('npm', Constants.requiredVersions.npm);
} catch (error) {
// ignore
Output.outputLine(Constants.outputChannel.requirements, error.message);
}
currentState.npm = await createRequiredVersion('npm', getNpmVersion, CommandContext.NpmIsAvailable);
@ -170,7 +168,7 @@ export namespace required {
try {
await installUsingNpm('truffle', Constants.requiredVersions.truffle, scope);
} catch (error) {
// ignore
Output.outputLine(Constants.outputChannel.requirements, error.message);
}
currentState.truffle = await createRequiredVersion('truffle', getTruffleVersion, CommandContext.TruffleIsAvailable);
@ -180,7 +178,7 @@ export namespace required {
try {
await installUsingNpm('ganache-cli', Constants.requiredVersions.ganache, scope);
} catch (error) {
// ignore
Output.outputLine(Constants.outputChannel.requirements, error.message);
}
currentState.ganache = await createRequiredVersion('ganache', getGanacheVersion, CommandContext.GanacheIsAvailable);
@ -218,7 +216,13 @@ export namespace required {
`^${packageVersion}` :
`>=${packageVersion.min} <${packageVersion.max}`;
await executeCommand(getWorkspaceRoot(), 'npm', 'i', scope ? '' : '-g', ` ${packageName}@"${versionString}"`);
const workspaceRoot = getWorkspaceRoot(true);
if (workspaceRoot === undefined && scope === Scope.locally) {
throw new Error(Constants.errorMessageStrings.WorkspaceShouldBeOpened);
}
await executeCommand(workspaceRoot, 'npm', 'i', scope ? '' : '-g', ` ${packageName}@"${versionString}"`);
}
async function getVersion(program: string, command: string, matcher: RegExp): Promise<string> {

Просмотреть файл

@ -1,27 +1,35 @@
import { executeCommand } from './command';
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { executeCommand, tryExecuteCommand } from './command';
const isWin = process.platform === 'win32';
export async function freePort(port: string | number): Promise<void> {
export async function killPort(port: string | number): Promise<void> {
const pid = await findPid(port);
if (isNaN(pid)) {
return;
}
const killCommand = isWin ? `taskkill /PID ${pid} /F` : `kill -TERM ${pid}`;
await executeCommand(undefined, killCommand);
}
async function findPid(port: string | number): Promise<number> {
export async function findPid(port: string | number): Promise<number> {
let pid;
let output = '';
if (isWin) {
const pidLine = await executeCommand(undefined, `netstat -ano -p tcp | find "LISTENING" | find ":${port}"`);
pid = pidLine.match(/\s+\d+\s+$/);
return pid ? parseInt(pid[0].trim(), 10) : -1;
output = (await tryExecuteCommand(
undefined,
`netstat -ano -p tcp | find "LISTENING" | findstr /r /c:":${port} *[^ ]*:[^ ]*"`))
.cmdOutput;
} else {
output = (await tryExecuteCommand(undefined, `lsof -i tcp:${port} | grep LISTEN | awk '{print $2}'`)).cmdOutput;
}
pid = await executeCommand(undefined, `lsof -i tcp:${port} | grep LISTEN | awk '{print $2}'`);
pid = output.match(/\s+\d+\s+$/);
return parseInt(pid, 10);
return pid ? parseInt(pid[0].trim(), 10) : Number.NaN;
}

Просмотреть файл

@ -10,6 +10,7 @@ import * as crypto from 'crypto';
import * as ESTree from 'estree';
import * as fs from 'fs-extra';
import * as path from 'path';
import { Constants } from '../Constants';
import { getWorkspaceRoot } from './workspace';
export namespace TruffleConfiguration {
@ -18,8 +19,8 @@ export namespace TruffleConfiguration {
);
interface IFound {
node: ESTree.Node;
state: string | undefined;
node?: ESTree.Node;
state?: string;
}
export interface IProvider {
@ -79,11 +80,52 @@ export namespace TruffleConfiguration {
options: INetworkOption;
}
export interface ISolCompiler {
/**
* A version or constraint - Ex. "^0.5.0" . Can also be set to "native" to use a native solc
*/
version: string;
/**
* Use a version obtained through docker
*/
docker?: boolean;
settings?: {
optimizer?: {
enabled: boolean,
/**
* Optimize for how many times you intend to run the code
*/
runs: number,
},
/**
* Default: "byzantium"
*/
evmVersion?: string,
};
}
export interface IExternalCompiler {
command: string;
workingDirectory: string;
targets: object[];
}
export interface IConfiguration {
contracts_directory: string;
contracts_build_directory: string;
migrations_directory: string;
networks?: INetwork[];
compilers?: {
solc?: ISolCompiler;
external?: IExternalCompiler;
};
}
export function getTruffleConfigUri(): string {
const configFilePath = path.join(getWorkspaceRoot(), 'truffle-config.js');
const configFilePath = path.join(getWorkspaceRoot()!, 'truffle-config.js');
if (!fs.pathExistsSync(configFilePath)) {
throw new Error('Truffle configuration file not found');
throw new Error(Constants.errorMessageStrings.TruffleConfigIsNotExist);
}
return configFilePath;
@ -98,9 +140,9 @@ export namespace TruffleConfiguration {
constructor(private readonly filePath: string) { }
public async getAST(): Promise<ESTree.BaseNode> {
public getAST(): ESTree.BaseNode {
if (!this.ast) {
const file = await fs.readFile(this.filePath, 'utf8');
const file = fs.readFileSync(this.filePath, 'utf8');
this.ast = acorn.parse(file, {
allowHashBang: true,
allowReserved: true,
@ -111,86 +153,94 @@ export namespace TruffleConfiguration {
return this.ast;
}
public async writeAST(): Promise<void> {
return fs.writeFile(this.filePath, generate(this.ast as ESTree.Node, {comments: true}));
public writeAST(): void {
return fs.writeFileSync(this.filePath, generate(this.ast as ESTree.Node, {comments: true}));
}
public async getNetworks(): Promise<INetwork[]> {
const ast = await this.getAST();
const networks: INetwork[] = [];
const moduleExports: IFound = walk.findNodeAt(ast as ESTree.Node, null, null, isModuleExportsExpression);
public getNetworks(): INetwork[] {
const ast = this.getAST();
const moduleExports = getModuleExportsObjectExpression(ast as ESTree.Node);
if (moduleExports.node) {
const node = moduleExports.node as ESTree.ExpressionStatement;
const rightExpression = (node.expression as ESTree.AssignmentExpression).right;
if (rightExpression.type === 'ObjectExpression') {
const networksNode = findProperty(rightExpression, 'networks');
if (networksNode && networksNode.value.type === 'ObjectExpression') {
networksNode.value.properties.forEach((property: ESTree.Property) => {
if (property.key.type === 'Identifier') {
networks.push({
name: property.key.name,
options: astToNetworkOptions(property.value as ESTree.ObjectExpression),
});
}
if (property.key.type === 'Literal') {
networks.push({
name: '' + property.key.value,
options: astToNetworkOptions(property.value as ESTree.ObjectExpression),
});
}
});
}
if (moduleExports) {
const networksNode = findProperty(moduleExports, 'networks');
if (networksNode && networksNode.value.type === 'ObjectExpression') {
return astToNetworks(networksNode.value);
}
}
return networks;
return [];
}
public async setNetworks(network: INetwork): Promise<void> {
const ast = await this.getAST();
const moduleExports: IFound = walk.findNodeAt(ast as ESTree.Node, null, null, isModuleExportsExpression);
public setNetworks(network: INetwork): void {
const ast = this.getAST();
const moduleExports = getModuleExportsObjectExpression(ast as ESTree.Node);
if (moduleExports.node) {
const node = moduleExports.node as ESTree.ExpressionStatement;
const rightExpression = (node.expression as ESTree.AssignmentExpression).right;
if (moduleExports) {
let networksNode = findProperty(moduleExports, 'networks');
if (!networksNode) {
networksNode = generateProperty('networks', generateObjectExpression());
moduleExports.properties.push(networksNode);
}
if (rightExpression.type === 'ObjectExpression') {
let networksNode = findProperty(rightExpression, 'networks');
if (!networksNode) {
networksNode = generateProperty('networks', generateObjectExpression());
rightExpression.properties.push(networksNode);
}
if (networksNode.value.type === 'ObjectExpression') {
const isExist = findProperty(networksNode.value, network.name);
if (isExist) {
throw Error(`Network with name ${network.name} already existed in truffle-config.js`);
} else {
const networkNode = generateProperty(network.name, generateObjectExpression());
networkNode.value = networkOptionsToAst(network);
networksNode.value.properties.push(networkNode);
}
if (networksNode.value.type === 'ObjectExpression') {
const isExist = findProperty(networksNode.value, network.name);
if (isExist) {
throw new Error(Constants.errorMessageStrings.NetworkAlreadyExist(network.name));
} else {
const networkNode = generateProperty(network.name, generateObjectExpression());
networkNode.value = networkOptionsToAst(network);
networksNode.value.properties.push(networkNode);
}
}
}
this.ast = ast;
return this.writeAST();
this.writeAST();
}
public async importFs(): Promise<void> {
const ast = await this.getAST();
public importFs(): void {
const ast = this.getAST();
const fsRequired: IFound = walk.findNodeAt(ast as ESTree.Node, null, null, isVarDeclaration('fs'));
if (!fsRequired) {
if (!fsRequired.node) {
const declaration = generateVariableDeclaration('fs', 'require', 'fs');
(ast as ESTree.Program).body.unshift(declaration);
}
this.ast = ast;
await this.writeAST();
this.writeAST();
}
public getConfiguration(): IConfiguration {
const ast = this.getAST();
const moduleExports = getModuleExportsObjectExpression(ast as ESTree.Node);
if (moduleExports) {
return astToConfiguration(moduleExports);
}
return getDefaultConfiguration();
}
}
function getModuleExportsObjectExpression(ast: ESTree.Node): ESTree.ObjectExpression | void {
const moduleExports: IFound = walk.findNodeAt(ast as ESTree.Node, null, null, isModuleExportsExpression);
if (moduleExports.node) {
const node = moduleExports.node as ESTree.ExpressionStatement;
const rightExpression = (node.expression as ESTree.AssignmentExpression).right;
if (rightExpression.type === 'ObjectExpression') {
return rightExpression;
}
}
}
function getDefaultConfiguration(): IConfiguration {
return {
contracts_build_directory: path.join('./', 'build', 'contracts'),
contracts_directory: path.join('./', 'contracts'),
migrations_directory: path.join('./', 'migrations'),
};
}
function isModuleExportsExpression(nodeType: string, node: ESTree.Node): boolean {
@ -412,6 +462,59 @@ export namespace TruffleConfiguration {
};
}
function astToConfiguration(node: ESTree.ObjectExpression): IConfiguration {
const configuration = getDefaultConfiguration();
const contractsDir = findProperty(node, 'contracts_directory');
if (contractsDir && contractsDir.value.type === 'Literal' &&
typeof contractsDir.value.value === 'string' && contractsDir.value.value) {
configuration.contracts_directory = contractsDir.value.value;
}
const contractsBuildDir = findProperty(node, 'contracts_build_directory');
if (contractsBuildDir && contractsBuildDir.value.type === 'Literal' &&
typeof contractsBuildDir.value.value === 'string' && contractsBuildDir.value.value) {
configuration.contracts_build_directory = contractsBuildDir.value.value;
}
const migrationsDir = findProperty(node, 'migrations_directory');
if (migrationsDir && migrationsDir.value.type === 'Literal' &&
typeof migrationsDir.value.value === 'string' && migrationsDir.value.value) {
configuration.migrations_directory = migrationsDir.value.value;
}
const networks = findProperty(node, 'networks');
if (networks && networks.value.type === 'ObjectExpression') {
configuration.networks = astToNetworks(networks.value);
}
// TODO: compilers
return configuration;
}
function astToNetworks(node: ESTree.ObjectExpression): INetwork[] {
const networks: INetwork[] = [];
node.properties.forEach((property: ESTree.Property) => {
if (property.key.type === 'Identifier') {
networks.push({
name: property.key.name,
options: astToNetworkOptions(property.value as ESTree.ObjectExpression),
});
}
if (property.key.type === 'Literal') {
networks.push({
name: '' + property.key.value,
options: astToNetworkOptions(property.value as ESTree.ObjectExpression),
});
}
});
return networks;
}
function generateProperty(name: string, value: ESTree.Expression): ESTree.Property {
notAllowedSymbols.lastIndex = 0;
const isLiteral = notAllowedSymbols.test(name);

Просмотреть файл

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as vscode from 'vscode';
export async function openExternal(uri: vscode.Uri): Promise<boolean> {
return vscode.env.openExternal(uri);
}
export async function writeToClipboard(text: string): Promise<void> {
return vscode.env.clipboard.writeText(text);
}

Просмотреть файл

@ -4,10 +4,10 @@
import { workspace } from 'vscode';
import { Constants } from '../Constants';
export function getWorkspaceRoot(): string {
export function getWorkspaceRoot(ignoreException: boolean = false): string | undefined {
const workspaceRoot = workspace.workspaceFolders && workspace.workspaceFolders[0].uri.fsPath;
if (workspaceRoot === undefined) {
if (workspaceRoot === undefined && !ignoreException) {
throw Error(Constants.validationMessages.undefinedVariable('Workspace root'));
}
@ -15,7 +15,5 @@ export function getWorkspaceRoot(): string {
}
export function isWorkspaceOpen(): boolean {
const workspaceRoot = workspace.workspaceFolders && workspace.workspaceFolders[0].uri.fsPath;
return workspaceRoot ? true : false;
return !!(workspace.workspaceFolders && workspace.workspaceFolders[0].uri.fsPath);
}

98
src/pages/BasicWebView.ts Normal file
Просмотреть файл

@ -0,0 +1,98 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import {
Disposable,
ExtensionContext,
Uri,
ViewColumn,
WebviewOptions,
WebviewPanel,
WebviewPanelOptions,
window,
} from 'vscode';
export abstract class BasicWebView {
protected panel?: WebviewPanel;
protected readonly context: ExtensionContext;
protected readonly disposables: Disposable[];
protected readonly options: WebviewPanelOptions & WebviewOptions;
protected readonly rootPath: Uri;
protected abstract showOnStartup: string;
protected abstract title: string;
protected abstract viewType: string;
protected constructor(context: ExtensionContext) {
this.context = context;
this.disposables = [];
this.rootPath = Uri.file(this.context.asAbsolutePath('.'));
this.options = {
enableCommandUris: true,
enableScripts: true,
localResourceRoots: [ this.rootPath ],
retainContextWhenHidden: true,
};
}
public async checkAndShow(): Promise<void> {
const showOnStartup = this.context.globalState.get(this.showOnStartup);
if (showOnStartup === false) {
return;
}
if (showOnStartup === undefined) {
this.context.globalState.update(this.showOnStartup, await this.setShowOnStartupFlagAtFirstTime());
}
return this.show();
}
public async show(): Promise<void> {
if (this.panel) {
return this.panel.reveal(ViewColumn.One);
}
this.panel = window.createWebviewPanel(this.viewType, this.title, ViewColumn.One, this.options);
this.panel.webview.html = await this.getHtmlForWebview();
this.panel.webview.onDidReceiveMessage((message) => this.receiveMessage(message), null, this.disposables);
this.panel.onDidDispose(() => this.dispose(), null, this.disposables);
}
protected abstract async setShowOnStartupFlagAtFirstTime(): Promise<boolean>;
protected abstract async getHtmlForWebview(): Promise<string>;
protected async receiveMessage(message: {[key: string]: any}): Promise<void> {
if (!this.panel) {
return;
}
if (message.command === 'documentReady') {
this.panel.webview.postMessage({
command: 'showOnStartup',
value: this.context.globalState.get(this.showOnStartup),
});
}
if (message.command === 'toggleShowPage') {
this.context.globalState.update(this.showOnStartup, message.value);
}
}
private dispose(): void {
if (this.panel) {
this.panel.dispose();
}
while (this.disposables.length) {
const x = this.disposables.pop();
if (x) {
x.dispose();
}
}
this.panel = undefined;
}
}

60
src/pages/Requirements.ts Normal file
Просмотреть файл

@ -0,0 +1,60 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as fs from 'fs-extra';
import { ExtensionContext } from 'vscode';
import { Constants } from '../Constants';
import { required } from '../helpers';
import { BasicWebView } from './BasicWebView';
export class RequirementsPage extends BasicWebView {
protected readonly showOnStartup: string;
protected readonly title: string;
protected readonly viewType: string;
constructor(context: ExtensionContext) {
super(context);
this.showOnStartup = Constants.showOnStartupRequirementsPage;
this.title = Constants.webViewPages.requirements;
this.viewType = 'requirementsPage';
}
protected async setShowOnStartupFlagAtFirstTime(): Promise<boolean> {
return true;
}
protected async getHtmlForWebview(): Promise<string> {
const rootPath = this.rootPath.with({scheme: 'vscode-resource'}).toString();
const html = await fs.readFile(Constants.requirementsPagePath, 'utf8');
return html.replace(/{{root}}/g, rootPath);
}
protected async receiveMessage(message: {[key: string]: any}): Promise<void> {
await super.receiveMessage(message);
if (!this.panel) {
return;
}
if (message.command === 'documentReady') {
this.panel.webview.postMessage({
command: 'versions',
value: await required.getAllVersions(),
});
}
if (message.command === 'installNpm') {
await required.installNpm();
}
if (message.command === 'installTruffle') {
await required.installTruffle(required.Scope.global);
}
if (message.command === 'installGanache') {
await required.installGanache(required.Scope.global);
}
}
}

32
src/pages/Welcome.ts Normal file
Просмотреть файл

@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as fs from 'fs-extra';
import { ExtensionContext } from 'vscode';
import { Constants } from '../Constants';
import { BasicWebView } from './BasicWebView';
export class WelcomePage extends BasicWebView {
protected readonly showOnStartup: string;
protected readonly title: string;
protected readonly viewType: string;
constructor(context: ExtensionContext) {
super(context);
this.showOnStartup = Constants.showOnStartupWelcomePage;
this.title = Constants.webViewPages.welcome;
this.viewType = 'welcomePage';
}
protected async setShowOnStartupFlagAtFirstTime(): Promise<boolean> {
return false;
}
protected async getHtmlForWebview(): Promise<string> {
const rootPath = this.rootPath.with({scheme: 'vscode-resource'}).toString();
const html = await fs.readFile(Constants.welcomePagePath, 'utf8');
return html.replace(/{{root}}/g, rootPath);
}
}

7
src/pages/index.ts Normal file
Просмотреть файл

@ -0,0 +1,7 @@
import { RequirementsPage } from './Requirements';
import { WelcomePage } from './Welcome';
export {
RequirementsPage,
WelcomePage,
};

Просмотреть файл

@ -8,7 +8,7 @@ import { Output } from '../Output';
export class ConsortiumTreeManager {
private readonly items: IExtensionItem[];
private readonly resourceKey: string = 'treeContent';
private readonly resourceKey: string = Constants.consortiumTreeResourceKey;
constructor(private readonly context: ExtensionContext) {
this.items = this.loadState();

Просмотреть файл

@ -0,0 +1,82 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { ResourceGroups } from 'azure-arm-resource/lib/resource/operations';
import { ConsortiumResource, MemberResource } from '../ARMBlockchain';
import { Constants } from '../Constants';
import { Debounce } from './debounceValidation';
import { Validator } from './validator';
const debounce = new Debounce();
export namespace AzureBlockchainServiceValidator {
export async function validateAccessPassword(password: string): Promise<string | null> {
return new Validator(password)
.isNotEmpty()
.hasLowerCase()
.hasUpperCase()
.hasDigit()
.hasSpecialChar(Constants.validationRegexps.specialChars)
.hasNotUnallowedChar(Constants.validationRegexps.unallowedChars)
.inLengthRange(12, 72)
.getErrors();
}
export async function validateResourceGroupName(
name: string,
resourceGroups: ResourceGroups,
): Promise<string | null> {
if (!name.match(new RegExp(/^[-\w\._\(\)]+$/))) {
return Constants.validationMessages.invalidResourceGroupName;
}
const timeOverFunction = buildTimeOverFunction(
name,
resourceGroups.checkExistence.bind(resourceGroups),
Constants.validationMessages.resourceGroupAlreadyExists,
);
return await debounce.debounced(timeOverFunction);
}
export async function validateConsortiumName(
name: string,
consortiumResource: ConsortiumResource,
): Promise<string | null> {
const timeOverFunction = buildTimeOverFunction(
name,
consortiumResource.checkExistence.bind(consortiumResource),
);
return await debounce.debounced(timeOverFunction);
}
export async function validateMemberName(
name: string,
memberResource: MemberResource,
) {
const timeOverFunction = buildTimeOverFunction(
name,
memberResource.checkExistence.bind(memberResource),
);
return await debounce.debounced(timeOverFunction);
}
function buildTimeOverFunction(
name: string,
checkExistence: (name: string) => Promise<any>,
errorFunction?: (error: string) => string,
): () => Promise<string | null> {
return async () => {
const validator = new Validator(name);
await validator.isAvailable(
checkExistence,
errorFunction,
);
return validator.getErrors();
};
}
}

Просмотреть файл

@ -1,17 +1,14 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { Constants } from '../Constants';
import { Validator } from './validator';
export namespace DialogResultValidator {
export function validateConfirmationResult(result: string): string | null {
if (!result ||
![Constants.confirmationDialogResult.yes,
Constants.confirmationDialogResult.no]
.includes(result.toLowerCase())) {
return Constants.validationMessages.invalidConfirmationResult;
}
const validator = new Validator(result)
.isNotEmpty()
.isConfirmationValue();
return null;
return validator.getErrors();
}
}

Просмотреть файл

@ -2,24 +2,18 @@
// Licensed under the MIT license.
import { Constants } from '../Constants';
import { Validator } from './validator';
export namespace UrlValidator {
export const urlValidationExpression = new RegExp(
/^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w.-]+)+[\w\-._~:/?#[\]@!$&'()*+,;=]+$/gm,
);
export function validateHostUrl(url: string): string | null {
const matches = url.match(UrlValidator.urlValidationExpression);
if (matches === null || matches.length > 1) {
return Constants.validationMessages.incorrectHostAddress;
}
const validator = new Validator(url)
.isNotEmpty()
.isUrl();
return null;
return validator.getErrors();
}
export function splitUrl(url: string): string[] {
const address = url.replace(/(^\w+:|^)\/\//, '');
return address.split(':');
export function validatePort(port: string | number): string | null {
return `${port}`.match(Constants.validationRegexps.port) ? null : Constants.validationMessages.invalidPort;
}
}

Просмотреть файл

@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { Constants } from '../../Constants';
export class Debounce {
private debounceEvent: {
timeout?: NodeJS.Timeout,
resolve?: (value: string | null) => void,
} = {};
private timeout: number;
constructor(
options: { timeout: number } = { timeout: Constants.defaultDebounceTimeout },
) {
this.timeout = options.timeout;
}
public debounced(timeOverFunction: () => Promise<string | null>): Promise<string | null> {
if (this.debounceEvent.timeout) {
this.debounceEvent.resolve!(null);
clearTimeout(this.debounceEvent.timeout);
}
return new Promise<string | null>((resolve, reject) => {
this.debounceEvent.resolve = resolve;
this.debounceEvent.timeout = setTimeout(async () => {
try {
resolve(await timeOverFunction());
} catch (e) {
reject(e);
} finally {
this.debounceEvent.timeout = undefined;
this.debounceEvent.resolve = undefined;
}
}, this.timeout);
});
}
}

Просмотреть файл

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { Constants } from '../../Constants';
import { IRule } from '../validator';
export class HasDigit implements IRule {
public validate(value: string): string | null {
const hasDigit = value.search(Constants.validationRegexps.digits) !== -1;
return hasDigit ? null : Constants.validationMessages.noDigits;
}
}

Просмотреть файл

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { Constants } from '../../Constants';
import { IRule } from '../validator';
export class HasLowerCase implements IRule {
public validate(value: string): string | null {
const hasLowerCase = value.search(Constants.validationRegexps.lowerCaseLetter) !== -1;
return hasLowerCase ? null : Constants.validationMessages.noLowerCaseLetter;
}
}

Просмотреть файл

@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { Constants } from '../../Constants';
import { IRule } from '../validator';
export class HasNotUnallowedChar implements IRule {
constructor(private readonly unallowedChars: RegExp) {}
public validate(value: string): string | null {
const hasUnallowedChars = value.search(this.unallowedChars) !== -1;
return hasUnallowedChars ? Constants.validationMessages.unallowedChars : null;
}
}

Просмотреть файл

@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { Constants } from '../../Constants';
import { IRule } from '../validator';
export class HasSpecialChar implements IRule {
constructor(private readonly specialChars: RegExp) {}
public validate(value: string): string | null {
const hasSpecialChars = value.search(this.specialChars) !== -1;
return hasSpecialChars ? null : Constants.validationMessages.noSpecialChars;
}
}

Просмотреть файл

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { Constants } from '../../Constants';
import { IRule } from '../validator';
export class HasUpperCase implements IRule {
public validate(value: string): string | null {
const hasUpperCase = value.search(Constants.validationRegexps.upperCaseLetter) !== -1;
return hasUpperCase ? null : Constants.validationMessages.noUpperCaseLetter;
}
}

Просмотреть файл

@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
export * from './hasDigit';
export * from './hasLowerCase';
export * from './hasSpecialChar';
export * from './hasNotUnallowedChar';
export * from './hasUpperCase';
export * from './isAvailable';
export * from './isConfirmationValue';
export * from './isLowerCase';
export * from './isNotEmpty';
export * from './isUrl';
export * from './lengthRange';

Просмотреть файл

@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { IRule } from '../validator';
export class IsAvailable implements IRule {
constructor(
private readonly checkAvailable: (name: string) => Promise<{
message: string | null,
nameAvailable: boolean,
reason: string,
} | boolean>,
private readonly errorMessage?: (error: string) => string,
) {}
public async validate(name: string): Promise<string | null> {
let nameAvailable: boolean;
let message: string = '';
if (this.errorMessage) {
nameAvailable = !await this.checkAvailable(name) as boolean;
} else {
const response = await this.checkAvailable(name) as { message: string, nameAvailable: boolean };
nameAvailable = response.nameAvailable;
message = response.message;
}
return nameAvailable ? null : (this.errorMessage && this.errorMessage(name)) || message;
}
}

Просмотреть файл

@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { Constants } from '../../Constants';
import { IRule } from '../validator';
export class IsConfirmationValue implements IRule {
private readonly yesNoOptions: string[];
constructor() {
this.yesNoOptions = [
Constants.confirmationDialogResult.yes,
Constants.confirmationDialogResult.no,
];
}
public validate(value: string): string | null {
const isConfirmationValue = this.yesNoOptions.includes(value.toLowerCase());
return isConfirmationValue ? null : Constants.validationMessages.invalidConfirmationResult;
}
}

Просмотреть файл

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { Constants } from '../../Constants';
import { IRule } from '../validator';
export class IsLowerCase implements IRule {
public validate(value: string): string | null {
const isLowerCase = value.search(Constants.validationRegexps.isLowerCase) !== -1;
return isLowerCase || !value ? null : Constants.validationMessages.onlyLowerCaseAllowed;
}
}

Просмотреть файл

@ -0,0 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { Constants } from '../../Constants';
import { IRule } from '../validator';
export class IsNotEmpty implements IRule {
public validate(name: string): string | null {
return !!name.trim() ? null : Constants.validationMessages.valueCannotBeEmpty;
}
}

Просмотреть файл

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { Constants } from '../../Constants';
import { IRule } from '../validator';
export class IsUrl implements IRule {
public validate(value: string): string | null {
const isUrl = value.search(Constants.validationRegexps.isUrl) !== -1;
return isUrl ? null : Constants.validationMessages.invalidHostAddress;
}
}

Просмотреть файл

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { Constants } from '../../Constants';
import { IRule } from '../validator';
export class LengthRange implements IRule {
constructor(
private readonly min: number,
private readonly max: number,
) {}
public validate(value: string): string | null {
const inRange = value.length >= this.min && value.length <= this.max;
return inRange ? null : Constants.validationMessages.lengthRange(this.min, this.max);
}
}

112
src/validators/validator.ts Normal file
Просмотреть файл

@ -0,0 +1,112 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import {
HasDigit,
HasLowerCase,
HasNotUnallowedChar,
HasSpecialChar,
HasUpperCase,
IsAvailable,
IsConfirmationValue,
IsLowerCase,
IsNotEmpty,
IsUrl,
LengthRange,
} from './validationFunctions';
export interface IRule {
validate(value: string): string | null | Promise<string | null>;
}
export class Validator {
private errors: Set<string> = new Set();
constructor(private readonly value: string) {}
public getErrors(): string | null {
return Array.from(this.errors).join('\r\n') || null;
}
public hasLowerCase(): Validator {
this.validateSync(new HasLowerCase());
return this;
}
public hasUpperCase(): Validator {
this.validateSync(new HasUpperCase());
return this;
}
public isConfirmationValue(): Validator {
this.validateSync(new IsConfirmationValue());
return this;
}
public isLowerCase(): Validator {
this.validateSync(new IsLowerCase());
return this;
}
public isUrl(): Validator {
this.validateSync(new IsUrl());
return this;
}
public hasDigit(): Validator {
this.validateSync(new HasDigit());
return this;
}
public hasSpecialChar(specialChars: RegExp): Validator {
this.validateSync(new HasSpecialChar(specialChars));
return this;
}
public hasNotUnallowedChar(unallowedChars: RegExp): Validator {
this.validateSync(new HasNotUnallowedChar(unallowedChars));
return this;
}
public inLengthRange(min: number, max: number): Validator {
this.validateSync(new LengthRange(min, max));
return this;
}
public isNotEmpty(): Validator {
this.validateSync(new IsNotEmpty());
return this;
}
public async isAvailable(
checkAvailable: (name: string) => Promise<{
message: string | null,
nameAvailable: boolean,
reason: string,
} | boolean>,
errorMessage?: (error: string) => string,
): Promise<Validator> {
await this.validate(new IsAvailable(
checkAvailable,
errorMessage,
));
return this;
}
private validateSync(fn: IRule): void {
const error = fn.validate(this.value) as string | null;
if (error) {
this.errors.add(error);
}
}
private async validate(fn: IRule): Promise<void> {
const error = await fn.validate(this.value);
if (error) {
this.errors.add(error);
}
}
}

Просмотреть файл

@ -0,0 +1,712 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as assert from 'assert';
import { ServiceClientCredentials } from 'ms-rest';
import * as msrestazure from 'ms-rest-azure';
import * as sinon from 'sinon';
import * as uuid from 'uuid';
import * as vscode from 'vscode';
import { AzureBlockchainServiceClient } from '../src/ARMBlockchain';
import { Constants } from '../src/Constants';
import { vscodeEnvironment } from '../src/helpers';
import { Output } from '../src/Output';
describe('AzureBlockchainServiceClient', () => {
let credentials: ServiceClientCredentials;
let subscriptionId: string;
let resourceGroup: string;
let baseUri: string;
let apiVersion: string;
let memberName: string;
const azureBlockchainServiceClient = require('../src/ARMBlockchain/AzureBlockchainServiceClient');
const defaultResponseBody = '{ "message": "default response body" }';
let callbackFunction: (error: Error | null, result?: any) => void;
before(() => {
credentials = {
signRequest: () => undefined,
};
callbackFunction = (_error: Error | null, _result?: any) => undefined;
});
beforeEach(() => {
subscriptionId = uuid.v4();
resourceGroup = uuid.v4();
baseUri = uuid.v4();
apiVersion = uuid.v4();
memberName = uuid.v4();
sinon.stub(azureBlockchainServiceClient.__proto__, 'constructor');
});
afterEach(() => {
sinon.restore();
});
describe('Unit tests', () => {
let options: msrestazure.AzureServiceClientOptions;
let body: string;
let resultElement: string;
before(() => {
options = {};
body = uuid.v4();
resultElement = uuid.v4();
});
describe('constructor', () => {
it('AzureBlockchainServiceClient was created.', async () => {
// Arrange, Act
const serviceClient = new azureBlockchainServiceClient.AzureBlockchainServiceClient(
credentials,
subscriptionId,
resourceGroup,
baseUri,
apiVersion,
options,
);
// Assert
assert.notStrictEqual(serviceClient, undefined);
assert.strictEqual(serviceClient.constructor.name, AzureBlockchainServiceClient.name);
});
describe('invalid subscriptionId', () => {
const invalidSubscriptions = [String.Empty, null, undefined, ''];
invalidSubscriptions.forEach(async (subscription) => {
it(`AzureBlockchainServiceClient constructor throws error when subscriptionId is ${subscription}.`,
async () => {
// Arrange
let serviceClient;
// Act
const action = () => {
serviceClient = new azureBlockchainServiceClient.AzureBlockchainServiceClient(
credentials,
subscription,
resourceGroup,
baseUri,
apiVersion,
options,
);
};
// Assert
assert.throws(
action,
Error,
Constants.errorMessageStrings.VariableDoesNotExist(Constants.serviceClientVariables.subscriptionId));
assert.strictEqual(serviceClient, undefined);
});
});
});
describe('invalid credentials', () => {
const invalidCredentials = [null, undefined];
invalidCredentials.forEach(async (credential) => {
it(`AzureBlockchainServiceClient constructor throws error when credentials is ${credential}.`,
async () => {
// Arrange
let serviceClient;
// Act
const action = () => {
serviceClient = new azureBlockchainServiceClient.AzureBlockchainServiceClient(
credential,
subscriptionId,
resourceGroup,
baseUri,
apiVersion,
options,
);
};
// Assert
assert.throws(
action,
Error,
Constants.errorMessageStrings.VariableDoesNotExist(Constants.serviceClientVariables.credentials));
assert.strictEqual(serviceClient, undefined);
});
});
});
});
describe('Public methods.', () => {
let serviceClient: AzureBlockchainServiceClient;
let pipelineMock: sinon.SinonStub<any[], any> | sinon.SinonStub<unknown[], {}>;
let sendRequestToAzureMock: sinon.SinonStub<any[], any>;
let outputMock: sinon.SinonMock;
let outputLineMock: sinon.SinonExpectation;
let openExternalSpy: sinon.SinonSpy<[vscode.Uri], Thenable<boolean>>;
let windowMock: sinon.SinonMock;
let showErrorMessageMock: sinon.SinonExpectation;
beforeEach(() => {
serviceClient = new azureBlockchainServiceClient.AzureBlockchainServiceClient(
credentials,
subscriptionId,
resourceGroup,
baseUri,
apiVersion,
options,
);
// @ts-ignore
pipelineMock = sinon.stub(serviceClient, 'pipeline');
sendRequestToAzureMock = sinon.stub(serviceClient, 'sendRequestToAzure' as any);
openExternalSpy = sinon.stub(vscodeEnvironment, 'openExternal');
outputMock = sinon.mock(Output);
outputLineMock = outputMock.expects('outputLine');
windowMock = sinon.mock(vscode.window);
showErrorMessageMock = windowMock.expects('showErrorMessage');
});
afterEach(() => {
sendRequestToAzureMock.restore();
pipelineMock.restore();
openExternalSpy.restore();
outputMock.restore();
windowMock.restore();
});
it('createConsortium shows error when request failed.', async () => {
// Arrange
const response = sinon.stub();
const error = { message: uuid.v4() };
pipelineMock.callsFake((...args: any[]): {} => {
return args[1](error, response, defaultResponseBody);
});
// Act
await serviceClient.createConsortium(memberName, body);
// Assert
assert.strictEqual(pipelineMock.calledOnce, true);
assert.strictEqual(outputLineMock
.calledOnceWithExactly(Constants.outputChannel.azureBlockchainServiceClient, error.message), true);
assert.strictEqual(openExternalSpy.notCalled, true);
});
describe('createConsortium shows error when response is not successed.', () => {
const statusCodes = [103, 300, 400, 498];
statusCodes.forEach(async (statusCode) => {
it(`response status code is ${statusCode}.`, async () => {
// Arrange
const response = {statusCode, statusMessage: uuid.v4()};
let callbackSpy: any;
pipelineMock.callsFake((...args: any[]): {} => {
callbackSpy = sinon.spy(args[1]);
return callbackSpy(null, response, defaultResponseBody);
});
// Act
await serviceClient.createConsortium(memberName, body);
// Assert
assert.strictEqual(pipelineMock.calledOnce, true);
assert.strictEqual(callbackSpy.args[0][0], null);
assert.strictEqual(outputLineMock
.calledOnceWithExactly(
Constants.outputChannel.azureBlockchainServiceClient,
`${response.statusMessage}(${response.statusCode}): ${defaultResponseBody}`),
true);
assert.strictEqual(openExternalSpy.notCalled, true);
assert.strictEqual(showErrorMessageMock.calledOnceWithExactly(
Constants.executeCommandMessage.failedToRunCommand('CreateConsortium')), true);
});
});
});
describe('createConsortium does not show error when response is successed.', () => {
const statusCodes = [200, 207, 226];
statusCodes.forEach(async (statusCode) => {
it(`response status code is ${statusCode}.`, async () => {
// Arrange
const response = {statusCode, statusMessage: uuid.v4()};
let callbackSpy: any;
pipelineMock.callsFake((...args: any[]): {} => {
callbackSpy = sinon.spy(args[1]);
return callbackSpy(null, response, defaultResponseBody);
});
// Act
await serviceClient.createConsortium(memberName, body);
// Assert
assert.strictEqual(pipelineMock.calledOnce, true);
assert.strictEqual(callbackSpy.args[0][0], null);
assert.strictEqual(outputLineMock.notCalled, true);
assert.strictEqual(showErrorMessageMock.notCalled, true);
assert.strictEqual(openExternalSpy.calledOnce, true);
assert.strictEqual(openExternalSpy.args[0][0] instanceof vscode.Uri, true);
});
});
});
it('getMembers returns error.', async () => {
// Arrange
const callbackFunctionSpy = sinon.spy(callbackFunction);
const error = new Error(uuid.v4());
sendRequestToAzureMock.callsFake((...args: any[]): {} => {
return args[1](error);
});
// Act
await serviceClient.getMembers(callbackFunctionSpy);
// Assert
assert.strictEqual(sendRequestToAzureMock.calledOnce, true);
assert.strictEqual(callbackFunctionSpy.calledOnceWithExactly(error), true);
});
it('getMembers does not return error.', async () => {
// Arrange
const callbackFunctionSpy = sinon.spy(callbackFunction);
sendRequestToAzureMock.callsFake((...args: any[]): {} => {
return args[1](null, resultElement);
});
// Act
await serviceClient.getMembers(callbackFunctionSpy);
// Assert
assert.strictEqual(sendRequestToAzureMock.calledOnce, true);
assert.strictEqual(callbackFunctionSpy.calledOnceWithExactly(null, resultElement), true);
});
it('getTransactionNodes returns error.', async () => {
// Arrange
const callbackFunctionSpy = sinon.spy(callbackFunction);
const error = new Error(uuid.v4());
sendRequestToAzureMock.callsFake((...args: any[]): {} => {
return args[1](error);
});
// Act
await serviceClient.getTransactionNodes(memberName, callbackFunctionSpy);
// Assert
assert.strictEqual(sendRequestToAzureMock.calledOnce, true);
assert.strictEqual(callbackFunctionSpy.calledOnceWithExactly(error), true);
});
it('getTransactionNodes does not return error.', async () => {
// Arrange
const callbackFunctionSpy = sinon.spy(callbackFunction);
sendRequestToAzureMock.callsFake((...args: any[]): {} => {
return args[1](null, resultElement);
});
// Act
await serviceClient.getTransactionNodes(memberName, callbackFunctionSpy);
// Assert
assert.strictEqual(sendRequestToAzureMock.calledOnce, true);
assert.strictEqual(callbackFunctionSpy.calledOnceWithExactly(null, resultElement), true);
});
it('getMemberAccessKeys returns error.', async () => {
// Arrange
const callbackFunctionSpy = sinon.spy(callbackFunction);
const error = new Error(uuid.v4());
sendRequestToAzureMock.callsFake((...args: any[]): {} => {
return args[1](error);
});
// Act
await serviceClient.getMemberAccessKeys(memberName, callbackFunctionSpy);
// Assert
assert.strictEqual(sendRequestToAzureMock.calledOnce, true);
assert.strictEqual(callbackFunctionSpy.calledOnceWithExactly(error), true);
});
it('getMemberAccessKeys does not return error.', async () => {
// Arrange
const callbackFunctionSpy = sinon.spy(callbackFunction);
sendRequestToAzureMock.callsFake((...args: any[]): {} => {
return args[1](null, resultElement);
});
// Act
await serviceClient.getMemberAccessKeys(memberName, callbackFunctionSpy);
// Assert
assert.strictEqual(sendRequestToAzureMock.calledOnce, true);
assert.strictEqual(callbackFunctionSpy.calledOnceWithExactly(null, resultElement), true);
});
it('getSkus returns error.', async () => {
// Arrange
const callbackFunctionSpy = sinon.spy(callbackFunction);
const error = new Error(uuid.v4());
sendRequestToAzureMock.callsFake((...args: any[]): {} => {
return args[1](error);
});
// Act
await serviceClient.getSkus(callbackFunctionSpy);
// Assert
assert.strictEqual(sendRequestToAzureMock.calledOnce, true);
assert.strictEqual(callbackFunctionSpy.calledOnceWithExactly(error), true);
});
it('getSkus does not return error.', async () => {
// Arrange
const callbackFunctionSpy = sinon.spy(callbackFunction);
sendRequestToAzureMock.callsFake((...args: any[]): {} => {
return args[1](null, resultElement);
});
// Act
await serviceClient.getSkus(callbackFunctionSpy);
// Assert
assert.strictEqual(sendRequestToAzureMock.calledOnce, true);
assert.strictEqual(callbackFunctionSpy.calledOnceWithExactly(null, resultElement), true);
});
});
});
describe('Integration tests', () => {
const incorrectResponseBody = uuid.v4();
let windowMock: sinon.SinonMock;
let showErrorMessageMock: sinon.SinonExpectation;
let pipelineMock: sinon.SinonStub<any[], any> | sinon.SinonStub<unknown[], {}>;
let serviceClient: AzureBlockchainServiceClient;
beforeEach(() => {
windowMock = sinon.mock(vscode.window);
showErrorMessageMock = windowMock.expects('showErrorMessage');
const specialOptions = {
acceptLanguage: uuid.v4(),
generateClientRequestId: true,
};
serviceClient = new azureBlockchainServiceClient.AzureBlockchainServiceClient(
credentials,
subscriptionId,
resourceGroup,
baseUri,
apiVersion,
specialOptions,
);
// @ts-ignore
pipelineMock = sinon.stub(serviceClient, 'pipeline');
});
afterEach(() => {
windowMock.restore();
pipelineMock.restore();
});
describe('getMembers', () => {
it('shows error when request failed.', async () => {
// Arrange
const response = sinon.stub();
const error = { message: uuid.v4() };
const callbackFunctionSpy = sinon.spy(callbackFunction);
pipelineMock.callsFake((...args: any[]): {} => {
return args[1](error, response, defaultResponseBody);
});
// Act
await serviceClient.getMembers(callbackFunctionSpy);
// Assert
assert.strictEqual(pipelineMock.calledOnce, true);
assert.strictEqual(showErrorMessageMock.calledOnceWithExactly(error.message), true);
assert.strictEqual(callbackFunctionSpy.calledOnceWithExactly(error as Error), true);
});
describe('throws error when response is not successed.', () => {
const responseData = [
[400, defaultResponseBody],
[103, defaultResponseBody],
[530, defaultResponseBody],
[200, incorrectResponseBody]];
responseData.forEach(async (response) => {
it(`response status code is ${response[0]} and response body is ${JSON.stringify(response[1])}.`,
async () => {
// Arrange
const res = {statusCode: response[0]};
const callbackFunctionSpy = sinon.spy(callbackFunction);
let pipelineCallbackSpy: any;
pipelineMock.callsFake((...args: any[]): {} => {
pipelineCallbackSpy = sinon.spy(args[1]);
return pipelineCallbackSpy(null, res, response[1]);
});
// Act
await serviceClient.getMembers(callbackFunctionSpy);
// Assert
assert.strictEqual(pipelineMock.calledOnce, true);
assert.strictEqual(showErrorMessageMock.notCalled, true);
assert.strictEqual(callbackFunctionSpy.calledOnce, true);
assert.strictEqual(callbackFunctionSpy.args[0][0] instanceof Error, true);
assert.strictEqual(pipelineCallbackSpy.calledOnce, true);
assert.strictEqual(pipelineCallbackSpy.args[0][0], null);
});
});
});
it('does not throw error when response is successed.', async () => {
// Arrange
const res = {statusCode: 200};
const callbackFunctionSpy = sinon.spy(callbackFunction);
const parsedResult = JSON.parse(defaultResponseBody);
let pipelineCallbackSpy: any;
pipelineMock.callsFake((...args: any[]): {} => {
pipelineCallbackSpy = sinon.spy(args[1]);
return pipelineCallbackSpy(null, res, defaultResponseBody);
});
// Act
await serviceClient.getMembers(callbackFunctionSpy);
// Assert
assert.strictEqual(pipelineMock.calledOnce, true);
assert.strictEqual(showErrorMessageMock.notCalled, true);
assert.strictEqual(callbackFunctionSpy.calledOnceWithExactly(null, parsedResult), true);
assert.strictEqual(pipelineCallbackSpy.calledOnce, true);
assert.strictEqual(pipelineCallbackSpy.args[0][0], null);
});
});
describe('getTransactionNodes', () => {
it('shows error when request failed.', async () => {
// Arrange
const response = sinon.stub();
const error = { message: uuid.v4() };
const callbackFunctionSpy = sinon.spy(callbackFunction);
pipelineMock.callsFake((...args: any[]): {} => {
return args[1](error, response, defaultResponseBody);
});
// Act
await serviceClient.getTransactionNodes(memberName, callbackFunctionSpy);
// Assert
assert.strictEqual(pipelineMock.calledOnce, true);
assert.strictEqual(showErrorMessageMock.calledOnceWithExactly(error.message), true);
assert.strictEqual(callbackFunctionSpy.calledOnceWithExactly(error as Error), true);
});
describe('throws error when response is not successed.', () => {
const responseData = [
[400, defaultResponseBody],
[103, defaultResponseBody],
[530, defaultResponseBody],
[200, incorrectResponseBody]];
responseData.forEach(async (response) => {
it(`response status code is ${response[0]} and response body is ${JSON.stringify(response[1])}.`,
async () => {
// Arrange
const res = {statusCode: response[0]};
const callbackFunctionSpy = sinon.spy(callbackFunction);
let pipelineCallbackSpy: any;
pipelineMock.callsFake((...args: any[]): {} => {
pipelineCallbackSpy = sinon.spy(args[1]);
return pipelineCallbackSpy(null, res, response[1]);
});
// Act
await serviceClient.getTransactionNodes(memberName, callbackFunctionSpy);
// Assert
assert.strictEqual(pipelineMock.calledOnce, true);
assert.strictEqual(showErrorMessageMock.notCalled, true);
assert.strictEqual(callbackFunctionSpy.calledOnce, true);
assert.strictEqual(callbackFunctionSpy.args[0][0] instanceof Error, true);
assert.strictEqual(pipelineCallbackSpy.calledOnce, true);
assert.strictEqual(pipelineCallbackSpy.args[0][0], null);
});
});
});
it('does not throw error when response is successed.', async () => {
// Arrange
const res = {statusCode: 200};
const callbackFunctionSpy = sinon.spy(callbackFunction);
const parsedResult = JSON.parse(defaultResponseBody);
let pipelineCallbackSpy: any;
pipelineMock.callsFake((...args: any[]): {} => {
pipelineCallbackSpy = sinon.spy(args[1]);
return pipelineCallbackSpy(null, res, defaultResponseBody);
});
// Act
await serviceClient.getTransactionNodes(memberName, callbackFunctionSpy);
// Assert
assert.strictEqual(pipelineMock.calledOnce, true);
assert.strictEqual(showErrorMessageMock.notCalled, true);
assert.strictEqual(callbackFunctionSpy.calledOnceWithExactly(null, parsedResult), true);
assert.strictEqual(pipelineCallbackSpy.calledOnce, true);
assert.strictEqual(pipelineCallbackSpy.args[0][0], null);
});
});
describe('getMemberAccessKeys', () => {
it('shows error when request failed.', async () => {
// Arrange
const response = sinon.stub();
const error = { message: uuid.v4() };
const callbackFunctionSpy = sinon.spy(callbackFunction);
pipelineMock.callsFake((...args: any[]): {} => {
return args[1](error, response, defaultResponseBody);
});
// Act
await serviceClient.getMemberAccessKeys(memberName, callbackFunctionSpy);
// Assert
assert.strictEqual(pipelineMock.calledOnce, true);
assert.strictEqual(showErrorMessageMock.calledOnceWithExactly(error.message), true);
assert.strictEqual(callbackFunctionSpy.calledOnceWithExactly(error as Error), true);
});
describe('throws error when response is not successed.', () => {
const responseData = [
[400, defaultResponseBody],
[103, defaultResponseBody],
[530, defaultResponseBody],
[200, incorrectResponseBody]];
responseData.forEach(async (response) => {
it(`response status code is ${response[0]} and response body is ${JSON.stringify(response[1])}.`,
async () => {
// Arrange
const res = {statusCode: response[0]};
const callbackFunctionSpy = sinon.spy(callbackFunction);
let pipelineCallbackSpy: any;
pipelineMock.callsFake((...args: any[]): {} => {
pipelineCallbackSpy = sinon.spy(args[1]);
return pipelineCallbackSpy(null, res, response[1]);
});
// Act
await serviceClient.getMemberAccessKeys(memberName, callbackFunctionSpy);
// Assert
assert.strictEqual(pipelineMock.calledOnce, true);
assert.strictEqual(showErrorMessageMock.notCalled, true);
assert.strictEqual(callbackFunctionSpy.calledOnce, true);
assert.strictEqual(callbackFunctionSpy.args[0][0] instanceof Error, true);
assert.strictEqual(pipelineCallbackSpy.calledOnce, true);
assert.strictEqual(pipelineCallbackSpy.args[0][0], null);
});
});
});
it('does not throw error when response is successed.', async () => {
// Arrange
const res = {statusCode: 200};
const callbackFunctionSpy = sinon.spy(callbackFunction);
const parsedResult = JSON.parse(defaultResponseBody);
let pipelineCallbackSpy: any;
pipelineMock.callsFake((...args: any[]): {} => {
pipelineCallbackSpy = sinon.spy(args[1]);
return pipelineCallbackSpy(null, res, defaultResponseBody);
});
// Act
await serviceClient.getMemberAccessKeys(memberName, callbackFunctionSpy);
// Assert
assert.strictEqual(pipelineMock.calledOnce, true);
assert.strictEqual(showErrorMessageMock.notCalled, true);
assert.strictEqual(callbackFunctionSpy.calledOnceWithExactly(null, parsedResult), true);
assert.strictEqual(pipelineCallbackSpy.calledOnce, true);
assert.strictEqual(pipelineCallbackSpy.args[0][0], null);
});
});
describe('getSkus', () => {
it('shows error when request failed.', async () => {
// Arrange
const response = sinon.stub();
const error = { message: uuid.v4() };
const callbackFunctionSpy = sinon.spy(callbackFunction);
pipelineMock.callsFake((...args: any[]): {} => {
return args[1](error, response, defaultResponseBody);
});
// Act
await serviceClient.getSkus(callbackFunctionSpy);
// Assert
assert.strictEqual(pipelineMock.calledOnce, true);
assert.strictEqual(showErrorMessageMock.calledOnceWithExactly(error.message), true);
assert.strictEqual(callbackFunctionSpy.calledOnceWithExactly(error as Error), true);
});
describe('throws error when response is not successed.', () => {
const responseData = [
[400, defaultResponseBody],
[103, defaultResponseBody],
[530, defaultResponseBody],
[200, incorrectResponseBody]];
responseData.forEach(async (response) => {
it(`response status code is ${response[0]} and response body is ${JSON.stringify(response[1])}.`,
async () => {
// Arrange
const res = {statusCode: response[0]};
const callbackFunctionSpy = sinon.spy(callbackFunction);
let pipelineCallbackSpy: any;
pipelineMock.callsFake((...args: any[]): {} => {
pipelineCallbackSpy = sinon.spy(args[1]);
return pipelineCallbackSpy(null, res, response[1]);
});
// Act
await serviceClient.getSkus(callbackFunctionSpy);
// Assert
assert.strictEqual(pipelineMock.calledOnce, true);
assert.strictEqual(showErrorMessageMock.notCalled, true);
assert.strictEqual(callbackFunctionSpy.calledOnce, true);
assert.strictEqual(callbackFunctionSpy.args[0][0] instanceof Error, true);
assert.strictEqual(pipelineCallbackSpy.calledOnce, true);
assert.strictEqual(pipelineCallbackSpy.args[0][0], null);
});
});
});
it('does not throw error when response is successed.', async () => {
// Arrange
const res = {statusCode: 200};
const callbackFunctionSpy = sinon.spy(callbackFunction);
const parsedResult = JSON.parse(defaultResponseBody);
let pipelineCallbackSpy: any;
pipelineMock.callsFake((...args: any[]): {} => {
pipelineCallbackSpy = sinon.spy(args[1]);
return pipelineCallbackSpy(null, res, defaultResponseBody);
});
// Act
await serviceClient.getSkus(callbackFunctionSpy);
// Assert
assert.strictEqual(pipelineMock.calledOnce, true);
assert.strictEqual(showErrorMessageMock.notCalled, true);
assert.strictEqual(callbackFunctionSpy.calledOnceWithExactly(null, parsedResult), true);
assert.strictEqual(pipelineCallbackSpy.calledOnce, true);
assert.strictEqual(pipelineCallbackSpy.args[0][0], null);
});
});
});
});

Просмотреть файл

@ -0,0 +1,109 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as assert from 'assert';
import * as sinon from 'sinon';
import * as uuid from 'uuid';
import { extensions, QuickPickOptions } from 'vscode';
import { IAzureMemberDto, IAzureTransactionNodeDto, MemberResource } from '../src/ARMBlockchain';
import { TransactionNodeResource } from '../src/ARMBlockchain/Operations/TransactionNodeResource';
import { ConsortiumResourceExplorer } from '../src/ConsortiumResourceExplorer';
import { Constants } from '../src/Constants';
import * as helpers from '../src/helpers';
import {
ResourceGroupItem,
SubscriptionItem,
} from '../src/Models';
import { ConsortiumItem } from '../src/Models/ConsortiumItem';
describe('Consortium Resource Explorer', () => {
afterEach(() => {
sinon.restore();
});
const getListMemberStub = async (): Promise<IAzureMemberDto[]> => [];
const getListTransactionNodeStub = async (): Promise<IAzureTransactionNodeDto[]> => [];
const azureSession = {
credentials: {
signRequest(): void { return; },
},
environment: {
activeDirectoryEndpointUrl: 'string',
activeDirectoryGraphApiVersion: 'string',
activeDirectoryGraphResourceId: 'string',
activeDirectoryResourceId: 'string',
azureDataLakeAnalyticsCatalogAndJobEndpointSuffix: 'string',
azureDataLakeStoreFileSystemEndpointSuffix: 'string',
galleryEndpointUrl: 'string',
keyVaultDnsSuffix: 'string',
managementEndpointUrl: 'string',
mentEndpointUrl: 'string',
name: 'string',
portalUrl: 'string',
publishingProfileUrl: 'string',
resourceManagerEndpointUrl: 'string',
sqlManagementEndpointUrl: '',
sqlServerHostnameSuffix: 'string',
storageEndpointSuffix: 'string',
validateAuthority: true,
},
tenantId: 'string',
userId: 'string',
};
const filter = {
session: '',
subscription: { displayName: '', subscriptionId: '' },
};
const accountApi = {
async waitForLogin(): Promise<boolean> {
return true;
},
async waitForFilters(): Promise<boolean> {
return true;
},
filters: [filter],
};
const subscriptionItem = new SubscriptionItem('', uuid.v4(), azureSession);
const resourceGroupItem = new ResourceGroupItem();
const consortiumItem = new ConsortiumItem('', '', '', '');
const showQuickPickStub = sinon.stub();
showQuickPickStub
.onCall(0)
.returns(subscriptionItem);
showQuickPickStub
.onCall(1)
.returns(resourceGroupItem as ResourceGroupItem);
showQuickPickStub
.onCall(2)
.returns(consortiumItem as ConsortiumItem);
describe('ConsortiumResourceExplorer.SelectOrCreateConsortium', () => {
it('SelectOrCreateConsortium all method should be executed',
async () => {
// Arrange
sinon.stub(MemberResource.prototype, 'getListMember')
.returns(getListMemberStub());
sinon.stub(TransactionNodeResource.prototype, 'getListTransactionNode')
.returns(getListTransactionNodeStub());
sinon.replace(helpers, 'showQuickPick', showQuickPickStub);
const getExtensionFake = sinon.fake.returns({ exports: accountApi });
sinon.replace(extensions, 'getExtension', getExtensionFake);
// Act
await (new ConsortiumResourceExplorer()).selectOrCreateConsortium();
// Assert
assert.strictEqual(
(showQuickPickStub.getCall(0).args[1] as QuickPickOptions).placeHolder,
Constants.placeholders.selectSubscription,
);
assert.strictEqual(
(showQuickPickStub.getCall(1).args[1] as QuickPickOptions).placeHolder,
Constants.placeholders.selectResourceGroup,
);
});
});
});

Просмотреть файл

@ -0,0 +1,16 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as assert from 'assert';
import rewire = require('rewire');
describe('ConsortiumTreeManager tests', () => {
it('defaultNetworksItems should return array with 4 elements', async () => {
const treeManagerRewire = rewire('../src/treeService/ConsortiumTreeManager');
const defaultNetworksItems = treeManagerRewire.__get__('defaultNetworksItems');
const result = defaultNetworksItems();
assert.strictEqual(result.length, 4);
});
});

Просмотреть файл

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { Memento } from 'vscode';
export class FakeExtensionState implements Memento {
constructor(private dict: { [id: string]: any} ) {
dict = {};
}
public get<T>(key: string): T | undefined {
return this.dict[key] as T;
}
public update(key: string, value: any): Thenable<void> {
return this.dict[key] = value;
}
}

Просмотреть файл

@ -0,0 +1,149 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as assert from 'assert';
import * as fs from 'fs';
import * as os from 'os';
import * as sinon from 'sinon';
import uuid = require('uuid');
import { Memento } from 'vscode';
import { Constants } from '../src/Constants';
import { MnemonicRepository } from '../src/MnemonicService/MnemonicRepository';
import { FakeExtensionState } from './FakeExtensionState';
import { TestConstants } from './TestConstants';
describe('MnemonicRepository', () => {
describe('Unit test', () => {
let readFileSyncMock: any;
let existsSyncMock: any;
let globalState: Memento;
beforeEach(() => {
readFileSyncMock = sinon.stub(fs, 'readFileSync');
existsSyncMock = sinon.stub(fs, 'existsSync');
// Clean state before run test
globalState = new FakeExtensionState({});
MnemonicRepository.initialize(globalState);
});
afterEach(() => {
sinon.restore();
// Clean state after run test
globalState = new FakeExtensionState({});
MnemonicRepository.initialize(globalState);
});
const separators: string [] = [' ', os.EOL, ' '];
const trimmedString = uuid.v4();
let testString = trimmedString;
separators.forEach((element, index) => {
it(`GetMnemonic should return trimmed content ${index + 1}`, () => {
// Arrange
const filePath = uuid.v4();
testString = `${element}${testString}${element}`;
readFileSyncMock.returns(testString);
// Act
const result = MnemonicRepository.getMnemonic(filePath);
// Assert
assert.strictEqual(result, trimmedString);
assert.strictEqual(readFileSyncMock.calledOnce, true);
});
});
it('GetMnemonic should throw exception when file not exists', () => {
// Arrange
const filePath = uuid.v4();
readFileSyncMock.throws(TestConstants.testError);
// Act
try {
MnemonicRepository.getMnemonic(filePath);
assert.fail(TestConstants.testShouldThrowError);
} catch (error) {
// Assert
assert.strictEqual(error.name, TestConstants.testError);
}
});
it('GetAllMnemonicPaths should return correct paths', () => {
// Arrange
const storage: string[] = [uuid.v4(), uuid.v4(), uuid.v4()];
globalState.update(Constants.mnemonicConstants.mnemonicStorage, storage);
// Act
const result = MnemonicRepository.getAllMnemonicPaths();
// Assert
assert.strictEqual(result.length, storage.length);
assert.deepEqual(result, storage);
});
it('getExistedMnemonicPaths should return existing paths', () => {
// Arrange
const storage: string[] = [uuid.v4(), uuid.v4(), uuid.v4()];
globalState.update(Constants.mnemonicConstants.mnemonicStorage, storage);
existsSyncMock.onCall(0).callsFake(() => true);
existsSyncMock.onCall(1).callsFake(() => false);
existsSyncMock.onCall(2).callsFake(() => true);
// Act
const result = MnemonicRepository.getExistedMnemonicPaths();
// Assert
assert.strictEqual(result.length, 2);
assert.strictEqual(result[0], storage[0]);
assert.strictEqual(result[1], storage[2]);
});
it('saveMnemonicPath should update global state', () => {
// Arrange
const filePath = uuid.v4();
// Act
MnemonicRepository.saveMnemonicPath(filePath);
const result = globalState.get<string[]>(Constants.mnemonicConstants.mnemonicStorage) as string[];
// Assert
assert.strictEqual(result.length, 1);
assert.strictEqual(result[0], filePath);
});
it('MaskMnemonic should return short label', () => {
// Arrange
const expectedResult = 'tes ... est';
// Act
const result = MnemonicRepository.MaskMnemonic(TestConstants.testMnemonic);
// Assert
assert.notStrictEqual(result, TestConstants.testMnemonic);
assert.strictEqual(result, expectedResult);
});
it('MaskMnemonic should return default place holder', () => {
// Act
const result = MnemonicRepository.MaskMnemonic('');
// Assert
assert.strictEqual(result, Constants.placeholders.emptyLineText);
});
it('MaskMnemonic should return short label when mnemonic is short', () => {
// Arrange
const expectedResult = 'abc ... abc';
// Act
const result = MnemonicRepository.MaskMnemonic('abc');
// Assert
assert.strictEqual(result, expectedResult);
});
});
});

32
test/TestConstants.ts Normal file
Просмотреть файл

@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
export class TestConstants {
public static testError: string = 'TestError';
public static testShouldThrowError: string = 'Unexpected behaviour - should throw error';
public static testMnemonic: string = 'test test test test test test test test test test test test';
public static testDialogAnswers: string[] =
['yes', 'YES', 'Yes', 'YEs', 'YeS', 'yES', 'yEs', 'yeS', 'no', 'NO', 'No', 'nO'];
public static consortiumTestNames = {
local: 'localhost:1234',
publicEthereum: 'publicEthereum',
testEthereum: 'testEthereum',
};
public static networksNames = {
azureBlockchainService: 'azureBlockchainService',
development: 'development',
ethereumNetwork: 'EthereumNetwork',
ethereumTestnet: 'EthereumTestnet',
localNetwork: 'LocalNetwork',
testConsortium: 'testConsortium',
testMainNetwork: 'testMainNetwork',
testNetwork: 'testNetwork',
};
public static truffleCommandTestDataFolder: string = 'testData';
}

Просмотреть файл

@ -0,0 +1,66 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as assert from 'assert';
import * as fs from 'fs';
import * as path from 'path';
import * as vscode from 'vscode';
import { TruffleCommands } from '../../src/commands/TruffleCommands';
import { Constants } from '../../src/Constants';
describe('TruffleCommands', () => {
describe('Integration test', () => {
describe('Success path', () => {
const fileUri = {
fsPath: path.join(__dirname, 'testData', 'TestContract.json'),
} as vscode.Uri;
const testJson = fs.readFileSync(fileUri.fsPath, null);
const testJsonString = JSON.parse(testJson.toString());
it('writeBytecodeToBuffer should write correct bytecode to clipboard', async () => {
// Arrange
const testBytecode = testJsonString[Constants.contractProperties.bytecode];
// Act
await TruffleCommands.writeBytecodeToBuffer(fileUri);
// Assert
assert.strictEqual(await vscode.env.clipboard.readText(), testBytecode);
});
it('writeAbiToBuffer should write correct aby to clipboard', async () => {
// Arrange
const testAbi = JSON.stringify(testJsonString[Constants.contractProperties.abi]);
// Act
await TruffleCommands.writeAbiToBuffer(fileUri);
// Assert
assert.strictEqual(await vscode.env.clipboard.readText(), testAbi);
});
});
describe('Invalid cases', () => {
const fileUri = {
fsPath: path.join(__dirname, 'WriteToBuffer.test.ts'),
} as vscode.Uri;
it('writeBytecodeToBuffer throw error when uri is not JSON file', async () => {
// Act and assert
await assert.rejects(
TruffleCommands.writeBytecodeToBuffer(fileUri),
Error,
Constants.errorMessageStrings.InvalidContract);
});
it('writeAbiToBuffer throw error when uri is not JSON file', async () => {
// Act and assert
await assert.rejects(
TruffleCommands.writeAbiToBuffer(fileUri),
Error,
Constants.errorMessageStrings.InvalidContract);
});
});
});
});

Просмотреть файл

@ -0,0 +1,95 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as assert from 'assert';
import * as sinon from 'sinon';
import * as uuid from 'uuid';
import { TruffleCommands } from '../../src/commands/TruffleCommands';
import * as helpers from '../../src/helpers';
import * as commands from '../../src/helpers/command';
import { TestConstants } from '../TestConstants';
describe('BuildContracts Command', () => {
describe('Integration test', async () => {
let requiredMock: sinon.SinonMock;
let getWorkspaceRootMock: any;
let checkAppsSilent: sinon.SinonExpectation;
let installTruffle: sinon.SinonExpectation;
let commandContextMock: sinon.SinonMock;
let executeCommandMock: sinon.SinonExpectation;
beforeEach(() => {
requiredMock = sinon.mock(helpers.required);
getWorkspaceRootMock = sinon.stub(helpers, 'getWorkspaceRoot');
getWorkspaceRootMock.returns(uuid.v4());
checkAppsSilent = requiredMock.expects('checkAppsSilent');
installTruffle = requiredMock.expects('installTruffle');
commandContextMock = sinon.mock(commands);
executeCommandMock = commandContextMock.expects('executeCommand');
});
afterEach(() => {
sinon.restore();
});
it('should not throw exception when truffle already installed', async () => {
// Arrange
checkAppsSilent.returns(true);
executeCommandMock.returns(uuid.v4());
// Act
await TruffleCommands.buildContracts();
// Assert
assert.strictEqual(checkAppsSilent.calledOnce, true);
assert.strictEqual(getWorkspaceRootMock.calledOnce, true);
assert.strictEqual(installTruffle.called, false);
assert.strictEqual(executeCommandMock.called, true);
});
it('should not throw exception when truffle not installed', async () => {
// Arrange
checkAppsSilent.returns(false);
executeCommandMock.returns(uuid.v4());
// Act
await TruffleCommands.buildContracts();
// Assert
assert.strictEqual(checkAppsSilent.calledOnce, true);
assert.strictEqual(getWorkspaceRootMock.calledOnce, true);
assert.strictEqual(installTruffle.calledOnce, true);
assert.strictEqual(executeCommandMock.called, true);
});
it('should throw exception when truffle build failed', async () => {
// Arrange
checkAppsSilent.returns(true);
executeCommandMock.throws(TestConstants.testError);
// Act and assert
await assert.rejects(TruffleCommands.buildContracts(), Error, TestConstants.testError);
assert.strictEqual(checkAppsSilent.calledOnce, true);
assert.strictEqual(getWorkspaceRootMock.calledOnce, true);
assert.strictEqual(installTruffle.called, false);
assert.strictEqual(executeCommandMock.called, true);
});
it('should throw exception when truffle install failed', async () => {
// Arrange
checkAppsSilent.returns(false);
executeCommandMock.returns(uuid.v4());
installTruffle.throws(TestConstants.testError);
// Act and assert
await assert.rejects(TruffleCommands.buildContracts(), Error, TestConstants.testError);
assert.strictEqual(checkAppsSilent.calledOnce, true);
assert.strictEqual(getWorkspaceRootMock.called, false);
assert.strictEqual(installTruffle.called, true);
assert.strictEqual(executeCommandMock.called, false);
});
});
});

Просмотреть файл

@ -0,0 +1,906 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as assert from 'assert';
import * as fs from 'fs';
import * as path from 'path';
import * as sinon from 'sinon';
import uuid = require('uuid');
import * as vscode from 'vscode';
import { ExtensionContext } from 'vscode';
import { AzureAccount } from '../../src/azure-account.api';
import { TruffleCommands } from '../../src/commands/TruffleCommands';
import { ConsortiumResourceExplorer } from '../../src/ConsortiumResourceExplorer';
import { Constants } from '../../src/Constants';
import { GanacheService } from '../../src/GanacheService/GanacheService';
import * as helpers from '../../src/helpers';
import { TruffleConfiguration } from '../../src/helpers';
import * as commands from '../../src/helpers/command';
import * as workspace from '../../src/helpers/workspace';
import { MnemonicRepository } from '../../src/MnemonicService/MnemonicRepository';
import {
AzureConsortium,
CancellationEvent,
IExtensionItem,
ItemType,
LocalNetworkConsortium,
MainNetworkConsortium,
Network,
TestNetworkConsortium,
} from '../../src/Models';
import { ConsortiumTreeManager } from '../../src/treeService/ConsortiumTreeManager';
import { TestConstants } from '../TestConstants';
describe('TruffleCommands', () => {
describe('Integration test', async () => {
describe('deployContracts', () => {
let requiredMock: sinon.SinonMock;
let checkAppsSilent: sinon.SinonExpectation;
let installTruffle: sinon.SinonExpectation;
let getWorkspaceRootMock: any;
let windowMock: sinon.SinonMock;
let showQuickPickMock: any;
let showInputBoxMock: any;
let showSaveDialogMock: sinon.SinonExpectation;
let showTextDocumentMock: sinon.SinonExpectation;
let ganacheServiceMock: sinon.SinonMock;
let startGanacheServer: sinon.SinonExpectation;
let getItemsMock: sinon.SinonStub<[(boolean | undefined)?], IExtensionItem[]>;
let loadStateMock: sinon.SinonStub<[], IExtensionItem[]>;
let testConsortiumItems: Network[];
let truffleConfigSetNetworkMock: any;
let truffleConfigGetNetworkMock: any;
let truffleConfigGenerateMnemonicMock: any;
let consortiumTreeManager: ConsortiumTreeManager;
let commandContextMock: sinon.SinonMock;
let executeCommandMock: sinon.SinonExpectation;
let mnemonicRepositoryMock: sinon.SinonMock;
let getMnemonicMock: sinon.SinonStub<any[], any>;
let getAllMnemonicPathsMock: sinon.SinonStub<any[], any>;
let saveMnemonicPathMock: sinon.SinonExpectation;
let openTextDocumentMock: any;
let writeFileSyncMock: any;
let getAccessKeysMock: any;
let getExtensionMock: any;
beforeEach(async () => {
getWorkspaceRootMock = sinon.stub(workspace, 'getWorkspaceRoot');
requiredMock = sinon.mock(helpers.required);
checkAppsSilent = requiredMock.expects('checkAppsSilent');
installTruffle = requiredMock.expects('installTruffle');
windowMock = sinon.mock(vscode.window);
showQuickPickMock = sinon.stub(vscode.window, 'showQuickPick');
showInputBoxMock = sinon.stub(vscode.window, 'showInputBox');
showSaveDialogMock = windowMock.expects('showSaveDialog');
showTextDocumentMock = windowMock.expects('showTextDocument');
ganacheServiceMock = sinon.mock(GanacheService);
startGanacheServer = ganacheServiceMock.expects('startGanacheServer');
getItemsMock = sinon.stub(ConsortiumTreeManager.prototype, 'getItems');
loadStateMock = sinon.stub(ConsortiumTreeManager.prototype, 'loadState');
testConsortiumItems = await createTestConsortiumItems();
getItemsMock.returns(testConsortiumItems);
loadStateMock.returns(testConsortiumItems);
truffleConfigSetNetworkMock = sinon.stub(TruffleConfiguration.TruffleConfig.prototype, 'setNetworks');
truffleConfigGetNetworkMock = sinon.stub(TruffleConfiguration.TruffleConfig.prototype, 'getNetworks');
truffleConfigGetNetworkMock.returns(getTestTruffleNetworks());
truffleConfigGenerateMnemonicMock = sinon.stub(TruffleConfiguration, 'generateMnemonic');
truffleConfigGenerateMnemonicMock.returns(TestConstants.testMnemonic);
consortiumTreeManager = new ConsortiumTreeManager({} as ExtensionContext);
commandContextMock = sinon.mock(commands);
executeCommandMock = commandContextMock.expects('executeCommand');
mnemonicRepositoryMock = sinon.mock(MnemonicRepository);
getMnemonicMock = mnemonicRepositoryMock.expects('getMnemonic').returns(TestConstants.testMnemonic);
getAllMnemonicPathsMock = mnemonicRepositoryMock.expects('getAllMnemonicPaths').returns([] as string []);
saveMnemonicPathMock = mnemonicRepositoryMock.expects('saveMnemonicPath');
openTextDocumentMock = sinon.stub(vscode.workspace, 'openTextDocument');
writeFileSyncMock = sinon.stub(fs, 'writeFileSync');
getAccessKeysMock = sinon.stub(ConsortiumResourceExplorer.prototype, 'getAccessKeys');
getExtensionMock = sinon.stub(vscode.extensions, 'getExtension').returns(mockExtension);
});
afterEach(() => {
sinon.restore();
getWorkspaceRootMock.restore();
});
it('should throw exception when config file not found', async () => {
// Arrange
checkAppsSilent.returns(true);
getWorkspaceRootMock.returns(__dirname);
executeCommandMock.returns(uuid.v4());
// Act and assert
await assert.rejects(TruffleCommands.deployContracts(consortiumTreeManager),
Error,
Constants.errorMessageStrings.TruffleConfigIsNotExist);
});
it('should throw cancellationEvent when showQuickPick return undefined', async () => {
// Arrange
checkAppsSilent.returns(true);
getWorkspaceRootMock.returns(path.join(__dirname, TestConstants.truffleCommandTestDataFolder));
executeCommandMock.returns(uuid.v4());
showQuickPickMock.returns(undefined);
// Act and assert
await assert.rejects(TruffleCommands.deployContracts(consortiumTreeManager), CancellationEvent);
});
it('to development should complete successfully', async () => {
// Arrange
checkAppsSilent.returns(true);
getWorkspaceRootMock.returns(path.join(__dirname, TestConstants.truffleCommandTestDataFolder));
executeCommandMock.returns(uuid.v4());
showQuickPickMock.onCall(0).callsFake((items: any) => {
return items.find((item: any) => item.label === TestConstants.networksNames.development);
});
// Act
await TruffleCommands.deployContracts(consortiumTreeManager);
// Assert
assert.strictEqual(showQuickPickMock.calledOnce, true);
assert.strictEqual(showInputBoxMock.called, false);
assert.strictEqual(checkAppsSilent.calledOnce, true);
assert.strictEqual(installTruffle.called, false);
assert.strictEqual(getWorkspaceRootMock.calledOnce, true);
assert.strictEqual(executeCommandMock.called, true);
assert.strictEqual(startGanacheServer.called, true);
assert.strictEqual(truffleConfigSetNetworkMock.called, false);
});
it('to development should throw exception when there is an error on command execution', async () => {
// Arrange
checkAppsSilent.returns(true);
getWorkspaceRootMock.returns(path.join(__dirname, TestConstants.truffleCommandTestDataFolder));
executeCommandMock.throws(TestConstants.testError);
showQuickPickMock.callsFake((items: any) => {
return items.find((item: any) => item.label === TestConstants.networksNames.development);
});
// Act and assert
await assert.rejects(TruffleCommands.deployContracts(consortiumTreeManager), Error);
assert.strictEqual(showQuickPickMock.calledOnce, true);
assert.strictEqual(showInputBoxMock.called, false);
assert.strictEqual(checkAppsSilent.calledOnce, true);
assert.strictEqual(installTruffle.called, false);
assert.strictEqual(getWorkspaceRootMock.calledOnce, true);
assert.strictEqual(executeCommandMock.called, true);
assert.strictEqual(startGanacheServer.called, true);
assert.strictEqual(truffleConfigSetNetworkMock.called, false);
});
it('to mainNetwork should throw cancellationEvent when showInputBox return undefined', async () => {
// Arrange
checkAppsSilent.returns(true);
getWorkspaceRootMock.returns(path.join(__dirname, TestConstants.truffleCommandTestDataFolder));
showInputBoxMock.returns(undefined);
executeCommandMock.returns(uuid.v4());
showQuickPickMock.onCall(0).callsFake((items: any) => {
return items.find((item: any) => item.label === TestConstants.networksNames.testMainNetwork);
});
// Act and assert
await assert.rejects(TruffleCommands.deployContracts(consortiumTreeManager), CancellationEvent);
assert.strictEqual(showQuickPickMock.calledOnce, true);
assert.strictEqual(showInputBoxMock.called, true);
assert.strictEqual(checkAppsSilent.calledOnce, true);
assert.strictEqual(installTruffle.called, false);
assert.strictEqual(getWorkspaceRootMock.calledOnce, true);
assert.strictEqual(executeCommandMock.called, false);
assert.strictEqual(startGanacheServer.called, false);
assert.strictEqual(truffleConfigSetNetworkMock.called, false);
});
it('to mainNetwork should throw cancellationEvent when showInputBox return not "yes"', async () => {
// Arrange
checkAppsSilent.returns(true);
getWorkspaceRootMock.returns(path.join(__dirname, TestConstants.truffleCommandTestDataFolder));
showInputBoxMock.returns(uuid.v4());
executeCommandMock.returns(uuid.v4());
showQuickPickMock.onCall(0).callsFake((items: any) => {
return items.find((item: any) => item.label === TestConstants.networksNames.testMainNetwork);
});
// Act and assert
await assert.rejects(TruffleCommands.deployContracts(consortiumTreeManager), CancellationEvent);
assert.strictEqual(showQuickPickMock.calledOnce, true);
assert.strictEqual(showInputBoxMock.called, true);
assert.strictEqual(checkAppsSilent.calledOnce, true);
assert.strictEqual(installTruffle.called, false);
assert.strictEqual(getWorkspaceRootMock.calledOnce, true);
assert.strictEqual(executeCommandMock.called, false);
assert.strictEqual(startGanacheServer.called, false);
assert.strictEqual(truffleConfigSetNetworkMock.called, false);
});
it('to mainNetwork should complete successfully', async () => {
// Arrange
checkAppsSilent.returns(true);
getWorkspaceRootMock.returns(path.join(__dirname, TestConstants.truffleCommandTestDataFolder));
showInputBoxMock.returns(Constants.confirmationDialogResult.yes);
executeCommandMock.returns(uuid.v4());
showQuickPickMock.onCall(0).callsFake((items: any) => {
return items.find((item: any) => item.label === TestConstants.networksNames.testMainNetwork);
});
// Act
await TruffleCommands.deployContracts(consortiumTreeManager);
// Assert
assert.strictEqual(showQuickPickMock.calledOnce, true);
assert.strictEqual(showInputBoxMock.calledOnce, true);
assert.strictEqual(checkAppsSilent.calledOnce, true);
assert.strictEqual(installTruffle.called, false);
assert.strictEqual(getWorkspaceRootMock.calledOnce, true);
assert.strictEqual(executeCommandMock.called, true);
assert.strictEqual(startGanacheServer.called, false);
assert.strictEqual(truffleConfigSetNetworkMock.called, false);
});
it('to mainNetwork should throw exception when there is an error on command execution', async () => {
// Arrange
checkAppsSilent.returns(true);
getWorkspaceRootMock.returns(path.join(__dirname, TestConstants.truffleCommandTestDataFolder));
showInputBoxMock.returns(Constants.confirmationDialogResult.yes);
executeCommandMock.throws(TestConstants.testError);
showQuickPickMock.onCall(0).callsFake((items: any) => {
return items.find((item: any) => item.label === TestConstants.networksNames.testMainNetwork);
});
// Act and assert
await assert.rejects(TruffleCommands.deployContracts(consortiumTreeManager));
assert.strictEqual(showQuickPickMock.calledOnce, true);
assert.strictEqual(showInputBoxMock.calledOnce, true);
assert.strictEqual(checkAppsSilent.calledOnce, true);
assert.strictEqual(installTruffle.called, false);
assert.strictEqual(getWorkspaceRootMock.calledOnce, true);
assert.strictEqual(executeCommandMock.called, true);
assert.strictEqual(startGanacheServer.called, false);
assert.strictEqual(truffleConfigSetNetworkMock.called, false);
});
it('to network should complete successfully', async () => {
// Arrange
checkAppsSilent.returns(true);
getWorkspaceRootMock.returns(path.join(__dirname, TestConstants.truffleCommandTestDataFolder));
executeCommandMock.returns(uuid.v4());
showQuickPickMock.onCall(0).callsFake((items: any) => {
return items.find((item: any) => item.label === TestConstants.networksNames.testNetwork);
});
// Act
await TruffleCommands.deployContracts(consortiumTreeManager);
// Assert
assert.strictEqual(showQuickPickMock.calledOnce, true);
assert.strictEqual(showInputBoxMock.calledOnce, false);
assert.strictEqual(checkAppsSilent.calledOnce, true);
assert.strictEqual(installTruffle.called, false);
assert.strictEqual(getWorkspaceRootMock.calledOnce, true);
assert.strictEqual(executeCommandMock.called, true);
assert.strictEqual(startGanacheServer.called, false);
assert.strictEqual(truffleConfigSetNetworkMock.called, false);
});
it('to network should throw exception when there is an error on command execution', async () => {
// Arrange
checkAppsSilent.returns(true);
getWorkspaceRootMock.returns(path.join(__dirname, TestConstants.truffleCommandTestDataFolder));
executeCommandMock.throws(TestConstants.testError);
showQuickPickMock.onCall(0).callsFake((items: any) => {
return items.find((item: any) => item.label === TestConstants.networksNames.testNetwork);
});
// Act and assert
await assert.rejects(TruffleCommands.deployContracts(consortiumTreeManager));
assert.strictEqual(showQuickPickMock.calledOnce, true);
assert.strictEqual(showInputBoxMock.calledOnce, false);
assert.strictEqual(checkAppsSilent.calledOnce, true);
assert.strictEqual(installTruffle.called, false);
assert.strictEqual(getWorkspaceRootMock.calledOnce, true);
assert.strictEqual(executeCommandMock.called, true);
assert.strictEqual(startGanacheServer.called, false);
assert.strictEqual(truffleConfigSetNetworkMock.called, false);
});
it('to local consortium should complete successfully', async () => {
// Arrange
checkAppsSilent.returns(true);
getWorkspaceRootMock.returns(path.join(__dirname, TestConstants.truffleCommandTestDataFolder));
executeCommandMock.returns(uuid.v4());
showQuickPickMock.onCall(0).callsFake((items: any) => {
return items.find((item: any) => item.label === TestConstants.consortiumTestNames.local);
});
// Act
await TruffleCommands.deployContracts(consortiumTreeManager);
// Assert
assert.strictEqual(showQuickPickMock.calledOnce, true);
assert.strictEqual(showInputBoxMock.calledOnce, false);
assert.strictEqual(checkAppsSilent.calledOnce, true);
assert.strictEqual(installTruffle.called, false);
assert.strictEqual(getWorkspaceRootMock.calledOnce, true);
assert.strictEqual(executeCommandMock.called, true);
assert.strictEqual(startGanacheServer.called, true);
assert.strictEqual(truffleConfigSetNetworkMock.called, true);
});
it('to local consortium should throw exception when there is an error on command execution', async () => {
// Arrange
checkAppsSilent.returns(true);
getWorkspaceRootMock.returns(path.join(__dirname, TestConstants.truffleCommandTestDataFolder));
executeCommandMock.throws(TestConstants.testError);
showQuickPickMock.onCall(0).callsFake((items: any) => {
return items.find((item: any) => item.label === TestConstants.consortiumTestNames.local);
});
// Act and assert
await assert.rejects(TruffleCommands.deployContracts(consortiumTreeManager));
assert.strictEqual(showQuickPickMock.calledOnce, true);
assert.strictEqual(showInputBoxMock.calledOnce, false);
assert.strictEqual(checkAppsSilent.calledOnce, true);
assert.strictEqual(installTruffle.called, false);
assert.strictEqual(getWorkspaceRootMock.calledOnce, true);
assert.strictEqual(executeCommandMock.called, true);
assert.strictEqual(startGanacheServer.called, true);
assert.strictEqual(truffleConfigSetNetworkMock.called, true);
});
it('to EthereumNetwork should generate mnemonic and complete successfully', async () => {
// Arrange
checkAppsSilent.returns(true);
getWorkspaceRootMock.returns(path.join(__dirname, TestConstants.truffleCommandTestDataFolder));
executeCommandMock.returns(uuid.v4());
showQuickPickMock.onCall(0).callsFake((items: any) => {
return items.find((item: any) => item.label === TestConstants.consortiumTestNames.publicEthereum);
});
showQuickPickMock.onCall(1).callsFake((items: any) => {
return items.find((item: any) => item.label === Constants.placeholders.generateMnemonic);
});
showInputBoxMock.onCall(0).returns(Constants.confirmationDialogResult.yes);
showInputBoxMock.onCall(1).returns(100000000000);
showInputBoxMock.onCall(2).returns(4712388);
showSaveDialogMock.returns(uuid.v4());
// Act
await TruffleCommands.deployContracts(consortiumTreeManager);
// Assert
assert.strictEqual(showQuickPickMock.called, true);
assert.strictEqual(showQuickPickMock.callCount, 2);
assert.strictEqual(showInputBoxMock.called, true);
assert.strictEqual(showInputBoxMock.callCount, 3);
assert.strictEqual(getMnemonicMock.called, false);
assert.strictEqual(getAllMnemonicPathsMock.called, true);
assert.strictEqual(saveMnemonicPathMock.called, true);
assert.strictEqual(openTextDocumentMock.called, true);
assert.strictEqual(writeFileSyncMock.called, true);
assert.strictEqual(showTextDocumentMock.called, true);
assert.strictEqual(checkAppsSilent.calledOnce, true);
assert.strictEqual(installTruffle.called, false);
assert.strictEqual(getWorkspaceRootMock.called, true);
assert.strictEqual(executeCommandMock.called, true);
assert.strictEqual(startGanacheServer.called, false);
assert.strictEqual(truffleConfigSetNetworkMock.called, true);
});
it('to EthereumNetwork should generate mnemonic and complete successfully with default params', async () => {
// Arrange
checkAppsSilent.returns(true);
getWorkspaceRootMock.returns(path.join(__dirname, TestConstants.truffleCommandTestDataFolder));
executeCommandMock.returns(uuid.v4());
showQuickPickMock.onCall(0).callsFake((items: any) => {
return items.find((item: any) => item.label === TestConstants.consortiumTestNames.publicEthereum);
});
showQuickPickMock.onCall(1).callsFake((items: any) => {
return items.find((item: any) => item.label === Constants.placeholders.generateMnemonic);
});
showInputBoxMock.onCall(0).returns(Constants.confirmationDialogResult.yes);
showInputBoxMock.onCall(1).returns('');
showInputBoxMock.onCall(2).returns('');
showSaveDialogMock.returns(uuid.v4());
// Act
await TruffleCommands.deployContracts(consortiumTreeManager);
// Assert
assert.strictEqual(showQuickPickMock.called, true);
assert.strictEqual(showQuickPickMock.callCount, 2);
assert.strictEqual(showInputBoxMock.called, true);
assert.strictEqual(showInputBoxMock.callCount, 3);
assert.strictEqual(getMnemonicMock.called, false);
assert.strictEqual(getAllMnemonicPathsMock.called, true);
assert.strictEqual(saveMnemonicPathMock.called, true);
assert.strictEqual(openTextDocumentMock.called, true);
assert.strictEqual(writeFileSyncMock.called, true);
assert.strictEqual(showTextDocumentMock.called, true);
assert.strictEqual(checkAppsSilent.calledOnce, true);
assert.strictEqual(installTruffle.called, false);
assert.strictEqual(getWorkspaceRootMock.called, true);
assert.strictEqual(executeCommandMock.called, true);
assert.strictEqual(startGanacheServer.called, false);
assert.strictEqual(truffleConfigSetNetworkMock.called, true);
});
it('to EthereumNetwork should complete successfully when user paste mnemonic', async () => {
// Arrange
checkAppsSilent.returns(true);
getWorkspaceRootMock.returns(path.join(__dirname, TestConstants.truffleCommandTestDataFolder));
executeCommandMock.returns(uuid.v4());
showQuickPickMock.onCall(0).callsFake((items: any) => {
return items.find((item: any) => item.label === TestConstants.consortiumTestNames.publicEthereum);
});
showQuickPickMock.onCall(1).callsFake((items: any) => {
return items.find((item: any) => item.label === Constants.placeholders.pasteMnemonic);
});
showInputBoxMock.onCall(0).returns(Constants.confirmationDialogResult.yes);
showInputBoxMock.onCall(1).returns(TestConstants.testMnemonic);
showInputBoxMock.onCall(2).returns(100000000000);
showInputBoxMock.onCall(3).returns(4712388);
showSaveDialogMock.returns(uuid.v4());
// Act
await TruffleCommands.deployContracts(consortiumTreeManager);
// Assert
assert.strictEqual(showQuickPickMock.called, true);
assert.strictEqual(showQuickPickMock.callCount, 2);
assert.strictEqual(showInputBoxMock.called, true);
assert.strictEqual(showInputBoxMock.callCount, 4);
assert.strictEqual(getMnemonicMock.called, false);
assert.strictEqual(getAllMnemonicPathsMock.called, true);
assert.strictEqual(saveMnemonicPathMock.called, true);
assert.strictEqual(openTextDocumentMock.called, true);
assert.strictEqual(writeFileSyncMock.called, true);
assert.strictEqual(showTextDocumentMock.called, true);
assert.strictEqual(checkAppsSilent.calledOnce, true);
assert.strictEqual(installTruffle.called, false);
assert.strictEqual(getWorkspaceRootMock.called, true);
assert.strictEqual(executeCommandMock.called, true);
assert.strictEqual(startGanacheServer.called, false);
assert.strictEqual(truffleConfigSetNetworkMock.called, true);
});
it('to EthereumNetwork should complete successfully with default params when user paste mnemonic', async () => {
// Arrange
checkAppsSilent.returns(true);
getWorkspaceRootMock.returns(path.join(__dirname, TestConstants.truffleCommandTestDataFolder));
executeCommandMock.returns(uuid.v4());
showQuickPickMock.onCall(0).callsFake((items: any) => {
return items.find((item: any) => item.label === TestConstants.consortiumTestNames.publicEthereum);
});
showQuickPickMock.onCall(1).callsFake((items: any) => {
return items.find((item: any) => item.label === Constants.placeholders.pasteMnemonic);
});
showInputBoxMock.onCall(0).returns(Constants.confirmationDialogResult.yes);
showInputBoxMock.onCall(1).returns(TestConstants.testMnemonic);
showInputBoxMock.onCall(2).returns('');
showInputBoxMock.onCall(3).returns('');
showSaveDialogMock.returns(uuid.v4());
// Act
await TruffleCommands.deployContracts(consortiumTreeManager);
// Assert
assert.strictEqual(showQuickPickMock.called, true);
assert.strictEqual(showQuickPickMock.callCount, 2);
assert.strictEqual(showInputBoxMock.called, true);
assert.strictEqual(showInputBoxMock.callCount, 4);
assert.strictEqual(getMnemonicMock.called, false);
assert.strictEqual(getAllMnemonicPathsMock.called, true);
assert.strictEqual(saveMnemonicPathMock.called, true);
assert.strictEqual(openTextDocumentMock.called, true);
assert.strictEqual(writeFileSyncMock.called, true);
assert.strictEqual(showTextDocumentMock.called, true);
assert.strictEqual(checkAppsSilent.calledOnce, true);
assert.strictEqual(installTruffle.called, false);
assert.strictEqual(getWorkspaceRootMock.called, true);
assert.strictEqual(executeCommandMock.called, true);
assert.strictEqual(startGanacheServer.called, false);
assert.strictEqual(truffleConfigSetNetworkMock.called, true);
});
it('to EthereumTestnet should generate mnemonic and complete successfully', async () => {
// Arrange
checkAppsSilent.returns(true);
getWorkspaceRootMock.returns(path.join(__dirname, TestConstants.truffleCommandTestDataFolder));
executeCommandMock.returns(uuid.v4());
showQuickPickMock.onCall(0).callsFake((items: any) => {
return items.find((item: any) => item.label === TestConstants.consortiumTestNames.testEthereum);
});
showQuickPickMock.onCall(1).callsFake((items: any) => {
return items.find((item: any) => item.label === Constants.placeholders.generateMnemonic);
});
showInputBoxMock.onCall(0).returns(100000000000);
showInputBoxMock.onCall(1).returns(4712388);
showSaveDialogMock.returns(uuid.v4());
// Act
await TruffleCommands.deployContracts(consortiumTreeManager);
// Assert
assert.strictEqual(showQuickPickMock.called, true);
assert.strictEqual(showQuickPickMock.callCount, 2);
assert.strictEqual(showInputBoxMock.called, true);
assert.strictEqual(showInputBoxMock.callCount, 2);
assert.strictEqual(getMnemonicMock.called, false);
assert.strictEqual(getAllMnemonicPathsMock.called, true);
assert.strictEqual(saveMnemonicPathMock.called, true);
assert.strictEqual(openTextDocumentMock.called, true);
assert.strictEqual(writeFileSyncMock.called, true);
assert.strictEqual(showTextDocumentMock.called, true);
assert.strictEqual(checkAppsSilent.calledOnce, true);
assert.strictEqual(installTruffle.called, false);
assert.strictEqual(getWorkspaceRootMock.called, true);
assert.strictEqual(executeCommandMock.called, true);
assert.strictEqual(startGanacheServer.called, false);
assert.strictEqual(truffleConfigSetNetworkMock.called, true);
});
it('to EthereumTestnet should generate mnemonic and complete successfully with default params', async () => {
// Arrange
checkAppsSilent.returns(true);
getWorkspaceRootMock.returns(path.join(__dirname, TestConstants.truffleCommandTestDataFolder));
executeCommandMock.returns(uuid.v4());
showQuickPickMock.onCall(0).callsFake((items: any) => {
return items.find((item: any) => item.label === TestConstants.consortiumTestNames.testEthereum);
});
showQuickPickMock.onCall(1).callsFake((items: any) => {
return items.find((item: any) => item.label === Constants.placeholders.generateMnemonic);
});
showInputBoxMock.onCall(0).returns('');
showInputBoxMock.onCall(1).returns('');
showSaveDialogMock.returns(uuid.v4());
// Act
await TruffleCommands.deployContracts(consortiumTreeManager);
// Assert
assert.strictEqual(showQuickPickMock.called, true);
assert.strictEqual(showQuickPickMock.callCount, 2);
assert.strictEqual(showInputBoxMock.called, true);
assert.strictEqual(showInputBoxMock.callCount, 2);
assert.strictEqual(getMnemonicMock.called, false);
assert.strictEqual(getAllMnemonicPathsMock.called, true);
assert.strictEqual(saveMnemonicPathMock.called, true);
assert.strictEqual(openTextDocumentMock.called, true);
assert.strictEqual(writeFileSyncMock.called, true);
assert.strictEqual(showTextDocumentMock.called, true);
assert.strictEqual(checkAppsSilent.calledOnce, true);
assert.strictEqual(installTruffle.called, false);
assert.strictEqual(getWorkspaceRootMock.called, true);
assert.strictEqual(executeCommandMock.called, true);
assert.strictEqual(startGanacheServer.called, false);
assert.strictEqual(truffleConfigSetNetworkMock.called, true);
});
it('to EthereumTestnet should complete successfully when user paste mnemonic', async () => {
// Arrange
checkAppsSilent.returns(true);
getWorkspaceRootMock.returns(path.join(__dirname, TestConstants.truffleCommandTestDataFolder));
executeCommandMock.returns(uuid.v4());
showQuickPickMock.onCall(0).callsFake((items: any) => {
return items.find((item: any) => item.label === TestConstants.consortiumTestNames.testEthereum);
});
showQuickPickMock.onCall(1).callsFake((items: any) => {
return items.find((item: any) => item.label === Constants.placeholders.pasteMnemonic);
});
showInputBoxMock.onCall(0).returns(TestConstants.testMnemonic);
showInputBoxMock.onCall(1).returns(100000000000);
showInputBoxMock.onCall(2).returns(4712388);
showSaveDialogMock.returns(uuid.v4());
// Act
await TruffleCommands.deployContracts(consortiumTreeManager);
// Assert
assert.strictEqual(showQuickPickMock.called, true);
assert.strictEqual(showQuickPickMock.callCount, 2);
assert.strictEqual(showInputBoxMock.called, true);
assert.strictEqual(showInputBoxMock.callCount, 3);
assert.strictEqual(getMnemonicMock.called, false);
assert.strictEqual(getAllMnemonicPathsMock.called, true);
assert.strictEqual(saveMnemonicPathMock.called, true);
assert.strictEqual(openTextDocumentMock.called, true);
assert.strictEqual(writeFileSyncMock.called, true);
assert.strictEqual(showTextDocumentMock.called, true);
assert.strictEqual(checkAppsSilent.calledOnce, true);
assert.strictEqual(installTruffle.called, false);
assert.strictEqual(getWorkspaceRootMock.called, true);
assert.strictEqual(executeCommandMock.called, true);
assert.strictEqual(startGanacheServer.called, false);
assert.strictEqual(truffleConfigSetNetworkMock.called, true);
});
it('to EthereumTestnet should complete successfully with default params when user paste mnemonic', async () => {
// Arrange
checkAppsSilent.returns(true);
getWorkspaceRootMock.returns(path.join(__dirname, TestConstants.truffleCommandTestDataFolder));
executeCommandMock.returns(uuid.v4());
showQuickPickMock.onCall(0).callsFake((items: any) => {
return items.find((item: any) => item.label === TestConstants.consortiumTestNames.testEthereum);
});
showQuickPickMock.onCall(1).callsFake((items: any) => {
return items.find((item: any) => item.label === Constants.placeholders.pasteMnemonic);
});
showInputBoxMock.onCall(0).returns(TestConstants.testMnemonic);
showInputBoxMock.onCall(1).returns('');
showInputBoxMock.onCall(2).returns('');
showSaveDialogMock.returns(uuid.v4());
// Act
await TruffleCommands.deployContracts(consortiumTreeManager);
// Assert
assert.strictEqual(showQuickPickMock.called, true);
assert.strictEqual(showQuickPickMock.callCount, 2);
assert.strictEqual(showInputBoxMock.called, true);
assert.strictEqual(showInputBoxMock.callCount, 3);
assert.strictEqual(getMnemonicMock.called, false);
assert.strictEqual(getAllMnemonicPathsMock.called, true);
assert.strictEqual(saveMnemonicPathMock.called, true);
assert.strictEqual(openTextDocumentMock.called, true);
assert.strictEqual(writeFileSyncMock.called, true);
assert.strictEqual(showTextDocumentMock.called, true);
assert.strictEqual(checkAppsSilent.calledOnce, true);
assert.strictEqual(installTruffle.called, false);
assert.strictEqual(getWorkspaceRootMock.called, true);
assert.strictEqual(executeCommandMock.called, true);
assert.strictEqual(startGanacheServer.called, false);
assert.strictEqual(truffleConfigSetNetworkMock.called, true);
});
it('to AzureBlockchainService should generate mnemonic and complete successfully', async () => {
// Arrange
checkAppsSilent.returns(true);
getWorkspaceRootMock.returns(path.join(__dirname, TestConstants.truffleCommandTestDataFolder));
executeCommandMock.returns(uuid.v4());
getAccessKeysMock.returns(uuid.v4());
showQuickPickMock.onCall(0).callsFake((items: any) => {
return items.find((item: any) => item.label === TestConstants.networksNames.testConsortium);
});
showQuickPickMock.onCall(1).callsFake((items: any) => {
return items.find((item: any) => item.label === Constants.placeholders.generateMnemonic);
});
showSaveDialogMock.returns(uuid.v4());
// Act
await TruffleCommands.deployContracts(consortiumTreeManager);
// Assert
assert.strictEqual(showQuickPickMock.called, true);
assert.strictEqual(showQuickPickMock.callCount, 2);
assert.strictEqual(getAccessKeysMock.called, true);
assert.strictEqual(showInputBoxMock.called, false);
assert.strictEqual(getMnemonicMock.called, false);
assert.strictEqual(getAllMnemonicPathsMock.called, true);
assert.strictEqual(saveMnemonicPathMock.called, true);
assert.strictEqual(openTextDocumentMock.called, true);
assert.strictEqual(writeFileSyncMock.called, true);
assert.strictEqual(showTextDocumentMock.called, true);
assert.strictEqual(checkAppsSilent.calledOnce, true);
assert.strictEqual(installTruffle.called, false);
assert.strictEqual(getWorkspaceRootMock.called, true);
assert.strictEqual(executeCommandMock.called, true);
assert.strictEqual(startGanacheServer.called, false);
assert.strictEqual(truffleConfigSetNetworkMock.called, true);
assert.strictEqual(getExtensionMock.called, true);
});
it('to AzureBlockchainService should complete successfully when user paste mnemonic', async () => {
// Arrange
checkAppsSilent.returns(true);
getWorkspaceRootMock.returns(path.join(__dirname, TestConstants.truffleCommandTestDataFolder));
executeCommandMock.returns(uuid.v4());
getAccessKeysMock.returns(uuid.v4());
showQuickPickMock.onCall(0).callsFake((items: any) => {
return items.find((item: any) => item.label === TestConstants.networksNames.testConsortium);
});
showQuickPickMock.onCall(1).callsFake((items: any) => {
return items.find((item: any) => item.label === Constants.placeholders.pasteMnemonic);
});
showInputBoxMock.onCall(0).returns(TestConstants.testMnemonic);
showSaveDialogMock.returns(uuid.v4());
// Act
await TruffleCommands.deployContracts(consortiumTreeManager);
// Assert
assert.strictEqual(showQuickPickMock.called, true);
assert.strictEqual(showQuickPickMock.callCount, 2);
assert.strictEqual(getAccessKeysMock.called, true);
assert.strictEqual(showInputBoxMock.calledOnce, true);
assert.strictEqual(getMnemonicMock.called, false);
assert.strictEqual(getAllMnemonicPathsMock.called, true);
assert.strictEqual(saveMnemonicPathMock.called, true);
assert.strictEqual(openTextDocumentMock.called, true);
assert.strictEqual(writeFileSyncMock.called, true);
assert.strictEqual(showTextDocumentMock.called, true);
assert.strictEqual(checkAppsSilent.calledOnce, true);
assert.strictEqual(installTruffle.called, false);
assert.strictEqual(getWorkspaceRootMock.called, true);
assert.strictEqual(executeCommandMock.called, true);
assert.strictEqual(startGanacheServer.called, false);
assert.strictEqual(truffleConfigSetNetworkMock.called, true);
assert.strictEqual(getExtensionMock.called, true);
});
});
});
});
async function createTestConsortiumItems(): Promise<Network[]> {
const networks: Network[] = [];
const azureNetwork = new Network(TestConstants.networksNames.azureBlockchainService, ItemType.AZURE_BLOCKCHAIN);
const localNetwork = new Network(TestConstants.networksNames.localNetwork, ItemType.LOCAL_NETWORK);
const ethereumTestnet = new Network(TestConstants.networksNames.ethereumTestnet, ItemType.ETHEREUM_TEST_NETWORK);
const ethereumNetwork = new Network(TestConstants.networksNames.ethereumNetwork, ItemType.ETHEREUM_MAIN_NETWORK);
const azureConsortium = new AzureConsortium(
TestConstants.networksNames.testConsortium,
uuid.v4(),
uuid.v4(),
uuid.v4(),
'https://testConsortium.blockchain.azure.com/',
);
const localNetworkConsortium = new LocalNetworkConsortium(
TestConstants.consortiumTestNames.local,
'http://127.0.0.1:8545/',
);
const testNetworkConsortium = new TestNetworkConsortium(
TestConstants.consortiumTestNames.testEthereum,
'https://0.0.0.3:1234/',
);
const mainNetworkConsortium = new MainNetworkConsortium(
TestConstants.consortiumTestNames.publicEthereum,
'https://0.0.0.4:1234/',
);
azureConsortium.setConsortiumId(randomInteger());
localNetworkConsortium.setConsortiumId(randomInteger());
testNetworkConsortium.setConsortiumId(randomInteger());
mainNetworkConsortium.setConsortiumId(randomInteger());
azureNetwork.addChild(azureConsortium);
localNetwork.addChild(localNetworkConsortium);
ethereumTestnet.addChild(testNetworkConsortium);
ethereumNetwork.addChild(mainNetworkConsortium);
networks.push(azureNetwork, localNetwork, ethereumNetwork, ethereumTestnet);
return networks;
}
function getTestTruffleNetworks(): TruffleConfiguration.INetwork[] {
const networks: TruffleConfiguration.INetwork[] = [];
networks.push({
name: TestConstants.networksNames.development,
options: {
host: '127.0.0.1',
network_id: '*',
port: 8545,
},
},
{
name: TestConstants.networksNames.testMainNetwork,
options: {
consortium_id: 1559217403180,
gas: 4712388,
gasPrice: 100000000000,
network_id: 1,
},
},
{
name: TestConstants.networksNames.testNetwork,
options: {
consortium_id: 1559217403181,
gas: 4712388,
gasPrice: 100000000000,
network_id: 2,
},
});
return networks;
}
function randomInteger(): number {
const max = + Date.now();
const rand = - 0.5 + Math.random() * (max + 1);
return Math.round(rand);
}
async function waitAMoment() {
await new Promise<void>((resolve) => {
setTimeout(resolve, 100);
});
}
async function mockActivate() {
await waitAMoment();
return {} as AzureAccount;
}
const mockExtension: vscode.Extension<AzureAccount> = {
activate: mockActivate,
exports: {} as AzureAccount,
extensionPath: uuid.v4(),
id: uuid.v4(),
isActive: true,
packageJSON: uuid.v4(),
};

Просмотреть файл

@ -0,0 +1,60 @@
{
"contractName": "HelloBlockchain",
"abi": [
{
"constant": true,
"inputs": [],
"name": "ResponseMessage",
"outputs": [
{
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "Responder",
"outputs": [
{
"name": "",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "RequestMessage",
"outputs": [
{
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
}
],
"bytecode": "0x00000000000000000000000000000000",
"deployedBytecode": "0x00000000000000000000000000000000",
"compiler": {
"name": "solc",
"version": "0.5.0+commit.1d4f565a.Emscripten.clang"
},
"schemaVersion": "3.0.9",
"devdoc": {
"methods": {}
},
"userdoc": {
"methods": {}
}
}

Просмотреть файл

@ -0,0 +1,10 @@
const HDWalletProvider = require('truffle-hdwallet-provider');
const path = require('path');
const fs = require('fs');
module.exports = {
networks: {},
mocha: {},
compilers: {
solc: {}
}
};

170
test/commands.test.ts Normal file
Просмотреть файл

@ -0,0 +1,170 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import assert = require('assert');
import * as cp from 'child_process';
import events = require('events');
import * as os from 'os';
import rewire = require('rewire');
import * as sinon from 'sinon';
import stream = require('stream');
import * as outputCommandHelper from '../src/helpers/command';
describe('Commands helper', () => {
const command = 'test_command';
afterEach(() => {
sinon.restore();
sinon.resetHistory();
});
const cases = [
{ workingDirectory: 'defined', tmpdirExecuted: false },
{ workingDirectory: undefined, tmpdirExecuted: true },
];
cases.forEach((testCase) => {
it(`startProcess should return new ChildProcess when directory ${testCase.workingDirectory}`,
async () => {
// Arrange
const tmpdirStub = sinon.stub(os, 'tmpdir').returns('');
const spawnStub = sinon.stub(cp, 'spawn');
// Act
await outputCommandHelper.startProcess(testCase.workingDirectory, command, ['']);
// Assert
assert.strictEqual(tmpdirStub.calledOnce, testCase.tmpdirExecuted);
assert.strictEqual(spawnStub.calledOnce, true);
assert.strictEqual(spawnStub.getCall(0).args[0], command);
});
});
describe('tryExecuteCommand', () => {
let childProcessMock: sinon.SinonMock;
let processMock: cp.ChildProcess;
beforeEach(() => {
processMock = new events.EventEmitter() as cp.ChildProcess;
processMock.stdout = new events.EventEmitter() as stream.Readable;
processMock.stderr = new events.EventEmitter() as stream.Readable;
processMock.stdin = new stream.Writable();
childProcessMock = sinon.mock(cp);
});
afterEach(() => {
sinon.restore();
});
it('tryExecuteCommand should return correct result',
async () => {
// Arrange
const spawnMock = childProcessMock.expects('spawn').returns(processMock);
// Act
const commandResultPromise = outputCommandHelper.tryExecuteCommand('workingDirectory', command, '');
await new Promise<void>(async (resolve) => {
setTimeout(async () => {
await processMock.stdout.emit('data', 'test stdout');
await processMock.emit('close', 0);
resolve();
}, 500);
});
const commandResult = await commandResultPromise;
// Assert
assert.strictEqual(commandResult.cmdOutput, 'test stdout');
assert.strictEqual(spawnMock.calledOnce, true);
});
it('tryExecuteCommand should return correct result when there is message in error output',
async () => {
// Arrange
const spawnMock = childProcessMock.expects('spawn').returns(processMock);
// Act
const commandResultProms = outputCommandHelper.tryExecuteCommand('workingDirectory', command, '');
await new Promise<void>(async (resolve) => {
setTimeout(async () => {
await processMock.stderr.emit('data', 'test stdout');
await processMock.emit('close', 0);
resolve();
}, 500);
});
const commandResult = await commandResultProms;
// Assert
assert.strictEqual(spawnMock.calledOnce, true);
assert.strictEqual(commandResult.cmdOutputIncludingStderr, 'test stdout');
});
it('tryExecuteCommand should rejected on error',
async () => {
// Arrange
sinon.replace(cp, 'spawn', () => { throw new Error(); });
// Act
const commandResultProms = outputCommandHelper.tryExecuteCommand('workingDirectory', command, '');
await new Promise<void>(async (resolve) => {
setTimeout(async () => {
await processMock.stderr.emit('data', 'test stdout');
await processMock.emit('close', 0);
resolve();
}, 500);
});
// Assert
await assert.rejects(commandResultProms);
});
});
it('executeCommand should throw error when result code not equal to 0',
async () => {
// Arrange
const commandResult = {
cmdOutput: '',
cmdOutputIncludingStderr: '',
code: 1,
};
const commandRewire = rewire('../src/helpers/command');
commandRewire.__set__('tryExecuteCommand', sinon.mock().returns(commandResult));
// Act and Assert
await assert.rejects(commandRewire.executeCommand('workingDirectory', command, ''));
});
it('executeCommand should return command output',
async () => {
// Arrange
const commandResult = {
cmdOutput: 'stdout message',
cmdOutputIncludingStderr: 'stderr message',
code: 0,
};
const commandRewire = rewire('../src/helpers/command');
commandRewire.__set__('tryExecuteCommand', sinon.mock().returns(commandResult));
// Act
const res = await commandRewire.executeCommand('workingDirectory', command, '');
// Assert
assert.strictEqual(res, commandResult.cmdOutput);
});
it('executeCommand throw error when tryExecuteCommand rejected',
async () => {
// Arrange
sinon.replace(cp, 'spawn', () => { throw new Error(); });
// Act and Assert
await assert.rejects(outputCommandHelper.executeCommand('workingDirectory', command, ''))
.then(() => {
return undefined;
});
});
});

Просмотреть файл

@ -0,0 +1,239 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as assert from 'assert';
import rewire = require('rewire');
import * as sinon from 'sinon';
import * as uuid from 'uuid';
import * as vscode from 'vscode';
import { ConsortiumResourceExplorer } from '../../../src/ConsortiumResourceExplorer';
import { Constants } from '../../../src/Constants';
import { GanacheService } from '../../../src/GanacheService/GanacheService';
import {
AzureConsortium,
Consortium,
ItemType,
Network,
} from '../../../src/Models';
import { AzureAccountHelper } from '../../testHepers/azureAccountHelper';
describe('Consortium Commands', () => {
let defaultConsortiumName: string;
let defaultSubscriptionId: string;
let defaultResourcesGroup: string;
let defaultMemberName: string;
let defaultNetworkName: string;
let vscodeWindowMock: sinon.SinonMock;
before(() => {
sinon.restore();
defaultConsortiumName = uuid.v4();
defaultSubscriptionId = uuid.v4();
defaultResourcesGroup = uuid.v4();
defaultMemberName = uuid.v4();
defaultNetworkName = uuid.v4();
});
after(() => {
sinon.restore();
});
beforeEach(() => {
vscodeWindowMock = sinon.mock(vscode.window);
});
afterEach(() => {
vscodeWindowMock.restore();
});
describe('Integration tests', () => {
let consortiumCommandsRewire: any;
let consortiumTreeManager: { __proto__: any; ConsortiumTreeManager: { new(): void; prototype: any; }; };
before(() => {
consortiumTreeManager = require('../../../src/treeService/ConsortiumTreeManager');
sinon.stub(consortiumTreeManager.__proto__, 'constructor');
sinon.stub(consortiumTreeManager.ConsortiumTreeManager.prototype, 'loadState').returns({});
});
beforeEach(() => {
consortiumCommandsRewire = rewire('../../../src/commands/ConsortiumCommands');
});
describe('connectConsortium returns consortium', () => {
const defaultConsortiumUrl = 'http://127.0.0.1:2345';
let selectedDestination: any;
let getItemStub: sinon.SinonStub<any[], any> | sinon.SinonStub<unknown[], {}>;
let addChildStub: sinon.SinonStub<any[], any>;
let showQuickPickMock: sinon.SinonStub<any[], any>;
let showInputBoxMock: sinon.SinonExpectation;
let selectOrCreateConsortiumMock: sinon.SinonStub<any[], any>;
let startGanacheCmdStub: any;
beforeEach(() => {
showQuickPickMock = vscodeWindowMock.expects('showQuickPick');
showInputBoxMock = vscodeWindowMock.expects('showInputBox');
startGanacheCmdStub
= sinon.stub(GanacheService, 'startGanacheServer').callsFake(() => Promise.resolve(null));
getItemStub = sinon.stub(consortiumTreeManager.ConsortiumTreeManager.prototype, 'getItem')
.callsFake((...args: any[]) => {
const network = new Network(defaultNetworkName, args[0]);
addChildStub = sinon.stub(network, 'addChild');
return network;
});
selectOrCreateConsortiumMock = sinon.stub(ConsortiumResourceExplorer.prototype,
'selectOrCreateConsortium').returns(Promise.resolve(
new AzureConsortium(
defaultConsortiumName,
defaultSubscriptionId,
defaultResourcesGroup,
defaultMemberName)));
});
afterEach(() => {
startGanacheCmdStub.restore();
selectOrCreateConsortiumMock.restore();
getItemStub.restore();
});
function assertAfterEachTest(result: Consortium, itemType: number, contextValue: string, labelName: string) {
assert.strictEqual(selectedDestination.cmd.calledOnce, true);
assert.strictEqual(addChildStub.calledOnce, true);
assert.strictEqual(result.itemType, itemType);
assert.strictEqual(result.contextValue, contextValue);
assert.strictEqual(result.label, labelName);
}
it('for Local Network destination.', async () => {
// Arrange
let validationMessage;
const defaultPort = '1234';
const defaultLabel = `${Constants.localhostName}:${defaultPort}`;
const defaultUrl = `${Constants.networkProtocols.http}${Constants.localhost}:${defaultPort}`;
showQuickPickMock.callsFake(async (...args: any[]) => {
selectedDestination = args[0].find((x: any) => x.itemType === ItemType.LOCAL_NETWORK);
selectedDestination.cmd = sinon.spy(selectedDestination.cmd);
return selectedDestination;
});
showInputBoxMock.callsFake(async (..._args: any[]) => {
validationMessage = await _args[0].validateInput(defaultPort);
return defaultPort;
});
// Act
const result = await consortiumCommandsRewire.ConsortiumCommands
.connectConsortium(new consortiumTreeManager.ConsortiumTreeManager());
// Assert
assertAfterEachTest(result, ItemType.LOCAL_CONSORTIUM, Constants.contextValue.localConsortium, defaultLabel);
assert.strictEqual(startGanacheCmdStub.calledOnce, true);
assert.strictEqual(result.urls[0].origin, defaultUrl);
assert.notStrictEqual(validationMessage, undefined);
}).timeout(10000);
it('for Azure Blockchain Network destination.', async () => {
// Arrange
const getExtensionMock = sinon.stub(vscode.extensions, 'getExtension')
.returns(AzureAccountHelper.mockExtension);
showQuickPickMock.callsFake(async (...args: any[]) => {
const destination = args[0].find((x: any) => x.itemType === ItemType.AZURE_BLOCKCHAIN);
selectedDestination = destination;
selectedDestination.cmd = sinon.spy(destination.cmd);
return selectedDestination;
});
// Act
const result = await consortiumCommandsRewire.ConsortiumCommands
.connectConsortium(new consortiumTreeManager.ConsortiumTreeManager());
// Assert
assertAfterEachTest(
result, ItemType.AZURE_CONSORTIUM, Constants.contextValue.consortium, defaultConsortiumName);
assert.strictEqual(startGanacheCmdStub.notCalled, true);
assert.strictEqual(selectOrCreateConsortiumMock.calledOnce, true);
getExtensionMock.restore();
}).timeout(10000);
it('for Ethereum Test Network destination.', async () => {
// Arrange
let validationMessageConsortiumName;
let validationMessageConsortiumUrl;
showInputBoxMock.twice();
showInputBoxMock.onCall(0).callsFake(async (..._args: any[]) => {
validationMessageConsortiumName = await _args[0].validateInput(defaultConsortiumName);
return defaultConsortiumName;
});
showInputBoxMock.onCall(1).callsFake(async (..._args: any[]) => {
validationMessageConsortiumUrl = await _args[0].validateInput(defaultConsortiumUrl);
return defaultConsortiumUrl;
});
showQuickPickMock.callsFake(async (...args: any[]) => {
const destination = args[0].find((x: any) => x.itemType === ItemType.ETHEREUM_TEST_NETWORK);
selectedDestination = destination;
selectedDestination.cmd = sinon.spy(destination.cmd);
return selectedDestination;
});
// Act
const result = await consortiumCommandsRewire.ConsortiumCommands
.connectConsortium(new consortiumTreeManager.ConsortiumTreeManager());
// Assert
assertAfterEachTest(
result, ItemType.ETHEREUM_TEST_CONSORTIUM, Constants.contextValue.consortium, defaultConsortiumName);
assert.strictEqual(startGanacheCmdStub.notCalled, true);
assert.strictEqual(result.urls[0].origin, defaultConsortiumUrl);
assert.strictEqual(validationMessageConsortiumName, undefined);
assert.strictEqual(validationMessageConsortiumUrl, null);
}).timeout(10000);
it('for Ethereum Main Network destination.', async () => {
// Arrange
let validationMessageConsortiumName;
let validationMessageConsortiumUrl;
showInputBoxMock.twice();
showInputBoxMock.onCall(0).callsFake(async (..._args: any[]) => {
validationMessageConsortiumName = await _args[0].validateInput(defaultConsortiumName);
return defaultConsortiumName;
});
showInputBoxMock.onCall(1).callsFake(async (..._args: any[]) => {
validationMessageConsortiumUrl = await _args[0].validateInput(defaultConsortiumUrl);
return defaultConsortiumUrl;
});
showQuickPickMock.callsFake(async (...args: any[]) => {
const destination = args[0].find((x: any) => x.itemType === ItemType.ETHEREUM_MAIN_NETWORK);
selectedDestination = destination;
selectedDestination.cmd = sinon.spy(destination.cmd);
return selectedDestination;
});
// Act
const result = await consortiumCommandsRewire.ConsortiumCommands
.connectConsortium(new consortiumTreeManager.ConsortiumTreeManager());
// Assert
assertAfterEachTest(
result, ItemType.ETHEREUM_MAIN_CONSORTIUM, Constants.contextValue.consortium, defaultConsortiumName);
assert.strictEqual(startGanacheCmdStub.notCalled, true);
assert.strictEqual(result.urls[0].origin, defaultConsortiumUrl);
assert.strictEqual(validationMessageConsortiumName, undefined);
assert.strictEqual(validationMessageConsortiumUrl, null);
}).timeout(10000);
});
});
});

Просмотреть файл

@ -0,0 +1,542 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as assert from 'assert';
import rewire = require('rewire');
import * as sinon from 'sinon';
import * as uuid from 'uuid';
import * as vscode from 'vscode';
import { Constants } from '../../../src/Constants';
import { GanacheService } from '../../../src/GanacheService/GanacheService';
import * as command from '../../../src/helpers/command';
import {
AzureConsortium,
CancellationEvent,
Consortium,
IExtensionItem,
ItemType,
LocalNetworkConsortium,
MainNetworkConsortium,
Network,
TestNetworkConsortium,
} from '../../../src/Models';
describe('Consortium Commands', () => {
let defaultConsortiumName: string;
let defaultSubscriptionId: string;
let defaultResourcesGroup: string;
let defaultMemberName: string;
let defaultNetworkName: string;
let vscodeWindowMock: sinon.SinonMock;
before(() => {
sinon.restore();
defaultConsortiumName = uuid.v4();
defaultSubscriptionId = uuid.v4();
defaultResourcesGroup = uuid.v4();
defaultMemberName = uuid.v4();
defaultNetworkName = uuid.v4();
});
after(() => {
sinon.restore();
});
beforeEach(() => {
vscodeWindowMock = sinon.mock(vscode.window);
});
afterEach(() => {
vscodeWindowMock.restore();
});
describe('Unit tests', () => {
let consortiumCommandsRewire: any;
beforeEach(() => {
consortiumCommandsRewire = rewire('../../../src/commands/ConsortiumCommands');
});
describe('connectConsortium provides types of consortium destination and returns new consortium', () => {
let consortiumTreeManager: { __proto__: any; ConsortiumTreeManager: { prototype: any; new(): void; }; };
function assertAfterEachTest(
executeMock: sinon.SinonSpy,
result: Consortium,
itemType: number,
contextValue: string) {
assert.strictEqual(executeMock.calledOnce, true);
assert.strictEqual(result.label, defaultConsortiumName);
assert.strictEqual(result.itemType, itemType);
assert.strictEqual(result.contextValue, contextValue);
}
before(() => {
consortiumTreeManager = require('../../../src/treeService/ConsortiumTreeManager');
sinon.stub(consortiumTreeManager.__proto__, 'constructor');
sinon.stub(consortiumTreeManager.ConsortiumTreeManager.prototype, 'loadState').returns({});
});
after(() => {
sinon.restore();
});
it('for Local Network destination.', async () => {
// Arrange
const executeMock = sinon.spy((..._args: any[]) => {
return new LocalNetworkConsortium(defaultConsortiumName);
});
consortiumCommandsRewire.__set__('execute', executeMock);
// Act
const result = await consortiumCommandsRewire.ConsortiumCommands
.connectConsortium(new consortiumTreeManager.ConsortiumTreeManager());
// Assert
assertAfterEachTest(executeMock, result, ItemType.LOCAL_CONSORTIUM, Constants.contextValue.localConsortium);
});
it('for Azure Network destination.', async () => {
// Arrange
const executeMock = sinon.spy((..._args: any[]) => {
return new AzureConsortium(
defaultConsortiumName,
defaultSubscriptionId,
defaultResourcesGroup,
defaultMemberName);
});
consortiumCommandsRewire.__set__('execute', executeMock);
// Act
const result = await consortiumCommandsRewire.ConsortiumCommands
.connectConsortium(new consortiumTreeManager.ConsortiumTreeManager());
// Assert
assertAfterEachTest(executeMock, result, ItemType.AZURE_CONSORTIUM, Constants.contextValue.consortium);
});
it('for Ethereum Test Network destination.', async () => {
// Arrange
const executeMock = sinon.spy((..._args: any[]) => {
return new TestNetworkConsortium(defaultConsortiumName);
});
consortiumCommandsRewire.__set__('execute', executeMock);
// Act
const result = await consortiumCommandsRewire.ConsortiumCommands
.connectConsortium(new consortiumTreeManager.ConsortiumTreeManager());
// Assert
assertAfterEachTest(executeMock, result, ItemType.ETHEREUM_TEST_CONSORTIUM, Constants.contextValue.consortium);
});
it('for Ethereum Main Network destination.', async () => {
// Arrange
const executeMock = sinon.spy((..._args: any[]) => {
return new MainNetworkConsortium(defaultConsortiumName);
});
consortiumCommandsRewire.__set__('execute', executeMock);
// Act
const result = await consortiumCommandsRewire.ConsortiumCommands
.connectConsortium(new consortiumTreeManager.ConsortiumTreeManager());
// Assert
assertAfterEachTest(executeMock, result, ItemType.ETHEREUM_MAIN_CONSORTIUM, Constants.contextValue.consortium);
});
});
describe('getNetwork', () => {
let getItemStub: sinon.SinonStub<any[], any> | sinon.SinonStub<unknown[], {}>;
let showQuickPickMock: sinon.SinonStub<any[], any>;
let consortiumTreeManager: { __proto__: any; ConsortiumTreeManager: { prototype: any; new(): void; }; };
before(() => {
consortiumTreeManager = require('../../../src/treeService/ConsortiumTreeManager');
sinon.stub(consortiumTreeManager.__proto__, 'constructor');
sinon.stub(consortiumTreeManager.ConsortiumTreeManager.prototype, 'loadState').returns({});
});
after(() => {
sinon.restore();
});
beforeEach(() => {
getItemStub = sinon.stub(consortiumTreeManager.ConsortiumTreeManager.prototype, 'getItem');
showQuickPickMock = vscodeWindowMock.expects('showQuickPick');
showQuickPickMock.callsFake(async (..._args: any[]) => {
return { cmd: () => undefined };
});
});
afterEach(() => {
getItemStub.restore();
vscodeWindowMock.restore();
});
it('returns networkItem', async () => {
// Arrange
getItemStub.callsFake((...args: any[]) => {
const network = new Network(defaultNetworkName, args[0]);
sinon.stub(network, 'addChild');
return network;
});
// Act
await consortiumCommandsRewire.ConsortiumCommands
.connectConsortium(new consortiumTreeManager.ConsortiumTreeManager());
// Assert
assert.strictEqual(getItemStub.calledOnce, true);
});
it('throws error', async () => {
// Arrange
getItemStub.callsFake((..._args: any[]) => Promise.resolve(undefined));
// Act
const action = async () => {
await consortiumCommandsRewire.ConsortiumCommands
.connectConsortium(new consortiumTreeManager.ConsortiumTreeManager());
};
// Assert
await assert.rejects(action, Error, Constants.errorMessageStrings.ActionAborted);
});
});
describe('validateInput for Local Network', () => {
let showQuickPickMock: sinon.SinonStub<any[], any>;
let showInputBoxMock: sinon.SinonExpectation;
const firstPort = '1234';
const secondPort = '2345';
const firstUrl = `https://0.0.0.1:${firstPort}`;
const secondUrl = `https://0.0.0.2:${secondPort}`;
let consortiumTreeManager: { __proto__: any; ConsortiumTreeManager: { prototype: any; new(): void; }; };
let startGanacheCmdStub: any;
before(() => {
consortiumTreeManager = require('../../../src/treeService/ConsortiumTreeManager');
sinon.stub(consortiumTreeManager.ConsortiumTreeManager.prototype, 'loadState').callsFake(() => {
const networkList: IExtensionItem[] = [];
const localNetwork = new Network(defaultNetworkName, ItemType.LOCAL_NETWORK);
const localNetworkConsortium = new LocalNetworkConsortium(defaultConsortiumName, firstUrl);
localNetwork.addChild(localNetworkConsortium);
const testNetwork = new Network(defaultNetworkName, ItemType.ETHEREUM_TEST_NETWORK);
const testNetworkConsortium = new TestNetworkConsortium(defaultConsortiumName, secondUrl);
testNetwork.addChild(testNetworkConsortium);
networkList.push(localNetwork);
networkList.push(testNetwork);
return networkList;
});
startGanacheCmdStub
= sinon.stub(GanacheService, 'startGanacheServer').callsFake(() => Promise.resolve(null));
});
after(() => {
sinon.restore();
startGanacheCmdStub.restore();
});
beforeEach(() => {
showQuickPickMock = vscodeWindowMock.expects('showQuickPick');
showQuickPickMock.callsFake(async (...args: any[]) => {
return args[0].find((x: any) => x.itemType === ItemType.LOCAL_NETWORK);
});
showInputBoxMock = vscodeWindowMock.expects('showInputBox');
});
afterEach(() => {
vscodeWindowMock.restore();
});
const invalidPortList = [
{ port: '', validationMessage: Constants.validationMessages.invalidPort },
{ port: ' ', validationMessage: Constants.validationMessages.invalidPort },
{ port: '_', validationMessage: Constants.validationMessages.invalidPort },
{ port: 'a1/', validationMessage: Constants.validationMessages.invalidPort },
{ port: '1a', validationMessage: Constants.validationMessages.invalidPort },
{ port: 'port', validationMessage: Constants.validationMessages.invalidPort },
{ port: '0', validationMessage: Constants.validationMessages.invalidPort },
{ port: '01', validationMessage: Constants.validationMessages.invalidPort },
{ port: '65536', validationMessage: Constants.validationMessages.invalidPort },
];
invalidPortList.forEach((invalidPort) => {
it(`showInputBox shows validation messages when port is invalid and equals '${invalidPort.port}'`,
async () => {
// Arrange
let validationMessage = String.Empty;
showInputBoxMock.callsFake(async (..._args: any[]) => {
validationMessage = await _args[0].validateInput(invalidPort.port);
});
// Act
const action = async () => {
await consortiumCommandsRewire.ConsortiumCommands
.connectConsortium(new consortiumTreeManager.ConsortiumTreeManager());
};
// Assert
await assert.rejects(action, CancellationEvent);
assert.strictEqual(validationMessage, invalidPort.validationMessage);
});
});
const existingPortList = [
{ port: firstPort, validationMessage: Constants.validationMessages.networkAlreadyExists },
{ port: secondPort, validationMessage: Constants.validationMessages.portAlreadyInUse },
];
existingPortList.forEach((existingPort) => {
it('showInputBox shows validation messages when port is already in use or exists and ' +
`equals '${existingPort.port}'`, async () => {
// Arrange
let validationMessage = String.Empty;
const commandMock = sinon.mock(command);
const tryExecuteCommandMock = commandMock.expects('tryExecuteCommand');
if (process.platform === 'win32') {
tryExecuteCommandMock
.withArgs(
undefined,
`netstat -ano -p tcp | find "LISTENING" | findstr /r /c:":${secondPort} *[^ ]*:[^ ]*"`)
.returns({ cmdOutput: `\n${secondPort}\n`});
} else {
tryExecuteCommandMock
.withArgs(undefined, `lsof -i tcp:${secondPort} | grep LISTEN | awk '{print $2}'`)
.returns({ cmdOutput: `\n${secondPort}\n`});
}
showInputBoxMock.callsFake(async (..._args: any[]) => {
validationMessage = await _args[0].validateInput(existingPort.port);
});
// Act
const action = async () => {
await consortiumCommandsRewire.ConsortiumCommands
.connectConsortium(new consortiumTreeManager.ConsortiumTreeManager());
};
// Assert
await assert.rejects(action, CancellationEvent);
assert.strictEqual(validationMessage, existingPort.validationMessage);
commandMock.restore();
});
});
const validPortList = ['1', '12', '123', '5678', '65535'];
validPortList.forEach((validPort) => {
it(`showInputBox does not show validation messages when port is valid and equals '${validPort}'`,
async () => {
// Arrange
let validationMessage = String.Empty;
const commandMock = sinon.mock(command);
commandMock.expects('tryExecuteCommand').returns(Promise.resolve({ cmdOutput: ''}));
showInputBoxMock.callsFake(async (..._args: any[]) => {
validationMessage = await _args[0].validateInput(validPort);
return validPort;
});
// Act
await consortiumCommandsRewire.ConsortiumCommands
.connectConsortium(new consortiumTreeManager.ConsortiumTreeManager());
// Assert
assert.strictEqual(validationMessage, null);
commandMock.restore();
});
});
});
const ethereumDestinations = [
{itemType: ItemType.ETHEREUM_TEST_NETWORK, networkName: 'Ethereum Test Network'},
{itemType: ItemType.ETHEREUM_MAIN_NETWORK, networkName: 'Ethereum Main Network'},
];
ethereumDestinations.forEach(async (dest) => {
describe(`validateInput for ${dest.networkName}`, () => {
let consortiumTreeManager: { __proto__: any; ConsortiumTreeManager: { prototype: any; new(): void; }; };
let getItemStub: sinon.SinonStub<any[], any> | sinon.SinonStub<unknown[], {}>;
let showQuickPickMock: sinon.SinonStub<any[], any>;
let showInputBoxMock: sinon.SinonExpectation;
const emptyConsortiumName = '';
const defaultUrl = 'http://0.0.0.1:1234';
before(() => {
consortiumTreeManager = require('../../../src/treeService/ConsortiumTreeManager');
sinon.stub(consortiumTreeManager.ConsortiumTreeManager.prototype, 'loadState').returns({});
});
after(() => {
sinon.restore();
});
beforeEach(() => {
showQuickPickMock = vscodeWindowMock.expects('showQuickPick');
showInputBoxMock = vscodeWindowMock.expects('showInputBox');
getItemStub = sinon.stub(consortiumTreeManager.ConsortiumTreeManager.prototype, 'getItem')
.callsFake((...args: any[]) => {
const network = new Network(defaultNetworkName, args[0]);
sinon.stub(network, 'addChild');
return network;
});
});
afterEach(() => {
getItemStub.restore();
});
it(`showInputBox shows validation messages when consortium name is invalid and equals '${emptyConsortiumName}'`,
async () => {
// Arrange
let validationMessage = String.Empty;
showQuickPickMock.callsFake(async (...args: any[]) => {
return args[0].find((x: any) => x.itemType === dest.itemType);
});
showInputBoxMock.callsFake(async (..._args: any[]) => {
validationMessage = await _args[0].validateInput(emptyConsortiumName);
});
// Act
const action = async () => {
await consortiumCommandsRewire.ConsortiumCommands
.connectConsortium(new consortiumTreeManager.ConsortiumTreeManager());
};
// Assert
await assert.rejects(action, CancellationEvent);
assert.strictEqual(validationMessage, Constants.validationMessages.valueCannotBeEmpty);
});
const invalidConsortiumUrlList = [
{ consortiumUrl: '',
expectedErrors: [
Constants.validationMessages.valueCannotBeEmpty,
Constants.validationMessages.invalidHostAddress ],
},
{ consortiumUrl: ' ',
expectedErrors: [ Constants.validationMessages.invalidHostAddress ],
},
{ consortiumUrl: '_',
expectedErrors: [ Constants.validationMessages.invalidHostAddress ],
},
{ consortiumUrl: '/a1',
expectedErrors: [ Constants.validationMessages.invalidHostAddress ],
},
{ consortiumUrl: 'http://localhost:1234',
expectedErrors: [ Constants.validationMessages.invalidHostAddress ],
},
{ consortiumUrl: 'localhost:1234',
expectedErrors: [ Constants.validationMessages.invalidHostAddress ],
},
];
invalidConsortiumUrlList.forEach((invalidConsortiumUrl) => {
it('showInputBox shows validation messages when consortium url is invalid and equals ' +
`'${invalidConsortiumUrl.consortiumUrl}'`, async () => {
// Arrange
let validationMessage = String.Empty;
showQuickPickMock.callsFake(async (...args: any[]) => {
return args[0].find((x: any) => x.itemType === dest.itemType);
});
showInputBoxMock.twice();
showInputBoxMock.onCall(0).returns(Promise.resolve(defaultConsortiumName));
showInputBoxMock.onCall(1).callsFake(async (..._args: any[]) => {
validationMessage = await _args[0].validateInput(invalidConsortiumUrl.consortiumUrl);
});
// Act
const action = async () => {
await consortiumCommandsRewire.ConsortiumCommands
.connectConsortium(new consortiumTreeManager.ConsortiumTreeManager());
};
// Assert
await assert.rejects(action, CancellationEvent);
invalidConsortiumUrl.expectedErrors.forEach((error) => {
assert.strictEqual(validationMessage.includes(error), true);
});
});
});
const validConsortiumNameList = ['1', 'a', 'a1', ' '];
validConsortiumNameList.forEach((validConsortiumName) => {
it('showInputBox does not show validation messages when consortium name is invalid and equals ' +
`'${validConsortiumName}'`, async () => {
// Arrange
let validationMessage;
showQuickPickMock.callsFake(async (...args: any[]) => {
return args[0].find((x: any) => x.itemType === dest.itemType);
});
showInputBoxMock.twice();
showInputBoxMock.onCall(0).callsFake(async (..._args: any[]) => {
validationMessage = await _args[0].validateInput(validConsortiumName);
return validConsortiumName;
});
showInputBoxMock.onCall(1).returns(Promise.resolve(defaultUrl));
// Act
await consortiumCommandsRewire.ConsortiumCommands
.connectConsortium(new consortiumTreeManager.ConsortiumTreeManager());
// Assert
assert.strictEqual(validationMessage, undefined);
});
});
const validConsortiumUrlList = ['http://0.0.0.1:2345', 'https://0.0.0.2:3456'];
validConsortiumUrlList.forEach((validConsortiumUrl) => {
it('showInputBox does not show validation messages when consortium url is invalid and equals ' +
`'${validConsortiumUrl}'`, async () => {
// Arrange
let validationMessage;
showQuickPickMock.callsFake(async (...args: any[]) => {
return args[0].find((x: any) => x.itemType === dest.itemType);
});
showInputBoxMock.twice();
showInputBoxMock.onCall(0).returns(Promise.resolve({}));
showInputBoxMock.onCall(1).callsFake(async (..._args: any[]) => {
validationMessage = await _args[0].validateInput(validConsortiumUrl);
return validConsortiumUrl;
});
// Act
await consortiumCommandsRewire.ConsortiumCommands
.connectConsortium(new consortiumTreeManager.ConsortiumTreeManager());
// Assert
assert.strictEqual(validationMessage, null);
});
});
});
});
});
});

Просмотреть файл

@ -0,0 +1,90 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as assert from 'assert';
import rewire = require('rewire');
import * as sinon from 'sinon';
import { QuickPickOptions } from 'vscode';
import { Constants } from '../../../src/Constants';
import * as helpers from '../../../src/helpers';
import {
IExtensionItem,
Info,
ItemType,
Network,
} from '../../../src/Models';
import { ConsortiumTreeManager } from '../../../src/treeService/ConsortiumTreeManager';
describe('Create Consortium', () => {
afterEach(() => {
sinon.restore();
});
it('showQuickPick should be executed with Constants.placeholders.selectConsortium placeholder',
async () => {
// Arrange
const consortiumCommandsRewire = rewire('../../../src/commands/ConsortiumCommands');
const showQuickPickStub = sinon.stub();
showQuickPickStub
.returns({
cmd: sinon.mock().returns(''),
itemType: ItemType.AZURE_BLOCKCHAIN,
label: Constants.uiCommandStrings.CreateConsortiumAzureBlockchainService,
});
consortiumCommandsRewire.__set__('getNetwork', sinon.mock().returns(new Info('label')));
consortiumCommandsRewire.__set__('getConnectedAbsConsortiums', sinon.mock().returns([]));
sinon.replace(helpers, 'showQuickPick', showQuickPickStub);
// Act
await consortiumCommandsRewire.ConsortiumCommands.createConsortium();
// Assert
assert.strictEqual(
(showQuickPickStub.getCall(0).args[1] as QuickPickOptions).placeHolder,
Constants.placeholders.selectConsortium,
);
});
describe('getNetwork', () => {
const consortiumCommandsRewire = rewire('../../../src/commands/ConsortiumCommands');
sinon.replace(
ConsortiumTreeManager.prototype,
'getItem',
sinon.stub().returns(Promise.resolve(undefined)),
);
const getNetwork = consortiumCommandsRewire.__get__('getNetwork');
it('getNetwork should throw an error if no network found',
async () => {
// Arrange
const consortiumTreeManagerStub = {
getItem(_itemType: ItemType): IExtensionItem | undefined { return undefined; },
};
// Act and Assert
await assert.rejects(
getNetwork(
consortiumTreeManagerStub as ConsortiumTreeManager,
new Object() as ItemType,
));
});
it('getNetwork should not throw an error if network found',
async () => {
// Arrange
const consortiumTreeManagerStub = {
getItem(_itemType: ItemType): IExtensionItem | undefined {
return new Network('network', ItemType.AZURE_BLOCKCHAIN);
},
};
// Act and Assert
await assert.doesNotReject(
getNetwork(
consortiumTreeManagerStub as ConsortiumTreeManager,
new Object() as ItemType,
));
});
});
});

Просмотреть файл

@ -0,0 +1,69 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as assert from 'assert';
import rewire = require('rewire');
import * as sinon from 'sinon';
import { GanacheService } from '../../../src/GanacheService/GanacheService';
import {
IExtensionItem,
LocalNetworkConsortium,
MainNetworkConsortium,
} from '../../../src/Models';
import { ConsortiumTreeManager } from '../../../src/treeService/ConsortiumTreeManager';
import { ConsortiumView } from '../../../src/ViewItems/ConsortiumView';
describe('Disconnect Consortium', () => {
const consortiumTreeManagerStub = {
removeItem(_extensionItem: IExtensionItem): void { /* empty */ },
};
afterEach(() => {
sinon.restore();
});
describe('DisconnectConsortium_ShouldStopGanacheServer_LocalNetworkConsortium', () => {
const consortiums = [
{ consortiumInstance: new LocalNetworkConsortium('name', 'url'), executed: true },
{ consortiumInstance: new MainNetworkConsortium('name'), executed: false },
];
consortiums.forEach(async (consortium) => {
it('GanacheService.stopGanacheServer() should be executed only for LocalNetworkConsortium.',
async () => {
// Arrange
const consortiumCommandsRewire = rewire('../../../src/commands/ConsortiumCommands');
const consortiumView = new ConsortiumView(consortium.consortiumInstance);
const stopGanacheServerStub = sinon.stub(GanacheService, 'stopGanacheServer');
// Act
await consortiumCommandsRewire.ConsortiumCommands.disconnectConsortium(
(consortiumTreeManagerStub as ConsortiumTreeManager),
consortiumView,
);
// Assert
assert.strictEqual(stopGanacheServerStub.calledOnce, consortium.executed);
});
});
});
describe('DisconnectConsortium_ShouldRemoveItem', () => {
it('consortiumTreeManager.removeItem() should not be executed with IExtensionItem object from viewItem',
async () => {
// Arrange
const consortiumCommandsRewire = rewire('../../../src/commands/ConsortiumCommands');
const consortiumView = new ConsortiumView(new MainNetworkConsortium('name'));
const spy = sinon.spy(consortiumTreeManagerStub, 'removeItem');
// Act
await consortiumCommandsRewire.ConsortiumCommands.disconnectConsortium(
(consortiumTreeManagerStub as ConsortiumTreeManager),
consortiumView,
);
// Assert
assert.strictEqual(spy.calledOnce, true);
});
});
});

Просмотреть файл

@ -0,0 +1,678 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as assert from 'assert';
import * as fs from 'fs-extra';
import rewire = require('rewire');
import * as sinon from 'sinon';
import * as vscode from 'vscode';
import { Constants } from '../../src/Constants';
import * as helpers from '../../src/helpers';
import { CancellationEvent } from '../../src/Models';
import { Output } from '../../src/Output';
describe('ProjectСommands', () => {
describe('Unit tests', () => {
let helpersMock: sinon.SinonMock;
const projectPath = 'projectPath';
const truffleBoxName = 'truffleBoxName';
before(() => {
helpersMock = sinon.mock(helpers);
});
after(() => {
helpersMock.restore();
});
describe('newSolidityProject', () => {
let gitHelperMock: sinon.SinonMock;
let showQuickPickMock: sinon.SinonStub<any[], any>;
let gitInitMock: sinon.SinonStub<any[], any>;
let requiredMock: sinon.SinonMock;
let checkRequiredAppsMock: sinon.SinonExpectation;
beforeEach(() => {
helpersMock = sinon.mock(helpers);
showQuickPickMock = helpersMock.expects('showQuickPick').returns({
cmd: () => undefined,
label: 'emptyProject',
});
gitHelperMock = sinon.mock(helpers.gitHelper);
gitInitMock = gitHelperMock.expects('gitInit').returns(() => undefined);
requiredMock = sinon.mock(helpers.required);
checkRequiredAppsMock = requiredMock.expects('checkRequiredApps');
});
afterEach(() => {
requiredMock.restore();
gitHelperMock.restore();
helpersMock.restore();
});
it('Method newSolidityProject does not provide type of new project, because we have not required apps.',
async () => {
// Arrange
checkRequiredAppsMock.returns(false);
const projectCommandsRewire = rewire('../../src/commands/ProjectCommands');
projectCommandsRewire.__set__('chooseNewProjectDir', sinon.mock().returns(''));
const chooseNewProjectDirMock = projectCommandsRewire.__get__('chooseNewProjectDir');
// Act
await projectCommandsRewire.ProjectCommands.newSolidityProject();
// Assert
assert.strictEqual(checkRequiredAppsMock.calledOnce, true);
assert.strictEqual(showQuickPickMock.notCalled, true);
assert.strictEqual(chooseNewProjectDirMock.notCalled, true);
assert.strictEqual(gitInitMock.notCalled, true);
});
it('Method newSolidityProject provide type of new project, because we have all required apps.', async () => {
// Arrange
checkRequiredAppsMock.returns(true);
const projectCommandsRewire = rewire('../../src/commands/ProjectCommands');
projectCommandsRewire.__set__('chooseNewProjectDir', sinon.mock().returns(projectPath));
const chooseNewProjectDirMock = projectCommandsRewire.__get__('chooseNewProjectDir');
// Act
await projectCommandsRewire.ProjectCommands.newSolidityProject();
// Assert
assert.strictEqual(checkRequiredAppsMock.calledOnce, true);
assert.strictEqual(showQuickPickMock.calledOnce, true);
assert.strictEqual(chooseNewProjectDirMock.calledOnce, true);
assert.strictEqual(gitInitMock.calledOnce, true);
assert.strictEqual(gitInitMock.args[0][0], projectPath);
});
});
describe('chooseNewProjectDir', () => {
const firstProjectPath = 'firstProjectPath';
const secondProjectPath = 'secondProjectPath';
let fsMock: sinon.SinonMock;
let windowMock: sinon.SinonMock;
let showOpenFolderDialogMock: sinon.SinonExpectation;
let ensureDirMock: sinon.SinonExpectation;
let readdirMock: sinon.SinonExpectation;
let showErrorMessageMock: sinon.SinonStub<any[], any>;
beforeEach(() => {
helpersMock = sinon.mock(helpers);
showOpenFolderDialogMock = helpersMock.expects('showOpenFolderDialog');
showOpenFolderDialogMock.twice();
showOpenFolderDialogMock.onCall(0).returns(firstProjectPath);
showOpenFolderDialogMock.onCall(1).returns(secondProjectPath);
fsMock = sinon.mock(fs);
ensureDirMock = fsMock.expects('ensureDir');
readdirMock = fsMock.expects('readdir');
windowMock = sinon.mock(vscode.window);
showErrorMessageMock = windowMock.expects('showErrorMessage');
});
afterEach(() => {
fsMock.restore();
windowMock.restore();
helpersMock.restore();
});
it('Method chooseNewProjectDir returns projectPath which we selected', async () => {
// Arrange
readdirMock.returns([]);
const projectCommandsRewire = rewire('../../src/commands/ProjectCommands');
const chooseNewProjectDir = projectCommandsRewire.__get__('chooseNewProjectDir');
// Act
const newProjectPath = await chooseNewProjectDir();
// Assert
assert.strictEqual(showOpenFolderDialogMock.calledOnce, true);
assert.strictEqual(ensureDirMock.calledOnce, true);
assert.strictEqual(ensureDirMock.args[0][0], firstProjectPath);
assert.strictEqual(readdirMock.calledOnce, true);
assert.strictEqual(readdirMock.args[0][0], firstProjectPath);
assert.strictEqual(newProjectPath, firstProjectPath);
assert.strictEqual(showErrorMessageMock.notCalled, true);
});
it('Method chooseNewProjectDir returns projectPath at second time, because we selected not empty dir ' +
'at first time.', async () => {
// Arrange
ensureDirMock.twice();
readdirMock.twice();
readdirMock.onCall(0).returns(['somePath']);
readdirMock.onCall(1).returns([]);
const projectCommandsRewire = rewire('../../src/commands/ProjectCommands');
const chooseNewProjectDir = projectCommandsRewire.__get__('chooseNewProjectDir');
showErrorMessageMock.returns(Constants.informationMessage.openButton);
// Act
const newProjectPath = await chooseNewProjectDir();
// Assert
assert.strictEqual(showOpenFolderDialogMock.calledTwice, true);
assert.strictEqual(ensureDirMock.calledTwice, true);
assert.strictEqual(ensureDirMock.firstCall.args[0], firstProjectPath);
assert.strictEqual(ensureDirMock.secondCall.args[0], secondProjectPath);
assert.strictEqual(readdirMock.calledTwice, true);
assert.strictEqual(readdirMock.firstCall.args[0], firstProjectPath);
assert.strictEqual(readdirMock.secondCall.args[0], secondProjectPath);
assert.strictEqual(newProjectPath, secondProjectPath);
assert.strictEqual(showErrorMessageMock.calledOnce, true);
});
it('Method chooseNewProjectDir throws CancellationEvent, because user click on Cancel button', async () => {
// Arrange
readdirMock.returns(['somePath']);
const projectCommandsRewire = rewire('../../src/commands/ProjectCommands');
const chooseNewProjectDir = projectCommandsRewire.__get__('chooseNewProjectDir');
showErrorMessageMock.returns(Constants.informationMessage.cancelButton);
// Act
const action = async () => {
return await chooseNewProjectDir();
};
// Assert
await assert.rejects(action, CancellationEvent);
assert.strictEqual(showOpenFolderDialogMock.calledOnce, true);
assert.strictEqual(ensureDirMock.calledOnce, true);
assert.strictEqual(ensureDirMock.args[0][0], firstProjectPath);
assert.strictEqual(readdirMock.calledOnce, true);
assert.strictEqual(readdirMock.args[0][0], firstProjectPath);
assert.strictEqual(showErrorMessageMock.calledOnce, true);
});
});
describe('createNewEmptyProject', () => {
let withProgressMock: sinon.SinonStub<any[], any>;
let windowMock: sinon.SinonMock;
beforeEach(() => {
windowMock = sinon.mock(vscode.window);
withProgressMock = windowMock.expects('withProgress');
});
afterEach(() => {
windowMock.restore();
});
it('Method createNewEmptyProject runs method createProject.', async () => {
// Arrange
const projectCommandsRewire = rewire('../../src/commands/ProjectCommands');
const createNewEmptyProject = projectCommandsRewire.__get__('createNewEmptyProject');
// Act
await createNewEmptyProject(projectPath);
// Assert
assert.strictEqual(withProgressMock.calledOnce, true);
});
});
it('Method createProjectFromTruffleBox runs method createProject with special truffle box name.', async () => {
// Arrange
const projectCommandsRewire = rewire('../../src/commands/ProjectCommands');
const createProjectFromTruffleBox = projectCommandsRewire.__get__('createProjectFromTruffleBox');
projectCommandsRewire.__set__('getTruffleBoxName', sinon.mock().returns(truffleBoxName));
const getTruffleBoxNameMock = projectCommandsRewire.__get__('getTruffleBoxName');
projectCommandsRewire.__set__('createProject', sinon.mock());
const createProjectMock = projectCommandsRewire.__get__('createProject');
// Act
await createProjectFromTruffleBox(projectPath);
// Assert
assert.strictEqual(getTruffleBoxNameMock.calledOnce, true);
assert.strictEqual(createProjectMock.calledOnce, true);
assert.strictEqual(createProjectMock.args[0][0], projectPath);
assert.strictEqual(createProjectMock.args[0][1], truffleBoxName);
});
describe('createProject', () => {
let outputMock: sinon.SinonMock;
let showMock: sinon.SinonExpectation;
let outputCommandHelperMock: sinon.SinonMock;
let executeCommandMock: sinon.SinonExpectation;
let workspaceMock: sinon.SinonMock;
let updateWorkspaceFoldersMock: sinon.SinonExpectation;
let fsMock: sinon.SinonMock;
let emptyDirSyncMock: sinon.SinonExpectation;
beforeEach(() => {
outputMock = sinon.mock(Output);
showMock = outputMock.expects('show');
outputCommandHelperMock = sinon.mock(helpers.outputCommandHelper);
executeCommandMock = outputCommandHelperMock.expects('executeCommand');
workspaceMock = sinon.mock(vscode.workspace);
updateWorkspaceFoldersMock = workspaceMock.expects('updateWorkspaceFolders');
fsMock = sinon.mock(fs);
emptyDirSyncMock = fsMock.expects('emptyDirSync');
});
afterEach(() => {
outputMock.restore();
outputCommandHelperMock.restore();
workspaceMock.restore();
fsMock.restore();
});
it('Method createProject run command for create new project and project was created successfully. ' +
'Workspace was updated to certain workspace.', async () => {
// Arrange
sinon.stub(vscode.workspace, 'workspaceFolders').value(['1']);
const projectCommandsRewire = rewire('../../src/commands/ProjectCommands');
const createProject = projectCommandsRewire.__get__('createProject');
// Act
await createProject(projectPath, truffleBoxName);
// Assert
assert.strictEqual(showMock.calledOnce, true);
assert.strictEqual(executeCommandMock.calledOnce, true);
assert.strictEqual(executeCommandMock.args[0][0], projectPath);
assert.strictEqual(executeCommandMock.args[0][1], 'npx');
assert.strictEqual(executeCommandMock.args[0][2], Constants.truffleCommand);
assert.strictEqual(executeCommandMock.args[0][3], 'unbox');
assert.strictEqual(executeCommandMock.args[0][4], truffleBoxName);
assert.strictEqual(updateWorkspaceFoldersMock.calledOnce, true);
assert.strictEqual(updateWorkspaceFoldersMock.args[0][0], 0);
assert.strictEqual(updateWorkspaceFoldersMock.args[0][1], 1);
assert.strictEqual(updateWorkspaceFoldersMock.args[0][2].uri.path, `/${projectPath}`);
assert.strictEqual(emptyDirSyncMock.notCalled, true);
});
it('Method createProject run command for create new project and project was created successfully. ' +
'Workspace was not updated to certain workspace.', async () => {
// Arrange
sinon.stub(vscode.workspace, 'workspaceFolders').value(undefined);
const projectCommandsRewire = rewire('../../src/commands/ProjectCommands');
const createProject = projectCommandsRewire.__get__('createProject');
// Act
await createProject(projectPath, truffleBoxName);
// Assert
assert.strictEqual(showMock.calledOnce, true);
assert.strictEqual(executeCommandMock.calledOnce, true);
assert.strictEqual(executeCommandMock.args[0][0], projectPath);
assert.strictEqual(executeCommandMock.args[0][1], 'npx');
assert.strictEqual(executeCommandMock.args[0][2], Constants.truffleCommand);
assert.strictEqual(executeCommandMock.args[0][3], 'unbox');
assert.strictEqual(executeCommandMock.args[0][4], truffleBoxName);
assert.strictEqual(updateWorkspaceFoldersMock.calledOnce, true);
assert.strictEqual(updateWorkspaceFoldersMock.args[0][0], 0);
assert.strictEqual(updateWorkspaceFoldersMock.args[0][1], null);
assert.strictEqual(updateWorkspaceFoldersMock.args[0][2].uri.path, `/${projectPath}`);
assert.strictEqual(emptyDirSyncMock.notCalled, true);
});
it('Method createProject run command for create new project and creation was fell of project.', async () => {
// Arrange
executeCommandMock.throws();
const projectCommandsRewire = rewire('../../src/commands/ProjectCommands');
const createProject = projectCommandsRewire.__get__('createProject');
// Act
const action = async () => {
await createProject(projectPath, truffleBoxName);
};
// Assert
await assert.rejects(
action,
Error,
Constants.errorMessageStrings.NewProjectCreationFailed);
assert.strictEqual(showMock.calledOnce, true);
assert.strictEqual(executeCommandMock.calledOnce, true);
assert.strictEqual(executeCommandMock.args[0][0], projectPath);
assert.strictEqual(executeCommandMock.args[0][1], 'npx');
assert.strictEqual(executeCommandMock.args[0][2], Constants.truffleCommand);
assert.strictEqual(executeCommandMock.args[0][3], 'unbox');
assert.strictEqual(executeCommandMock.args[0][4], truffleBoxName);
assert.strictEqual(updateWorkspaceFoldersMock.notCalled, true);
assert.strictEqual(emptyDirSyncMock.calledOnce, true);
assert.strictEqual(emptyDirSyncMock.args[0][0], projectPath);
});
});
it('Method getTruffleBoxName should return a value', async () => {
// Arrange
const testName = 'test';
const projectCommandsRewire = rewire('../../src/commands/ProjectCommands');
const getTruffleBoxName = projectCommandsRewire.__get__('getTruffleBoxName');
const showInputBoxMock = helpersMock.expects('showInputBox');
showInputBoxMock.returns(testName);
// Act
const result = await getTruffleBoxName();
// Assert
assert.strictEqual(result, testName);
assert.strictEqual(showInputBoxMock.calledOnce, true);
});
});
describe('Integration tests', () => {
let helpersMock: sinon.SinonMock;
const truffleBoxName = 'truffleBoxName';
const firstProjectPath = 'firstProjectPath';
const secondProjectPath = 'secondProjectPath';
before(() => {
helpersMock = sinon.mock(helpers);
});
after(() => {
helpersMock.restore();
});
let gitHelperMock: sinon.SinonMock;
let showQuickPickMock: sinon.SinonStub<any[], any>;
let gitInitMock: sinon.SinonStub<any[], any>;
let requiredMock: sinon.SinonMock;
let checkRequiredAppsMock: sinon.SinonExpectation;
let fsMock: sinon.SinonMock;
let windowMock: sinon.SinonMock;
let showOpenFolderDialogMock: sinon.SinonExpectation;
let ensureDirMock: sinon.SinonExpectation;
let readdirMock: sinon.SinonExpectation;
let showErrorMessageMock: sinon.SinonStub<any[], any>;
let outputCommandHelperMock: sinon.SinonMock;
let executeCommandMock: sinon.SinonExpectation;
let outputMock: sinon.SinonMock;
let showMock: sinon.SinonExpectation;
let workspaceMock: sinon.SinonMock;
let updateWorkspaceFoldersMock: sinon.SinonExpectation;
let emptyDirSyncMock: sinon.SinonExpectation;
beforeEach(() => {
helpersMock = sinon.mock(helpers);
showQuickPickMock = helpersMock.expects('showQuickPick');
gitHelperMock = sinon.mock(helpers.gitHelper);
gitInitMock = gitHelperMock.expects('gitInit').returns(() => undefined);
requiredMock = sinon.mock(helpers.required);
checkRequiredAppsMock = requiredMock.expects('checkRequiredApps');
outputCommandHelperMock = sinon.mock(helpers.outputCommandHelper);
executeCommandMock = outputCommandHelperMock.expects('executeCommand');
showOpenFolderDialogMock = helpersMock.expects('showOpenFolderDialog');
showOpenFolderDialogMock.twice();
showOpenFolderDialogMock.onCall(0).returns(firstProjectPath);
showOpenFolderDialogMock.onCall(1).returns(secondProjectPath);
fsMock = sinon.mock(fs);
ensureDirMock = fsMock.expects('ensureDir');
readdirMock = fsMock.expects('readdir');
emptyDirSyncMock = fsMock.expects('emptyDirSync');
windowMock = sinon.mock(vscode.window);
showErrorMessageMock = windowMock.expects('showErrorMessage');
workspaceMock = sinon.mock(vscode.workspace);
updateWorkspaceFoldersMock = workspaceMock.expects('updateWorkspaceFolders');
outputMock = sinon.mock(Output);
showMock = outputMock.expects('show');
});
afterEach(() => {
requiredMock.restore();
gitHelperMock.restore();
helpersMock.restore();
fsMock.restore();
windowMock.restore();
outputMock.restore();
outputCommandHelperMock.restore();
workspaceMock.restore();
});
it('Method chooseNewProjectDir returns projectPath which we selected at second time.', async () => {
// Arrange
checkRequiredAppsMock.returns(true);
ensureDirMock.twice();
readdirMock.twice();
readdirMock.onCall(0).returns(['somePath']);
readdirMock.onCall(1).returns([]);
showQuickPickMock.returns({
cmd: () => undefined,
label: 'emptyProject',
});
const projectCommandsRewire = rewire('../../src/commands/ProjectCommands');
showErrorMessageMock.returns(Constants.informationMessage.openButton);
// Act
await projectCommandsRewire.ProjectCommands.newSolidityProject();
// Assert
assert.strictEqual(checkRequiredAppsMock.calledOnce, true);
assert.strictEqual(showQuickPickMock.calledOnce, true);
assert.strictEqual(gitInitMock.calledOnce, true);
assert.strictEqual(gitInitMock.args[0][0], secondProjectPath);
assert.strictEqual(showOpenFolderDialogMock.calledTwice, true);
assert.strictEqual(ensureDirMock.calledTwice, true);
assert.strictEqual(ensureDirMock.firstCall.args[0], firstProjectPath);
assert.strictEqual(ensureDirMock.secondCall.args[0], secondProjectPath);
assert.strictEqual(readdirMock.calledTwice, true);
assert.strictEqual(readdirMock.firstCall.args[0], firstProjectPath);
assert.strictEqual(readdirMock.secondCall.args[0], secondProjectPath);
assert.strictEqual(showErrorMessageMock.calledOnce, true);
});
it('Method chooseNewProjectDir returns projectPath which we selected at first time.', async () => {
// Arrange
checkRequiredAppsMock.returns(true);
sinon.stub(vscode.workspace, 'workspaceFolders').value(['1']);
readdirMock.returns([]);
showQuickPickMock.returns({
cmd: () => undefined,
label: 'emptyProject',
});
const projectCommandsRewire = rewire('../../src/commands/ProjectCommands');
// Act
await projectCommandsRewire.ProjectCommands.newSolidityProject();
// Assert
assert.strictEqual(checkRequiredAppsMock.calledOnce, true);
assert.strictEqual(showQuickPickMock.calledOnce, true);
assert.strictEqual(gitInitMock.calledOnce, true);
assert.strictEqual(gitInitMock.args[0][0], firstProjectPath);
assert.strictEqual(showOpenFolderDialogMock.calledOnce, true);
assert.strictEqual(ensureDirMock.calledOnce, true);
assert.strictEqual(ensureDirMock.args[0][0], firstProjectPath);
assert.strictEqual(readdirMock.calledOnce, true);
assert.strictEqual(readdirMock.args[0][0], firstProjectPath);
assert.strictEqual(showErrorMessageMock.notCalled, true);
});
it('Method createNewEmptyProject runs method createProject, and new empty project was created successfully.',
async () => {
// Arrange
checkRequiredAppsMock.returns(true);
readdirMock.returns([]);
sinon.stub(vscode.workspace, 'workspaceFolders').value(['1']);
const projectCommandsRewire = rewire('../../src/commands/ProjectCommands');
const createNewEmptyProject = projectCommandsRewire.__get__('createNewEmptyProject');
showErrorMessageMock.returns(Constants.informationMessage.openButton);
showQuickPickMock.returns({
cmd: createNewEmptyProject,
label: Constants.typeOfSolidityProject.text.emptyProject,
});
// Act
await projectCommandsRewire.ProjectCommands.newSolidityProject();
// Assert
assert.strictEqual(checkRequiredAppsMock.calledOnce, true);
assert.strictEqual(showQuickPickMock.calledOnce, true);
assert.strictEqual(gitInitMock.calledOnce, true);
assert.strictEqual(gitInitMock.args[0][0], firstProjectPath);
assert.strictEqual(showOpenFolderDialogMock.calledOnce, true);
assert.strictEqual(ensureDirMock.calledOnce, true);
assert.strictEqual(ensureDirMock.args[0][0], firstProjectPath);
assert.strictEqual(readdirMock.calledOnce, true);
assert.strictEqual(readdirMock.args[0][0], firstProjectPath);
assert.strictEqual(showErrorMessageMock.notCalled, true);
assert.strictEqual(showMock.calledOnce, true);
assert.strictEqual(executeCommandMock.calledOnce, true);
assert.strictEqual(executeCommandMock.args[0][0], firstProjectPath);
assert.strictEqual(executeCommandMock.args[0][1], 'npx');
assert.strictEqual(executeCommandMock.args[0][2], Constants.truffleCommand);
assert.strictEqual(executeCommandMock.args[0][3], 'unbox');
assert.strictEqual(executeCommandMock.args[0][4], Constants.defaultTruffleBox);
assert.strictEqual(updateWorkspaceFoldersMock.calledOnce, true);
assert.strictEqual(updateWorkspaceFoldersMock.args[0][0], 0);
assert.strictEqual(updateWorkspaceFoldersMock.args[0][1], 1);
assert.strictEqual(updateWorkspaceFoldersMock.args[0][2].uri.path, `/${firstProjectPath}`);
assert.strictEqual(emptyDirSyncMock.notCalled, true);
});
it('Method createNewEmptyProject runs method createProject, and method createProject throws error.',
async () => {
// Arrange
checkRequiredAppsMock.returns(true);
readdirMock.returns([]);
executeCommandMock.throws();
const projectCommandsRewire = rewire('../../src/commands/ProjectCommands');
const createNewEmptyProject = projectCommandsRewire.__get__('createNewEmptyProject');
showErrorMessageMock.returns(Constants.informationMessage.openButton);
showQuickPickMock.returns({
cmd: createNewEmptyProject,
label: Constants.typeOfSolidityProject.text.emptyProject,
});
// Act
const action = async () => {
await projectCommandsRewire.ProjectCommands.newSolidityProject();
};
// Assert
await assert.rejects(
action,
Error,
Constants.errorMessageStrings.NewProjectCreationFailed);
assert.strictEqual(checkRequiredAppsMock.calledOnce, true);
assert.strictEqual(showQuickPickMock.calledOnce, true);
assert.strictEqual(gitInitMock.notCalled, true);
assert.strictEqual(showOpenFolderDialogMock.calledOnce, true);
assert.strictEqual(ensureDirMock.calledOnce, true);
assert.strictEqual(ensureDirMock.args[0][0], firstProjectPath);
assert.strictEqual(readdirMock.calledOnce, true);
assert.strictEqual(readdirMock.args[0][0], firstProjectPath);
assert.strictEqual(showErrorMessageMock.notCalled, true);
assert.strictEqual(showMock.calledOnce, true);
assert.strictEqual(executeCommandMock.calledOnce, true);
assert.strictEqual(executeCommandMock.args[0][0], firstProjectPath);
assert.strictEqual(executeCommandMock.args[0][1], 'npx');
assert.strictEqual(executeCommandMock.args[0][2], Constants.truffleCommand);
assert.strictEqual(executeCommandMock.args[0][3], 'unbox');
assert.strictEqual(executeCommandMock.args[0][4], Constants.defaultTruffleBox);
assert.strictEqual(updateWorkspaceFoldersMock.notCalled, true);
assert.strictEqual(emptyDirSyncMock.calledOnce, true);
assert.strictEqual(emptyDirSyncMock.args[0][0], firstProjectPath);
});
it('Method createProjectFromTruffleBox get truffleBoxName and create new project with this name.',
async () => {
// Arrange
checkRequiredAppsMock.returns(true);
readdirMock.returns([]);
sinon.stub(vscode.workspace, 'workspaceFolders').value(['1']);
const projectCommandsRewire = rewire('../../src/commands/ProjectCommands');
projectCommandsRewire.__set__('getTruffleBoxName', sinon.mock().returns(truffleBoxName));
const getTruffleBoxNameMock = projectCommandsRewire.__get__('getTruffleBoxName');
const createProjectFromTruffleBox = projectCommandsRewire.__get__('createProjectFromTruffleBox');
showErrorMessageMock.returns(Constants.informationMessage.openButton);
showQuickPickMock.returns({
cmd: createProjectFromTruffleBox,
label: Constants.typeOfSolidityProject.text.projectFromTruffleBox,
});
// Act
await projectCommandsRewire.ProjectCommands.newSolidityProject();
// Assert
assert.strictEqual(checkRequiredAppsMock.calledOnce, true);
assert.strictEqual(showQuickPickMock.calledOnce, true);
assert.strictEqual(gitInitMock.calledOnce, true);
assert.strictEqual(getTruffleBoxNameMock.calledOnce, true);
assert.strictEqual(gitInitMock.args[0][0], firstProjectPath);
assert.strictEqual(showOpenFolderDialogMock.calledOnce, true);
assert.strictEqual(ensureDirMock.calledOnce, true);
assert.strictEqual(ensureDirMock.args[0][0], firstProjectPath);
assert.strictEqual(readdirMock.calledOnce, true);
assert.strictEqual(readdirMock.args[0][0], firstProjectPath);
assert.strictEqual(showErrorMessageMock.notCalled, true);
assert.strictEqual(showMock.calledOnce, true);
assert.strictEqual(executeCommandMock.calledOnce, true);
assert.strictEqual(executeCommandMock.args[0][0], firstProjectPath);
assert.strictEqual(executeCommandMock.args[0][1], 'npx');
assert.strictEqual(executeCommandMock.args[0][2], Constants.truffleCommand);
assert.strictEqual(executeCommandMock.args[0][3], 'unbox');
assert.strictEqual(executeCommandMock.args[0][4], truffleBoxName);
assert.strictEqual(updateWorkspaceFoldersMock.calledOnce, true);
assert.strictEqual(updateWorkspaceFoldersMock.args[0][0], 0);
assert.strictEqual(updateWorkspaceFoldersMock.args[0][1], 1);
assert.strictEqual(updateWorkspaceFoldersMock.args[0][2].uri.path, `/${firstProjectPath}`);
assert.strictEqual(emptyDirSyncMock.notCalled, true);
});
it('Method createProjectFromTruffleBox get truffleBoxName and create new project with this name. ' +
'showInputBox called twice in getTruffleBoxName.', async () => {
// Arrange
checkRequiredAppsMock.returns(true);
readdirMock.returns([]);
executeCommandMock.throws();
const projectCommandsRewire = rewire('../../src/commands/ProjectCommands');
projectCommandsRewire.__set__('getTruffleBoxName', sinon.mock().returns(truffleBoxName));
const getTruffleBoxNameMock = projectCommandsRewire.__get__('getTruffleBoxName');
const createProjectFromTruffleBox = projectCommandsRewire.__get__('createProjectFromTruffleBox');
showErrorMessageMock.returns(Constants.informationMessage.openButton);
showQuickPickMock.returns({
cmd: createProjectFromTruffleBox,
label: Constants.typeOfSolidityProject.text.projectFromTruffleBox,
});
// Act
const action = async () => {
await projectCommandsRewire.ProjectCommands.newSolidityProject();
};
// Assert
await assert.rejects(
action,
Error,
Constants.errorMessageStrings.NewProjectCreationFailed);
assert.strictEqual(checkRequiredAppsMock.calledOnce, true);
assert.strictEqual(showQuickPickMock.calledOnce, true);
assert.strictEqual(gitInitMock.notCalled, true);
assert.strictEqual(getTruffleBoxNameMock.calledOnce, true);
assert.strictEqual(showOpenFolderDialogMock.calledOnce, true);
assert.strictEqual(ensureDirMock.calledOnce, true);
assert.strictEqual(ensureDirMock.args[0][0], firstProjectPath);
assert.strictEqual(readdirMock.calledOnce, true);
assert.strictEqual(readdirMock.args[0][0], firstProjectPath);
assert.strictEqual(showErrorMessageMock.notCalled, true);
assert.strictEqual(showMock.calledOnce, true);
assert.strictEqual(executeCommandMock.calledOnce, true);
assert.strictEqual(executeCommandMock.args[0][0], firstProjectPath);
assert.strictEqual(executeCommandMock.args[0][1], 'npx');
assert.strictEqual(executeCommandMock.args[0][2], Constants.truffleCommand);
assert.strictEqual(executeCommandMock.args[0][3], 'unbox');
assert.strictEqual(executeCommandMock.args[0][4], truffleBoxName);
assert.strictEqual(updateWorkspaceFoldersMock.notCalled, true);
assert.strictEqual(emptyDirSyncMock.calledOnce, true);
assert.strictEqual(emptyDirSyncMock.args[0][0], firstProjectPath);
});
});
});

221
test/index.ts Normal file
Просмотреть файл

@ -0,0 +1,221 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
'use strict';
declare var global: any;
/* tslint:disable*/
import * as fs from 'fs';
import * as glob from 'glob';
import * as paths from 'path';
const istanbul = require('istanbul');
const Mocha = require('mocha');
const remapIstanbul = require('remap-istanbul');
// Linux: prevent a weird NPE when mocha on Linux requires the window size from the TTY
// Since we are not running in a tty environment, we just implementt he method statically
const tty = require('tty');
if (!tty.getWindowSize) {
tty.getWindowSize = (): number[] => {
return [80, 75];
};
}
let mocha = new Mocha({
ui: 'bdd',
useColors: true,
});
function configure(mochaOpts: any): void {
mocha = new Mocha(mochaOpts);
}
exports.configure = configure;
function _mkDirIfExists(dir: string): void {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
}
function _readCoverOptions(testsRoot: string): ITestRunnerOptions | undefined {
const coverConfigPath = paths.join(testsRoot, '..', '..', 'coverconfig.json');
if (fs.existsSync(coverConfigPath)) {
const configContent = fs.readFileSync(coverConfigPath, 'utf-8');
return JSON.parse(configContent);
}
return undefined;
}
function run(testsRoot: string, clb: any): any {
// Read configuration for the coverage file
const coverOptions = _readCoverOptions(testsRoot);
if (coverOptions && coverOptions.enabled) {
// Setup coverage pre-test, including post-test hook to report
const coverageRunner = new CoverageRunner(coverOptions, testsRoot);
coverageRunner.setupCoverage();
}
// Glob test files
glob('**/**.test.js', { cwd: testsRoot }, (error, files): any => {
if (error) {
return clb(error);
}
try {
// Fill into Mocha
files.forEach((f): Mocha => mocha.addFile(paths.join(testsRoot, f)));
// Run the tests
let failureCount = 0;
mocha.run()
.on('fail', () => failureCount++)
.on('end', () => clb(undefined, failureCount),
);
} catch (error) {
return clb(error);
}
});
}
exports.run = run;
interface ITestRunnerOptions {
enabled?: boolean;
relativeCoverageDir: string;
relativeSourcePath: string;
ignorePatterns: string[];
includePid?: boolean;
reports?: string[];
verbose?: boolean;
}
class CoverageRunner {
private coverageVar: string = '$$cov_' + new Date().getTime() + '$$';
private transformer: any = undefined;
private matchFn: any = undefined;
private instrumenter: any = undefined;
constructor(private options: ITestRunnerOptions, private testsRoot: string) {
if (!options.relativeSourcePath) {
return;
}
}
public setupCoverage(): void {
// Set up Code Coverage, hooking require so that instrumented code is returned
const self = this;
self.instrumenter = new istanbul.Instrumenter({ coverageVariable: self.coverageVar });
const sourceRoot = paths.join(self.testsRoot, self.options.relativeSourcePath);
// Glob source files
const srcFiles = glob.sync('**/**.js', {
cwd: sourceRoot,
ignore: self.options.ignorePatterns,
});
// Create a match function - taken from the run-with-cover.js in istanbul.
const decache = require('decache');
const fileMap: any = {};
srcFiles.forEach((file) => {
const fullPath = paths.join(sourceRoot, file);
fileMap[fullPath] = true;
// On Windows, extension is loaded pre-test hooks and this mean we lose
// our chance to hook the Require call. In order to instrument the code
// we have to decache the JS file so on next load it gets instrumented.
// This doesn't impact tests, but is a concern if we had some integration
// tests that relied on VSCode accessing our module since there could be
// some shared global state that we lose.
decache(fullPath);
});
self.matchFn = (file: string): boolean => fileMap[file];
self.matchFn.files = Object.keys(fileMap);
// Hook up to the Require function so that when this is called, if any of our source files
// are required, the instrumented version is pulled in instead. These instrumented versions
// write to a global coverage variable with hit counts whenever they are accessed
self.transformer = self.instrumenter.instrumentSync.bind(self.instrumenter);
const hookOpts = { verbose: false, extensions: ['.js'] };
istanbul.hook.hookRequire(self.matchFn, self.transformer, hookOpts);
// initialize the global variable to stop mocha from complaining about leaks
global[self.coverageVar] = {};
// Hook the process exit event to handle reporting
// Only report coverage if the process is exiting successfully
process.on('exit', (code: number) => {
self.reportCoverage();
process.exitCode = code;
});
}
/**
* Writes a coverage report.
* Note that as this is called in the process exit callback, all calls must be synchronous.
*
* @returns {void}
*
* @memberOf CoverageRunner
*/
public reportCoverage(): void {
const self = this;
istanbul.hook.unhookRequire();
let cov: any;
if (typeof global[self.coverageVar] === 'undefined' || Object.keys(global[self.coverageVar]).length === 0) {
console.error('No coverage information was collected, exit without writing coverage information');
return;
} else {
cov = global[self.coverageVar];
}
// TODO consider putting this under a conditional flag
// Files that are not touched by code ran by the test runner is manually instrumented, to
// illustrate the missing coverage.
self.matchFn.files.forEach((file: any) => {
if (cov[file]) {
return;
}
self.transformer(fs.readFileSync(file, 'utf-8'), file);
// When instrumenting the code, istanbul will give each FunctionDeclaration a value of 1 in coverState.s,
// presumably to compensate for function hoisting. We need to reset this, as the function was not hoisted,
// as it was never loaded.
Object.keys(self.instrumenter.coverState.s).forEach((key) => {
self.instrumenter.coverState.s[key] = 0;
});
cov[file] = self.instrumenter.coverState;
});
// TODO Allow config of reporting directory with
const reportingDir = paths.join(self.testsRoot, self.options.relativeCoverageDir);
const includePid = self.options.includePid;
const pidExt = includePid ? ('-' + process.pid) : '';
const coverageFile = paths.resolve(reportingDir, 'coverage' + pidExt + '.json');
// yes, do this again since some test runners could clean the dir initially created
_mkDirIfExists(reportingDir);
fs.writeFileSync(coverageFile, JSON.stringify(cov), 'utf8');
const remappedCollector = remapIstanbul.remap(cov, {
warn: (warning: any) => {
// We expect some warnings as any JS file without a typescript mapping will cause this.
// By default, we'll skip printing these to the console as it clutters it up
if (self.options.verbose) {
console.warn(warning);
}
},
});
const reporter = new istanbul.Reporter(undefined, reportingDir);
const reportTypes = (self.options.reports instanceof Array) ? self.options.reports : ['lcov'];
reporter.addAll(reportTypes);
reporter.write(remappedCollector, true, () => {
console.log(`reports written to ${reportingDir}`);
});
}
}

Просмотреть файл

@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { AzureAccount } from '../../src/azure-account.api';
import * as uuid from 'uuid';
import * as vscode from 'vscode';
export namespace AzureAccountHelper {
export const mockExtension: vscode.Extension<AzureAccount> = {
activate: mockActivate,
exports: {} as AzureAccount,
extensionPath: uuid.v4(),
id: uuid.v4(),
isActive: true,
packageJSON: uuid.v4(),
};
}
async function waitAmoment() {
await new Promise<void>((resolve) => {
setTimeout(() => {
resolve();
}, 1);
});
}
async function mockActivate() {
await waitAmoment();
return {} as AzureAccount;
}

Просмотреть файл

@ -0,0 +1,229 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as assert from 'assert';
import * as fs from 'fs';
import * as sinon from 'sinon';
import { InputBoxOptions, QuickPickItem, QuickPickOptions, Uri, window, workspace } from 'vscode';
import { Constants } from '../src/Constants';
import * as userInteraction from '../src/helpers/userInteraction';
import { CancellationEvent } from '../src/Models';
interface ITestItems extends QuickPickItem {
id: number;
label: string;
description: string;
testProperty: string;
}
describe('User interaction test', () => {
let windowMock: sinon.SinonMock;
beforeEach(() => {
windowMock = sinon.mock(window);
});
afterEach(() => {
windowMock.restore();
windowMock.verify();
});
it('showInputBox should return a value', async () => {
const option: InputBoxOptions = {};
windowMock.expects('showInputBox').withArgs(option).returns('test');
const result = await userInteraction.showInputBox(option);
assert.strictEqual(result, 'test');
});
it('showInputBox should throw cancellation event', async () => {
const option: InputBoxOptions = {};
windowMock.expects('showInputBox').withArgs(option).returns(undefined);
try {
await userInteraction.showInputBox(option);
assert.fail();
} catch (error) {
assert.strictEqual(true, error instanceof CancellationEvent);
}
});
it('showQuickPick should return a value', async () => {
const option: QuickPickOptions = {};
const items: QuickPickItem[] = [
{
description: 'test 1',
label: 'test 1',
},
{
description: 'test 2',
label: 'test 2',
},
];
windowMock.expects('showQuickPick').withArgs(items, option).returns(items[1]);
const result = await userInteraction.showQuickPick(items, option);
assert.deepStrictEqual(result, items[1]);
});
it('showQuickPick with custom items should return a value', async () => {
const option: QuickPickOptions = {};
const items: ITestItems[] = [
{
description: 'test 1',
id: 1,
label: 'test 1',
testProperty: 'test 1',
},
{
description: 'test 2',
id: 2,
label: 'test 2',
testProperty: 'test 2',
},
];
windowMock.expects('showQuickPick').withArgs(items, option).returns(items[1]);
const result = await userInteraction.showQuickPick(items, option);
assert.deepStrictEqual(result, items[1]);
});
it('showQuickPick should throw cancellation event', async () => {
const option: QuickPickOptions = {};
const items: QuickPickItem[] = [
{
description: 'test 1',
label: 'test 1',
},
{
description: 'test 2',
label: 'test 2',
},
];
windowMock.expects('showQuickPick').withArgs(items, option).returns(undefined);
try {
await userInteraction.showQuickPick(items, option);
assert.fail();
} catch (error) {
assert.strictEqual(true, error instanceof CancellationEvent);
}
});
it('showConfirmPaidOperationDialog should throw cancellation event if answer not yes', async () => {
const answer = 'test';
windowMock.expects('showInputBox').returns(answer);
try {
await userInteraction.showConfirmPaidOperationDialog();
assert.fail();
} catch (error) {
assert.strictEqual(true, error instanceof CancellationEvent);
} finally {
windowMock.restore();
windowMock.verify();
}
});
it('showConfirmPaidOperationDialog should throw cancellation event if answer undefined', async () => {
windowMock.expects('showInputBox').returns(undefined);
try {
await userInteraction.showConfirmPaidOperationDialog();
assert.fail();
} catch (error) {
assert.strictEqual(true, error instanceof CancellationEvent);
}
});
it('showConfirmPaidOperationDialog should not throw cancellation event if answer yes', async () => {
const answer = Constants.confirmationDialogResult.yes;
windowMock.expects('showInputBox').returns(answer);
try {
await userInteraction.showConfirmPaidOperationDialog();
} catch (error) {
assert.fail();
}
});
it('showOpenFolderDialog should return a folder path', async () => {
const folderPath = 'test/test';
const uris: Uri[] = [{ fsPath: folderPath} as Uri];
windowMock.expects('showOpenDialog').returns(uris);
const result = await userInteraction.showOpenFolderDialog();
assert.deepStrictEqual(result, folderPath);
});
it('showOpenFolderDialog should return path of first folder', async () => {
const folderPath1 = 'test/test';
const folderPath2 = 'test2/test2';
const uris: Uri[] = [{ fsPath: folderPath1}, { fsPath: folderPath2}] as Uri[];
windowMock.expects('showOpenDialog').returns(uris);
const result = await userInteraction.showOpenFolderDialog();
assert.strictEqual(result, folderPath1);
});
it('showOpenFolderDialog should throw cancellation event if dialog canceled', async () => {
windowMock.expects('showOpenDialog').returns(undefined);
try {
await userInteraction.showOpenFolderDialog();
assert.fail();
} catch (error) {
assert.strictEqual(true, error instanceof CancellationEvent);
}
});
it('saveTextInFile should return file path', async () => {
const fsMock = sinon.mock(fs);
const workspaceMock = sinon.mock(workspace);
const filePathe = 'filePath';
const text = 'test text';
workspaceMock.expects('openTextDocument');
windowMock.expects('showTextDocument');
windowMock.expects('showSaveDialog').returns({ fsPath: filePathe} as Uri);
fsMock.expects('writeFileSync');
const result = await userInteraction.saveTextInFile(text, filePathe);
assert.strictEqual(result, filePathe);
fsMock.restore();
fsMock.verify();
workspaceMock.restore();
workspaceMock.verify();
});
it('saveTextInFile should throw cancellation event if dialog canceled', async () => {
const workspaceMock = sinon.mock(workspace);
const filePathe = 'filePath';
const text = 'test text';
workspaceMock.expects('openTextDocument');
windowMock.expects('showTextDocument');
windowMock.expects('showSaveDialog').returns(undefined);
try {
await userInteraction.saveTextInFile(text, filePathe);
assert.fail();
} catch (error) {
assert.strictEqual(true, error instanceof CancellationEvent);
} finally {
workspaceMock.restore();
workspaceMock.verify();
}
});
});

Просмотреть файл

@ -0,0 +1,47 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as assert from 'assert';
import uuid = require('uuid');
import { Constants } from '../../src/Constants';
import { DialogResultValidator } from '../../src/validators/DialogResultValidator';
import { TestConstants } from '../TestConstants';
describe('DialogResultValidator', () => {
describe('Unit test', () => {
describe('validateConfirmationResult', () => {
it('should fail when result is empty', () => {
// Arrange
const testString = '';
// Act
const result = DialogResultValidator.validateConfirmationResult(testString) as string;
// Assert
assert.strictEqual(result.includes(Constants.validationMessages.valueCannotBeEmpty), true);
assert.strictEqual(result.includes(Constants.validationMessages.invalidConfirmationResult), true);
});
it('should fail when result different from yes or no', () => {
// Arrange
const testString = uuid.v4();
// Act
const result = DialogResultValidator.validateConfirmationResult(testString) as string;
// Assert
assert.strictEqual(result.includes(Constants.validationMessages.invalidConfirmationResult), true);
});
TestConstants.testDialogAnswers.forEach((answer) => {
it(`should pass when answer is ${answer}`, () => {
// Act
const result = DialogResultValidator.validateConfirmationResult(answer);
// Assert
assert.strictEqual(result, null);
});
});
});
});
});

Просмотреть файл

@ -0,0 +1,222 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as assert from 'assert';
import uuid = require('uuid');
import { Constants } from '../../src/Constants';
import { Validator } from '../../src/validators/validator';
import { TestConstants } from '../TestConstants';
describe('Validator', () => {
describe('Unit test', () => {
describe('check on specific chars and length', () => {
const validationCharsTests = [
{
errorMessage: Constants.validationMessages.noLowerCaseLetter,
maxLength: 20,
message: 'has lower case',
minLength: 5,
testString: 'STR!NG123',
},
{
errorMessage: Constants.validationMessages.noUpperCaseLetter,
maxLength: 20,
message: 'has upper case',
minLength: 5,
testString: 'str!ng123',
},
{
errorMessage: Constants.validationMessages.noDigits,
maxLength: 20,
message: 'has digit',
minLength: 5,
testString: 'Str!ng',
},
{
errorMessage: Constants.validationMessages.noSpecialChars,
maxLength: 20,
message: 'has special chars',
minLength: 5,
testString: 'String123',
},
{
errorMessage: Constants.validationMessages.unallowedChars,
maxLength: 20,
message: 'has not unallowed chars',
minLength: 5,
testString: 'Str!ng#/123',
},
{
errorMessage: Constants.validationMessages.lengthRange(12, 72),
maxLength: 72,
message: 'not be short',
minLength: 12,
testString: 'Str!ng123',
},
{
errorMessage: Constants.validationMessages.lengthRange(3, 8),
maxLength: 8,
message: 'not be long',
minLength: 3,
testString: 'Str!ng123',
},
{
errorMessage: null,
maxLength: 20,
message: 'pass when all conditions are met',
minLength: 5,
testString: 'Str!ng123',
},
];
validationCharsTests.forEach((element: any) => {
it(`should ${element.message}`, () => {
// Act
const result = new Validator(element.testString)
.hasLowerCase()
.hasUpperCase()
.hasDigit()
.hasSpecialChar(Constants.validationRegexps.specialChars)
.hasNotUnallowedChar(Constants.validationRegexps.unallowedChars)
.inLengthRange(element.minLength, element.maxLength);
// Assert
assert.strictEqual(result.getErrors(), element.errorMessage);
});
});
});
describe('getErrors', () => {
it('should return correct list of errors', () => {
// Arrange
const testString = 'string';
// Act
const result = new Validator(testString)
.hasDigit()
.hasUpperCase()
.hasSpecialChar(Constants.validationRegexps.specialChars)
.getErrors() as string;
// Assert
assert.strictEqual(result.includes(Constants.validationMessages.noDigits), true);
assert.strictEqual(result.includes(Constants.validationMessages.noUpperCaseLetter), true);
assert.strictEqual(result.includes(Constants.validationMessages.noSpecialChars), true);
});
});
describe('isConfirmationValue', () => {
it('should fail when string is not "yes" or "no"', () => {
// Arrange
const testString = uuid.v4();
// Act
const result = new Validator(testString).isConfirmationValue();
// Assert
assert.strictEqual(result.getErrors(), Constants.validationMessages.invalidConfirmationResult);
});
TestConstants.testDialogAnswers.forEach((answer) => {
it(`should pass when answer is ${answer}`, () => {
// Act
const result = new Validator(answer).isConfirmationValue();
// Assert
assert.strictEqual(result.getErrors(), null);
});
});
});
describe('isLowerCase', () => {
it('should fail when there is upper case letter', () => {
// Arrange
const testString = 'Str!ng123';
// Act
const result = new Validator(testString).isLowerCase();
// Assert
assert.strictEqual(result.getErrors(), Constants.validationMessages.onlyLowerCaseAllowed);
});
it('should pass when there are not upper case letters', () => {
// Arrange
const testString = 'str!ng123';
// Act
const result = new Validator(testString).isLowerCase();
// Assert
assert.strictEqual(result.getErrors(), null);
});
});
describe('isNotEmpty', () => {
it('should fail when string is empty', () => {
// Arrange
const testString = '';
// Act
const result = new Validator(testString).isNotEmpty();
// Assert
assert.strictEqual(result.getErrors(), Constants.validationMessages.valueCannotBeEmpty);
});
it('should fail when string is white space', () => {
// Arrange
const testString = ' ';
// Act
const result = new Validator(testString).isNotEmpty();
// Assert
assert.strictEqual(result.getErrors(), Constants.validationMessages.valueCannotBeEmpty);
});
it('should pass when string is not empty', () => {
// Arrange
const testString = 'str!ng123';
// Act
const result = new Validator(testString).isNotEmpty();
// Assert
assert.strictEqual(result.getErrors(), null);
});
});
describe('isUrl', () => {
it('should fail when string is not url', () => {
// Arrange
const testString = uuid.v4();
// Act
const result = new Validator(testString).isUrl();
// Assert
assert.strictEqual(result.getErrors(), Constants.validationMessages.invalidHostAddress);
});
const validUrls = [
'http://0.0.0.0',
'https://0.0.0.0',
'http://0.0.0.0:0',
'https://0.0.0.0:0',
'0.0.0.0',
'0.0.0.0:0',
];
validUrls.forEach((element) => {
it(`should pass when string is correct url - "${element}"`, () => {
// Act
const result = new Validator(element).isUrl();
// Assert
assert.strictEqual(result.getErrors(), null);
});
});
});
});
});

78
test/workspace.test.ts Normal file
Просмотреть файл

@ -0,0 +1,78 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as assert from 'assert';
import * as sinon from 'sinon';
import * as vscode from 'vscode';
import { Constants } from '../src/Constants';
import * as worspaceHelper from '../src/helpers/workspace';
describe('Workspace', () => {
describe('Unit test', () => {
const testworkspaceFolder: any[] = [
{
uri: {
fsPath: 'testPath1',
},
},
{
uri: {
fsPath: 'testPath2',
},
},
];
let workspaceMock: sinon.SinonStub<any[], any>;
beforeEach(() => {
workspaceMock = sinon.stub(vscode.workspace, 'workspaceFolders');
});
afterEach(() => {
workspaceMock.restore();
});
it('getWorkspaceRoot should throw exception when no workspace opened', () => {
// Arrange
workspaceMock.value(undefined);
// Act and assert
assert.throws(
() => worspaceHelper.getWorkspaceRoot(),
Error,
Constants.validationMessages.undefinedVariable('Workspace root'));
});
it('getWorkspaceRoot should return workspace root path', async () => {
// Arrange
workspaceMock.value(testworkspaceFolder);
// Act
const result = worspaceHelper.getWorkspaceRoot();
// Assert
assert.strictEqual(result, testworkspaceFolder[0].uri.fsPath);
});
it('isWorkspaceOpen should return false when no workspace opened', () => {
// Arrange
workspaceMock.value(undefined);
// Act
const result = worspaceHelper.isWorkspaceOpen();
// Assert
assert.strictEqual(result, false);
});
it('isWorkspaceOpen should return true when workspace opened', () => {
// Arrange
workspaceMock.value(testworkspaceFolder);
// Act
const result = worspaceHelper.isWorkspaceOpen();
// Assert
assert.strictEqual(result, true);
});
});
});

Просмотреть файл

@ -8,7 +8,8 @@
"dom"
],
"sourceMap": true,
"rootDir": "src",
"resolveJsonModule": true,
"rootDir": ".",
"strict": true,
/* enable all strict type-checking options */
/* Additional Checks */
@ -21,8 +22,12 @@
"strictNullChecks": true,
"noUnusedParameters": true,
"allowJs": true,
"typeRoots": [
"./node_modules/@types",
"./node_modules/@machinomy"
],
},
"include": ["src/**/*.js", "src/**/*.ts"],
"include": ["src/**/*.js", "src/**/*.ts", "test/**/*.js", "test/**/*.ts", "test/**/*.json"],
"exclude": [
"node_modules",
".vscode-test"

Просмотреть файл

@ -37,7 +37,8 @@
"avoid-template"
],
"exclude": [
"node_modules/**"
"node_modules/**",
"src/**/*.d.ts"
]
},
"defaultSeverity": "warning"

Просмотреть файл

@ -4,7 +4,7 @@ const config = {
target: 'node',
entry: './src/extension.ts',
output: {
path: path.join(__dirname, 'out'),
path: path.join(__dirname, 'out', 'src'),
filename: 'extension.js',
libraryTarget: 'commonjs2'
},