Skip to content

Commit

Permalink
Enable long running tests and use new federated credentials for testi…
Browse files Browse the repository at this point in the history
…ng (#864)

* WIP: Get token credential from SP

* WIP: See is test provider can sign in

* WIP: Remove webConfig, dont import dev into production code

* WIP: Remove web entry point from package.json

* Add await to the logIn

* Add listing tests

* Fix up resource listing test

* Update service connection

* Hardcode long running tests

* Add some console.debug

* Make token static

* Test code

* Add default

* Add resource provider to global test

* Use the resource provider instead of trying to calling it directly

* Try using the key vault values

* Use fetch and see response type

* Dont parse response2

* wip

* Add node-fetch as a resource

* Make token code use function

* Use service client from @azure/core-client

* Final test using service client

* Remove unused context

* Confirm access token is being made

* Leverage auth package

* Add create and delete tests for Resource Groups

* Comments

* Import settingUtils from bundle

* Import LocationListStep from bundle?

* Reorganize and remove some inports from bundle

* Add hasPortalUrl

* See if there are any build errors

* use auth refactor

* update package json

* use newly released auth package

* use parameter

* remove installing azure account

* remove pretest step

* remove installing azure account fr fr

* remove from recommendations

* reintroduce gulp

* nuclear warfare

* add webpack to pretest

* logs

* try logging error

* use the new env vars with the underscore

* don't swallow errors

* cleanup

* only set to true when doing azure federated credentials

* use new branch for quick testing

* use main again

* refactor subscription provider logic

* remove commented out code

* set in the helper

* make optional

---------

Co-authored-by: Nathan Turinski <[email protected]>
  • Loading branch information
hossam-nasr and nturinski authored May 24, 2024
1 parent f05be68 commit e056095
Show file tree
Hide file tree
Showing 14 changed files with 957 additions and 325 deletions.
2 changes: 2 additions & 0 deletions .azure-pipelines/1esmain.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ resources:
# Use those templates
extends:
template: azure-pipelines/1esmain.yml@azExtTemplates
parameters:
useAzureFederatedCredentials: true
1 change: 0 additions & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"ms-vscode.azure-account"
]
}
17 changes: 10 additions & 7 deletions extension.bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
//
// The tests should import '../extension.bundle'. 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 { LocationListStep } from '@microsoft/vscode-azext-azureutils';
export * from '@microsoft/vscode-azext-utils';
export * from './api/src/AzExtResourceType';
// export * from './api/src';
Expand All @@ -23,25 +24,27 @@ export * from './api/src/resources/base';
export * from './api/src/resources/workspace';
export * from './api/src/utils/getApi';
export * from './api/src/utils/wrapper';
export * from './src/api/DefaultAzureResourceProvider';
export { convertV1TreeItemId } from './src/api/compatibility/CompatibleAzExtTreeDataProvider';
export * from './src/commands/openInPortal';
export { hasPortalUrl } from './src/commands/openInPortal';
export * from './src/commands/tags/getTagDiagnostics';
export * from './src/commands/viewProperties';
export * from './src/services/AzureResourcesService';
// Export activate/deactivate for main.js
export { createResourceGroup } from './src/commands/createResourceGroup';
export * from './src/commands/deleteResourceGroup/v2/deleteResourceGroupV2';
export { activate, deactivate } from './src/extension';
export * from './src/extensionVariables';
export * from './src/hostapi.v2.internal';
export * from './src/tree/BranchDataItemWrapper';
export * from './src/tree/InvalidItem';
export { ResourceGroupsItem } from './src/tree/ResourceGroupsItem';
export * from './src/tree/azure/AzureResourceItem';
export * from './src/tree/azure/SubscriptionItem';
export { createSubscriptionContext as createSubscriptionContext2 } from './src/tree/azure/VSCodeAuthentication';
export * from './src/tree/azure/grouping/GroupingItem';
export * from './src/tree/azure/grouping/LocationGroupingItem';
export * from './src/tree/azure/grouping/ResourceGroupGroupingItem';
export * from './src/tree/azure/grouping/ResourceTypeGroupingItem';
export * from './src/tree/azure/SubscriptionItem';
export * from './src/tree/BranchDataItemWrapper';
export * from './src/tree/InvalidItem';
export { ResourceGroupsItem } from './src/tree/ResourceGroupsItem';
export * from './src/utils/azureClients';
export * from './src/utils/settingUtils';
export * from './src/utils/wrapFunctionsInTelemetry';
// NOTE: The auto-fix action "source.organizeImports" does weird things with this file, but there doesn't seem to be a way to disable it on a per-file basis so we'll just let it happen
3 changes: 1 addition & 2 deletions gulpfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { gulp_installAzureAccount, gulp_webpack } from '@microsoft/vscode-azext-dev';
import { gulp_webpack } from '@microsoft/vscode-azext-dev';
import * as fs from 'fs/promises';
import * as gulp from 'gulp';
import * as path from 'path';
Expand Down Expand Up @@ -50,6 +50,5 @@ async function cleanReadme(): Promise<void> {

exports['webpack-dev'] = gulp.series(prepareForWebpack, () => gulp_webpack('development'));
exports['webpack-prod'] = gulp.series(prepareForWebpack, () => gulp_webpack('production'));
exports.preTest = gulp_installAzureAccount;
exports.listIcons = listIcons;
exports.cleanReadme = cleanReadme;
1,016 changes: 731 additions & 285 deletions package-lock.json

Large diffs are not rendered by default.

11 changes: 7 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,8 @@
"%azureResourceGroups.deleteConfirmation.EnterName%",
"%azureResourceGroups.deleteConfirmation.ClickButton%"
],
"default": "EnterName"
"default": "EnterName",
"scope": "machine"
},
"azureResourceGroups.showHiddenTypes": {
"type": "boolean",
Expand All @@ -535,7 +536,8 @@
"azureResourceGroups.groupBy": {
"type": "string",
"description": "%azureResourceGroups.groupBy%",
"default": "resourceType"
"default": "resourceType",
"scope": "machine"
},
"azureResourceGroups.suppressActivityNotifications": {
"type": "boolean",
Expand Down Expand Up @@ -625,8 +627,8 @@
"lint": "eslint --ext .ts .",
"lint-fix": "eslint --ext .ts . --fix",
"listIcons": "gulp listIcons",
"pretest": "gulp preTest",
"test": "node ./out/test/runTest.js",
"pretest": "webpack",
"webpack": "tsc && gulp webpack-dev",
"webpack-prod": "npm run build && gulp webpack-prod",
"webpack-profile": "webpack --profile --json --mode production > webpack-stats.json && echo Use http://webpack.github.io/analyse to analyze the stats",
Expand All @@ -635,6 +637,7 @@
},
"devDependencies": {
"@azure/arm-resources-subscriptions": "^2.1.0",
"@azure/identity": "^4.1.0",
"@microsoft/api-extractor": "^7.33.8",
"@microsoft/eslint-config-azuretools": "^0.2.1",
"@microsoft/vscode-azext-dev": "^2.0.0",
Expand Down Expand Up @@ -668,7 +671,7 @@
"@azure/arm-resources": "^5.2.0",
"@azure/arm-resources-profile-2020-09-01-hybrid": "^2.1.0",
"@azure/ms-rest-js": "^2.7.0",
"@microsoft/vscode-azext-azureauth": "^2.3.0",
"@microsoft/vscode-azext-azureauth": "^2.4.1",
"@microsoft/vscode-azext-azureutils": "^2.0.0",
"@microsoft/vscode-azext-utils": "^2.4.0",
"buffer": "^6.0.3",
Expand Down
5 changes: 3 additions & 2 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { TagFileSystem } from './commands/tags/TagFileSystem';
import { registerTagDiagnostics } from './commands/tags/registerTagDiagnostics';
import { ext } from './extensionVariables';
import { AzureResourcesApiInternal } from './hostapi.v2.internal';
import { createVSCodeAzureSubscriptionProviderFactory } from './services/VSCodeAzureSubscriptionProvider';
import { getSubscriptionProviderFactory } from './services/getSubscriptionProviderFactory';
import { BranchDataItemCache } from './tree/BranchDataItemCache';
import { HelpTreeItem } from './tree/HelpTreeItem';
import { ResourceGroupsItem } from './tree/ResourceGroupsItem';
Expand Down Expand Up @@ -69,7 +69,8 @@ export async function activate(context: vscode.ExtensionContext, perfStats: { lo
activateContext.telemetry.properties.isActivationEvent = 'true';
activateContext.telemetry.measurements.mainFileLoad = (perfStats.loadEndTime - perfStats.loadStartTime) / 1000;

ext.subscriptionProviderFactory = createVSCodeAzureSubscriptionProviderFactory();

ext.subscriptionProviderFactory = getSubscriptionProviderFactory(activateContext);

ext.tagFS = new TagFileSystem(ext.appResourceTree);
context.subscriptions.push(vscode.workspace.registerFileSystemProvider(TagFileSystem.scheme, ext.tagFS));
Expand Down
6 changes: 1 addition & 5 deletions src/extensionVariables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { AzureSubscriptionProvider } from "@microsoft/vscode-azext-azureauth";
import { AzExtTreeDataProvider, IAzExtLogOutputChannel, IExperimentationServiceAdapter } from "@microsoft/vscode-azext-utils";
import { AzExtResourceType } from "api/src/AzExtResourceType";
import { DiagnosticCollection, Disposable, ExtensionContext, TreeView, UIKind, env } from "vscode";
import { DiagnosticCollection, Disposable, ExtensionContext, TreeView } from "vscode";
import { ActivityLogTreeItem } from "./activityLog/ActivityLogsTreeItem";
import { TagFileSystem } from "./commands/tags/TagFileSystem";
import { AzureResourcesApiInternal } from "./hostapi.v2.internal";
Expand Down Expand Up @@ -47,10 +47,6 @@ export namespace ext {

export let subscriptionProviderFactory: () => Promise<AzureSubscriptionProvider>;

// When debugging thru VS Code as a web environment, the UIKind is Desktop. However, if you sideload it into the browser, you must
// change this to UIKind.Web and then webpack it again
export const isWeb: boolean = env.uiKind === UIKind.Web;

export namespace v2 {
export let api: AzureResourcesApiInternal;
}
Expand Down
42 changes: 42 additions & 0 deletions src/services/getSubscriptionProviderFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { AzureDevOpsSubscriptionProviderInitializer, AzureSubscriptionProvider, createAzureDevOpsSubscriptionProviderFactory } from "@microsoft/vscode-azext-azureauth";
import { IActionContext } from "@microsoft/vscode-azext-utils";
import { createVSCodeAzureSubscriptionProviderFactory } from "./VSCodeAzureSubscriptionProvider";

/**
* Returns a factory function that creates a subscription provider, satisfying the `AzureSubscriptionProvider` interface.
*
* If the `useAzureSubscriptionProvider` is set to `true`, an `AzureDevOpsSubscriptionProviderFactory` is returned.
* Otherwise, a `VSCodeSubscriptionProviderFactory` is returned.
*
*/
export function getSubscriptionProviderFactory(activateContext?: IActionContext): () => Promise<AzureSubscriptionProvider> {
// if this for a nightly test, we want to use the test subscription provider
const useAzureFederatedCredentials: boolean = !/^(false|0)?$/i.test(process.env['AzCode_UseAzureFederatedCredentials'] || '')
if (useAzureFederatedCredentials) {
// when running tests, ensure we throw the errors and they aren't silently swallowed
if (activateContext) {
activateContext.errorHandling.rethrow = useAzureFederatedCredentials;
}

const serviceConnectionId: string | undefined = process.env['AzCode_ServiceConnectionID'];
const domain: string | undefined = process.env['AzCode_ServiceConnectionDomain'];
const clientId: string | undefined = process.env['AzCode_ServiceConnectionClientID'];

if (!serviceConnectionId || !domain || !clientId) {
throw new Error(`Using Azure DevOps federated credentials, but federated service connection is not configured\n
process.env.AzCodeServiceConnectionID: ${serviceConnectionId ? "✅" : "❌"}\n
process.env.AzCodeServiceConnectionDomain: ${domain ? "✅" : "❌"}\n
process.env.AzCodeServiceConnectionClientID: ${clientId ? "✅" : "❌"}\n
`);
}

const initializer: AzureDevOpsSubscriptionProviderInitializer = {
serviceConnectionId,
domain,
clientId,
}
return createAzureDevOpsSubscriptionProviderFactory(initializer);
} else {
return createVSCodeAzureSubscriptionProviderFactory();
}
}
4 changes: 2 additions & 2 deletions test/api/mockServiceFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,13 @@ class BasicMockResources extends MockResources {

export const createMockSubscriptionWithFunctions = (): BasicMockResources => {
const mockResources = new BasicMockResources();
ext.testing.overrideAzureServiceFactory = createTestAzureResourcesServiceFactory(mockResources);
ext.testing.overrideAzureServiceFactory = createMockAzureResourcesServiceFactory(mockResources);
const mockAzureSubscriptionProvider = new MockAzureSubscriptionProvider(mockResources);
ext.testing.overrideAzureSubscriptionProvider = () => mockAzureSubscriptionProvider;
return mockResources;
}

export function createTestAzureResourcesServiceFactory(mockResources: MockResources): AzureResourcesServiceFactory {
export function createMockAzureResourcesServiceFactory(mockResources: MockResources): AzureResourcesServiceFactory {
return () => {
return {
async listResources(_context, subscription: AzureSubscription): Promise<GenericResource[]> {
Expand Down
19 changes: 16 additions & 3 deletions test/global.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@

import { TestOutputChannel, TestUserInput } from '@microsoft/vscode-azext-dev';
import * as vscode from 'vscode';
import { ext, registerOnActionStartHandler } from '../extension.bundle';
import { ext, registerOnActionStartHandler, settingUtils } from '../extension.bundle';

export let longRunningTestsEnabled: boolean;

export const userSettings: { key: string, value: unknown }[] = [];
// Runs before all tests
suiteSetup(async function (this: Mocha.Context): Promise<void> {
this.timeout(1 * 60 * 1000);
Expand All @@ -22,6 +22,19 @@ suiteSetup(async function (this: Mocha.Context): Promise<void> {
// Use `TestUserInput` by default so we get an error if an unexpected call to `context.ui` occurs, rather than timing out
context.ui = new TestUserInput(vscode);
});
const groupBySetting = settingUtils.getWorkspaceSetting('groupBy')
userSettings.push({ key: 'groupBy', value: groupBySetting });

const deleteConfirmationSetting = settingUtils.getWorkspaceSetting('deleteConfirmation');
userSettings.push({ key: 'deleteConfirmation', value: deleteConfirmationSetting });

longRunningTestsEnabled = !/^(false|0)?$/i.test(process.env['AzCode_UseAzureFederatedCredentials'] || '');
});

longRunningTestsEnabled = !/^(false|0)?$/i.test(process.env.ENABLE_LONG_RUNNING_TESTS || '');
suiteTeardown(async function (this: Mocha.Context): Promise<void> {
for (const setting of userSettings) {
// reset the settings to their original values
console.debug(`Resetting setting '${setting.key}' to '${setting.value}'`);
await settingUtils.updateGlobalSetting(setting.key, setting.value);
}
});
122 changes: 122 additions & 0 deletions test/nightly/crud.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { type Location } from '@azure/arm-resources-subscriptions';
import { createTestActionContext, runWithTestActionContext } from '@microsoft/vscode-azext-dev';
import { AzExtParentTreeItem, IActionContext, LocationListStep, SubscriptionItem, createResourceClient, createResourceGroup, deleteResourceGroupV2, ext, randomUtils, settingUtils } from '../../extension.bundle';
import { longRunningTestsEnabled } from "../global.test";
import assert = require("assert");

let rgName: string;
let locations: Location[];
let testSubscription: SubscriptionItem;

suite('Resource CRUD Operations', function (this: Mocha.Suite): void {
this.timeout(7 * 60 * 1000);

suiteSetup(async function (this: Mocha.Context): Promise<void> {
if (!longRunningTestsEnabled) {
this.skip();
}

ext.testing.overrideAzureServiceFactory = undefined;
ext.testing.overrideAzureSubscriptionProvider = undefined;

const subscriptionTreeItems = await ext.appResourceTree.getChildren() as unknown as SubscriptionItem[];
if (subscriptionTreeItems.length > 0) {
const testContext = await createTestActionContext();
testSubscription = subscriptionTreeItems[0] as SubscriptionItem;
const context = { ...testContext, ...testSubscription.subscription };
locations = await LocationListStep.getLocations(context);
}

rgName = randomUtils.getRandomHexString(12);
});

test('Create Resource Group (single)', async () => {
await runWithTestActionContext('createResourceGroup', async context => {
const testInputs: (string | RegExp)[] = [rgName, locations[0].displayName!];
await context.ui.runWithInputs(testInputs, async () => {
await createResourceGroup(context, testSubscription);
});

assert.ok(await resourceGroupExists(context, rgName));
});
});

test('Create Resource Groups (all locations)', async () => {
await Promise.all(locations.map(async l => {
await runWithTestActionContext('createResourceGroup', async context => {
const testInputs: (string | RegExp)[] = [`${rgName}-${l.name}`, l.displayName!];
await context.ui.runWithInputs(testInputs, async () => {
await createResourceGroup(context, testSubscription);
});

assert.ok(await resourceGroupExists(context, `${rgName}-${l.name}`));
});
}));
});

test('Get Resources', async () => {
const subscriptionTreeItems = await ext.appResourceTree.getChildren();
assert.ok(subscriptionTreeItems.length > 0);
for (const subscription of subscriptionTreeItems) {
const groupTreeItems = await ext.appResourceTree.getChildren(subscription as AzExtParentTreeItem);
await Promise.all(groupTreeItems.map(async g => {
const children = await ext.appResourceTree.getChildren(g as AzExtParentTreeItem);
console.log(children);
}));
}

assert.ok(true);
});

test('Delete Resource (EnterName) - Fails when invalid', async () => {
await settingUtils.updateGlobalSetting('deleteConfirmation', 'EnterName');
await runWithTestActionContext('Delete Resource', async context => {
await context.ui.runWithInputs([rgName, 'rgName'], async () => {
try {
await deleteResourceGroupV2(context);
} catch (_) {
console.debug('Expected error: ', _);
// expected to fail here
}
assert.ok(await resourceGroupExists(context, rgName));
});
});
});

test('Delete Resource (EnterName)', async () => {
await settingUtils.updateGlobalSetting('deleteConfirmation', 'EnterName');
await runWithTestActionContext('Delete Resource', async context => {
await context.ui.runWithInputs([rgName, rgName], async () => {
await deleteResourceGroupV2(context);
});
});

});

test('Delete Resource (ClickButton)', async () => {
await settingUtils.updateGlobalSetting('deleteConfirmation', 'ClickButton');
const deleteArray: string[] = Array(locations.length).fill('Delete');
await runWithTestActionContext('Delete Resource', async context => {
await context.ui.runWithInputs([new RegExp(`${rgName}-`), ...deleteArray], async () => {
await deleteResourceGroupV2(context);
});
});

assert.ok(true);
});
});

async function resourceGroupExists(context: IActionContext, rgName: string): Promise<boolean> {
const client = await createResourceClient([context, testSubscription.subscription]);
try {
await client.resourceGroups.get(rgName);
return true;
} catch (_) {
return false;
}
}
18 changes: 18 additions & 0 deletions test/nightly/global.nightly.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as vscode from 'vscode';
import { longRunningTestsEnabled } from '../global.test';

export const resourceGroupsToDelete: string[] = [];

// Runs before all nightly tests
suiteSetup(async function (this: Mocha.Context): Promise<void> {
if (longRunningTestsEnabled) {
this.timeout(2 * 60 * 1000);
await vscode.commands.executeCommand('azureResourceGroups.logIn');
}
});

Loading

0 comments on commit e056095

Please sign in to comment.