Test Infrastructure with few Unit and Integration Tests (#6)

Test Infrastructure with few Unit and Integration Tests

The PR is to setup basic test infrastructure for Unit and Integration tests.
Includes-

Unit Tests:
OperAPI Parser
AzureUtil
NameUtil

Integrations Tests:
Create Service (default)
Note: Need nightly builds to be enabled to validate this test
This commit is contained in:
Annaji Sharma Ganti 2019-06-17 14:38:42 -04:00 коммит произвёл GitHub
Родитель 16ba356fd9
Коммит e38272cdee
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
18 изменённых файлов: 2424 добавлений и 33 удалений

6
.vscode/launch.json поставляемый
Просмотреть файл

@ -65,8 +65,7 @@
"MOCHA_enableTimeouts": "0", // Disable time-outs
"DEBUGTELEMETRY": "1",
"NODE_DEBUG": "",
"ENABLE_LONG_RUNNING_TESTS": "",
"FUNC_PATH": "func"
"ENABLE_LONG_RUNNING_TESTS": ""
}
},
{
@ -89,8 +88,7 @@
"MOCHA_enableTimeouts": "0", // Disable time-outs
"DEBUGTELEMETRY": "1",
"NODE_DEBUG": "",
"ENABLE_LONG_RUNNING_TESTS": "",
"FUNC_PATH": "func"
"ENABLE_LONG_RUNNING_TESTS": ""
}
}
]

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

@ -18,3 +18,13 @@ export { activateInternal, deactivateInternal } from './src/extension';
// The tests should import '../extension.bundle.ts'. At design-time they live in tests/ and so will pick up this file (extension.bundle.ts).
// At runtime the tests live in dist/tests and will therefore pick up the main webpack bundle at dist/extension.bundle.js.
export { ext } from './src/extensionVariables';
export * from './src/constants';
export * from "vscode-azureextensionui";
export * from './src/utils/fsUtil';
export * from './src/explorer/ApiManagementProvider';
export * from './src/vsCodeConfig/settings';
export * from './src/openApi/OpenApiParser';
export * from './src/openApi/OpenApiImportObject';
export { treeUtils } from './src/utils/treeUtils';
export * from './src/utils/azure';
export * from './src/utils/nameUtil';

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

@ -0,0 +1,42 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { File } from 'decompress';
import * as glob from 'glob';
import * as gulp from 'gulp';
// tslint:disable-next-line: no-require-imports
import decompress = require('gulp-decompress');
// tslint:disable-next-line: no-require-imports
import download = require('gulp-download');
import * as os from 'os';
import * as path from 'path';
import { Stream } from 'stream';
// Tests expect the extension to be installed.
// tslint:disable-next-line: promise-function-async
// tslint:disable:no-console
// tslint:disable: no-unsafe-any
export function gulp_installRestClient(): Promise<void> | Stream {
const version: string = '0.21.3';
const extensionPath: string = path.join(os.homedir(), `.vscode/extensions/humao.rest-client-${version}`);
const existingExtensions: string[] = glob.sync(extensionPath.replace(version, '*'));
if (existingExtensions.length === 0) {
console.log("installing rest-client extension.");
// tslint:disable-next-line:no-http-string
return download(`http://humao.gallery.vsassets.io/_apis/public/gallery/publisher/humao/extension/rest-client/${version}/assetbyname/Microsoft.VisualStudio.Services.VSIXPackage`)
.pipe(decompress({
filter: (file: File): boolean => file.path.startsWith('extension/'),
map: (file: File): File => {
file.path = file.path.slice(10);
return file;
}
}))
.pipe(gulp.dest(extensionPath));
} else {
console.log("rest-client extension already installed.");
// We need to signal to gulp that we've completed this async task
return Promise.resolve();
}
}

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

