updated features and tests
This commit is contained in:
Родитель
f7ded2d574
Коммит
6b42c802be
|
@ -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
|
||||
|
|
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -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
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"enabled": true,
|
||||
"relativeSourcePath": "../src",
|
||||
"relativeCoverageDir": "../../coverage",
|
||||
"ignorePatterns": [
|
||||
"**/node_modules/**"
|
||||
],
|
||||
"includePid": false,
|
||||
"reports": [
|
||||
"html",
|
||||
"lcov",
|
||||
"text-summary"
|
||||
],
|
||||
"verbose": false
|
||||
}
|
33
package.json
33
package.json
|
@ -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);
|
||||
});
|
||||
|
|
100
src/Constants.ts
100
src/Constants.ts
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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: {}
|
||||
}
|
||||
};
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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'
|
||||
},
|
||||
|
|
Загрузка…
Ссылка в новой задаче