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:
Родитель
16ba356fd9
Коммит
e38272cdee
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
});
|
||||
});
|
|
@ -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}`);
|
||||
}
|
||||
});
|
|
@ -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./);
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -66,7 +66,8 @@
|
|||
false,
|
||||
"single"
|
||||
],
|
||||
"radix": false
|
||||
"radix": false,
|
||||
"no-use-before-declare": false
|
||||
},
|
||||
"linterOptions": {
|
||||
"exclude": [
|
||||
|
|
Загрузка…
Ссылка в новой задаче