@ -9,6 +9,7 @@ import * as cp from 'child_process';
import * as gulp from 'gulp';
import * as path from 'path';
import { gulp_installAzureAccount, gulp_webpack } from 'vscode-azureextensiondev';
import { gulp_installRestClient } from './gulp/gulp_installRestClient';
const env = process.env;
@ -20,4 +21,4 @@ function test(): cp.ChildProcess {
exports['webpack-dev'] = () => gulp_webpack('development');
exports['webpack-prod'] = () => gulp_webpack('production');
exports.test = gulp.series(gulp_installAzureAccount, test);
exports.test = gulp.series(gulp_installAzureAccount, gulp_installRestClient, test);

2
package-lock.json сгенерированный
Просмотреть файл

@ -1,6 +1,6 @@
{
"name": "vscode-apimanagement",
"version": "0.0.1-alpha",
"version": "0.1.0-alpha",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

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

@ -25,12 +25,15 @@ export async function createTemporaryFile(fileName: string): Promise<string> {
export function getSessionWorkingFolderName() : string {
let sessionFolderName = ext.context.globalState.get(sessionFolderKey);
if (!sessionFolderName) {
const randomFolderNameLength: number = 12;
const buffer: Buffer = crypto.randomBytes(Math.ceil(randomFolderNameLength / 2));
sessionFolderName = buffer.toString('hex').slice(0, randomFolderNameLength);
sessionFolderName = getRandomHexString();
ext.outputChannel.appendLine(`Session working folder:${sessionFolderName}`);
ext.context.globalState.update(sessionFolderKey, sessionFolderName);
}
return <string>sessionFolderName;
}
export function getRandomHexString(length: number = 10): string {
const buffer: Buffer = crypto.randomBytes(Math.ceil(length / 2));
return buffer.toString('hex').slice(0, length);
}

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

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import { AzureParentTreeItem, AzureTreeDataProvider, AzureTreeItem } from 'vscode-azureextensionui';
import { AzureParentTreeItem, AzureTreeDataProvider, AzureTreeItem, RootTreeItem } from 'vscode-azureextensionui';
import { ext } from '../extensionVariables';
import { localize } from '../localize';
@ -37,4 +37,9 @@ export namespace treeUtils {
throw new Error(localize('noMatchingSubscription', 'Failed to find a subscription matching id "{0}".', subscriptionId));
}
}
export async function getRootNode(tree: AzureTreeDataProvider): Promise<AzureParentTreeItem> {
// is there a better way than querying children?
return <AzureParentTreeItem>(await tree.getChildren()).find((n: AzureParentTreeItem) => n instanceof RootTreeItem);
}
}

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

@ -6,6 +6,12 @@
import { ConfigurationTarget, Uri, workspace, WorkspaceConfiguration } from "vscode";
import { extensionPrefix } from "../constants";
export function getGlobalSetting<T>(key: string, prefix: string = extensionPrefix): T | undefined {
const projectConfiguration: WorkspaceConfiguration = workspace.getConfiguration(prefix);
const result: { globalValue?: T } | undefined = projectConfiguration.inspect<T>(key);
return result && result.globalValue;
}
export async function updateGlobalSetting<T = string>(section: string, value: T, prefix: string = extensionPrefix): Promise<void> {
const projectConfiguration: WorkspaceConfiguration = workspace.getConfiguration(prefix);
await projectConfiguration.update(section, value, ConfigurationTarget.Global);
@ -15,3 +21,8 @@ export function getWorkspaceSetting<T>(key: string, fsPath?: string, prefix: str
const projectConfiguration: WorkspaceConfiguration = workspace.getConfiguration(prefix, fsPath ? Uri.file(fsPath) : undefined);
return projectConfiguration.get<T>(key);
}
export async function updateWorkspaceSetting<T = string>(section: string, value: T, fsPath: string, prefix: string = extensionPrefix): Promise<void> {
const projectConfiguration: WorkspaceConfiguration = workspace.getConfiguration(prefix, Uri.file(fsPath));
await projectConfiguration.update(section, value);
}

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

@ -0,0 +1,21 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
/**
* Same as assert.throws except for async functions
*/
export async function assertThrowsAsync<T>(block: () => Promise<T>, error: RegExp | Function, message?: string): Promise<void> {
// tslint:disable-next-line:typedef
let blockSync = (): void => { /* ignore */ };
try {
await block();
} catch (e) {
blockSync = (): void => { throw e; };
} finally {
assert.throws(blockSync, error, message);
}
}

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

@ -0,0 +1,27 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { getNameFromId, getResourceGroupFromId, getSubscriptionFromId } from '../extension.bundle';
// tslint:disable: no-unsafe-any
suite("Azure Resource Util", () => {
const resourceId: string = "/subscriptions/a59d7183-f4a0-4b15-8ecc-9542203d3c54/resourceGroups/apim-rg/providers/Microsoft.ApiManagement/service/apim-service";
test("Parse name from azure resource id", async () => {
const name: string = getNameFromId(resourceId);
assert.equal(name, "apim-service");
});
test("Parse resource group name from azure resource id", async () => {
const rg: string = getResourceGroupFromId(resourceId);
assert.equal(rg, "apim-rg");
});
test("Parse subscription from azure resource id", async () => {
const sub: string = getSubscriptionFromId(resourceId);
assert.equal(sub, "a59d7183-f4a0-4b15-8ecc-9542203d3c54");
});
});

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

@ -0,0 +1,74 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { ApiManagementClient, ApiManagementModels } from 'azure-arm-apimanagement';
import { ResourceManagementClient } from 'azure-arm-resource';
import { IHookCallbackContext, ISuiteCallbackContext } from 'mocha';
import * as vscode from 'vscode';
import { AzureParentTreeItem } from 'vscode-azureextensionui';
import { ApiManagementProvider, AzureTreeDataProvider, ext, getRandomHexString, TestAzureAccount, TestUserInput, treeUtils } from '../extension.bundle';
import { longRunningTestsEnabled } from './global.test';
import { runWithApimSetting } from './runWithSetting';
// tslint:disable-next-line:max-func-body-length
suite('Create Azure Resources', async function (this: ISuiteCallbackContext): Promise<void> {
this.timeout(1200 * 1000);
const resourceGroupsToDelete: string[] = [];
const testAccount: TestAzureAccount = new TestAzureAccount();
let apiManagementClient: ApiManagementClient;
const resourceName1: string = getRandomHexString().toLowerCase();
suiteSetup(async function (this: IHookCallbackContext): Promise<void> {
if (!longRunningTestsEnabled) {
this.skip();
}
this.timeout(120 * 1000);
await testAccount.signIn();
ext.tree = new AzureTreeDataProvider(ApiManagementProvider, 'azureApiManagement.startTesting', undefined, testAccount);
const rootNode : AzureParentTreeItem = await treeUtils.getRootNode(ext.tree);
rootNode.root.userId = "vscodeapimtest@microsoft.com"; // userId doesnt exist for service principal.
apiManagementClient = getApiManagementClient(testAccount);
});
suiteTeardown(async function (this: IHookCallbackContext): Promise<void> {
if (!longRunningTestsEnabled) {
this.skip();
}
this.timeout(1200 * 1000);
const client: ResourceManagementClient = getResourceManagementClient(testAccount);
for (const resourceGroup of resourceGroupsToDelete) {
if (await client.resourceGroups.checkExistence(resourceGroup)) {
console.log(`Deleting resource group "${resourceGroup}"...`);
await client.resourceGroups.deleteMethod(resourceGroup);
console.log(`Resource group "${resourceGroup}" deleted.`);
} else {
// If the test failed, the resource group might not actually exist
console.log(`Ignoring resource group "${resourceGroup}" because it does not exist.`);
}
}
ext.tree.dispose();
});
test('createApiManagementService (Default)', async () => {
resourceGroupsToDelete.push(resourceName1);
await runWithApimSetting('advancedCreation', undefined, async () => {
ext.ui = new TestUserInput([resourceName1]);
await vscode.commands.executeCommand('azureApiManagement.createService');
const createdService: ApiManagementModels.ApiManagementServiceResource = await apiManagementClient.apiManagementService.get(resourceName1, resourceName1);
assert.ok(createdService);
});
});
});
function getApiManagementClient(testAccount: TestAzureAccount): ApiManagementClient {
return new ApiManagementClient(testAccount.getSubscriptionCredentials(), testAccount.getSubscriptionId());
}
function getResourceManagementClient(testAccount: TestAzureAccount): ResourceManagementClient {
return new ResourceManagementClient(testAccount.getSubscriptionCredentials(), testAccount.getSubscriptionId());
}

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

@ -1,22 +0,0 @@
//
// Note: This example test is leveraging the Mocha test framework.
// Please refer to their documentation on https://mochajs.org/ for help.
//
// The module 'assert' provides assertion methods from node
import * as assert from 'assert';
// You can import and use all API from the 'vscode' module
// as well as import your extension to test it
// import * as vscode from 'vscode';
// import * as myExtension from '../extension';
// Defines a Mocha test suite to group tests of similar kind together
suite("Extension Tests", () => {
// Defines a Mocha unit test
test("Something 1", () => {
assert.equal(-1, [1, 2, 3].indexOf(5));
assert.equal(-1, [1, 2, 3].indexOf(0));
});
});

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

@ -0,0 +1,39 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as fse from 'fs-extra';
import { IHookCallbackContext } from 'mocha';
import * as os from 'os';
import * as path from 'path';
import * as vscode from 'vscode';
import { TestOutputChannel } from 'vscode-azureextensiondev';
import { ext, getRandomHexString, parseError, TestUserInput } from '../extension.bundle';
export let longRunningTestsEnabled: boolean;
export const testFolderPath: string = path.join(os.tmpdir(), `azureApiManagementTest${getRandomHexString()}`);
// Runs before all tests
suiteSetup(async function (this: IHookCallbackContext): Promise<void> {
this.timeout(120 * 1000);
await fse.ensureDir(testFolderPath);
await vscode.commands.executeCommand('azureApiManagement.Refresh'); // activate the extension before tests begin
ext.outputChannel = new TestOutputChannel();
ext.ui = new TestUserInput([]);
// tslint:disable-next-line:strict-boolean-expressions
longRunningTestsEnabled = !/^(false|0)?$/i.test(process.env.ENABLE_LONG_RUNNING_TESTS || '');
});
suiteTeardown(async function (this: IHookCallbackContext): Promise<void> {
this.timeout(90 * 1000);
try {
await fse.remove(testFolderPath);
} catch (error) {
// Build machines fail pretty often with an EPERM error on Windows, but removing the temp test folder isn't worth failing the build
console.warn(`Failed to delete test folder path: ${parseError(error).message}`);
}
});

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

@ -0,0 +1,28 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { nameUtil } from '../extension.bundle';
import { IApiTreeRoot } from '../src/explorer/IApiTreeRoot';
import { IOperationTreeRoot } from '../src/explorer/IOperationTreeRoot';
import { IServiceTreeRoot } from '../src/explorer/IServiceTreeRoot';
// tslint:disable: no-unsafe-any
suite("Name Util", () => {
test("ServiceRoot", async () => {
const name : string = nameUtil(<IServiceTreeRoot>{ serviceName : "apim-service" });
assert.equal(name, "apim-service");
});
test("OperationRoot", async () => {
const name : string = nameUtil(<IOperationTreeRoot>{ serviceName : "apim-service", apiName: "apim-api", opName: "apim-op" });
assert.equal(name, "apim-service-apim-api-apim-op");
});
test("ApiRoot", async () => {
const name : string = nameUtil(<IApiTreeRoot>{ serviceName : "apim-service", apiName: "apim-api"});
assert.equal(name, "apim-service-apim-api");
});
});

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

@ -0,0 +1,32 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { IOpenApiImportObject, OpenApiParser } from '../extension.bundle';
import { assertThrowsAsync } from './assertThrowsAsync';
import {openApi2_0, openApi3_0} from './testData';
// tslint:disable: no-unsafe-any
suite("Open API Parser", () => {
test("Parse OpenAPI Json 2.0", async () => {
const parsedOpenAPI: IOpenApiImportObject = await new OpenApiParser().parse(openApi2_0);
assert.deepEqual(parsedOpenAPI.sourceDocument, openApi2_0);
assert.equal(parsedOpenAPI.importFormat, "swagger-json");
assert.equal(parsedOpenAPI.version, "2.0");
});
test("Parse OpenAPI Json > 3.0", async () => {
const parsedOpenAPI: IOpenApiImportObject = await new OpenApiParser().parse(openApi3_0);
assert.deepEqual(parsedOpenAPI.sourceDocument, openApi3_0);
assert.equal(parsedOpenAPI.importFormat, "openapi+json");
assert.equal(parsedOpenAPI.version, "3.0.0");
});
test("Invalid OpenAPI input", async () => {
// tslint:disable-next-line:no-any
const invalidSwagger : any = {};
await assertThrowsAsync(async () => new OpenApiParser().parse(invalidSwagger), /Could not parse the OpenAPI document./);
});
});

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

@ -0,0 +1,24 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { extensionPrefix, getGlobalSetting, updateGlobalSetting } from "../extension.bundle";
export async function runWithApimSetting(key: string, value: string | undefined, callback: () => Promise<void>): Promise<void> {
await runWithSettingInternal(key, value, extensionPrefix, callback);
}
export async function runWithSetting(key: string, value: string | undefined, callback: () => Promise<void>): Promise<void> {
await runWithSettingInternal(key, value, '', callback);
}
async function runWithSettingInternal(key: string, value: string | undefined, prefix: string, callback: () => Promise<void>): Promise<void> {
const oldValue: string | undefined = getGlobalSetting(key, prefix);
try {
await updateGlobalSetting(key, value, prefix);
await callback();
} finally {
await updateGlobalSetting(key, oldValue, prefix);
}
}

2097
test/testData.ts Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -66,7 +66,8 @@
false,
"single"
],
"radix": false
"radix": false,
"no-use-before-declare": false
},
"linterOptions": {
"exclude": [