Add API tests (#563)
* Add API tests * Add more tests * Remove console logs * Fixup * Add findItemById tests * Fixups * Revert changes to main * Fixup * Add back AA dep * Fixup * Revert change * Make Azure resource provider mockable * Remove source-map-support dep
This commit is contained in:
Родитель
2a72310708
Коммит
b4c3927bea
|
@ -15,12 +15,30 @@
|
|||
// 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 * from '@microsoft/vscode-azext-utils';
|
||||
export * from './src/commands/tags/getTagDiagnostics';
|
||||
export * from './src/utils/wrapFunctionsInTelemetry';
|
||||
export * from './api/src/AzExtResourceType';
|
||||
// export * from './api/src';
|
||||
export * from './api/src/extensionApi';
|
||||
export * from './api/src/resources/azure';
|
||||
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 { convertV1TreeItemId } from './src/api/compatibility/CompatibleAzExtTreeDataProvider';
|
||||
export * from './src/api/DefaultAzureResourceProvider';
|
||||
export * from './src/services/AzureResourcesService';
|
||||
export * from './src/commands/openInPortal';
|
||||
export * from './src/commands/tags/getTagDiagnostics';
|
||||
export * from './src/commands/viewProperties';
|
||||
// Export activate/deactivate for main.js
|
||||
export { activate, deactivate } from './src/extension';
|
||||
export * from './src/extensionVariables';
|
||||
export * from './src/services/SubscriptionProvider';
|
||||
export * from './src/tree/azure/AzureResourceItem';
|
||||
export * from './src/tree/azure/GroupingItem';
|
||||
export * from './src/tree/azure/SubscriptionItem';
|
||||
export { createSubscriptionContext as createSubscriptionContext2 } from './src/tree/azure/VSCodeAuthentication';
|
||||
export * from './src/tree/BranchDataItemWrapper';
|
||||
export * from './src/tree/InvalidItem';
|
||||
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
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
"devDependencies": {
|
||||
"@microsoft/api-extractor": "^7.33.8",
|
||||
"@microsoft/eslint-config-azuretools": "^0.1.0",
|
||||
"@microsoft/vscode-azext-dev": "^1.0.2",
|
||||
"@microsoft/vscode-azext-dev": "^1.0.4",
|
||||
"@types/gulp": "^4.0.6",
|
||||
"@types/mocha": "^7.0.2",
|
||||
"@types/node": "^14.0.0",
|
||||
|
@ -856,9 +856,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@microsoft/vscode-azext-dev": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/vscode-azext-dev/-/vscode-azext-dev-1.0.2.tgz",
|
||||
"integrity": "sha512-UdZBxGonOLnZVNs7fd0gXVFIg6mVmgXMgkCnTlaC4QZZK/SI7ODfoh2H3CNQd9wPvsebg0JMxOoQpEhf7Ek1qg==",
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/vscode-azext-dev/-/vscode-azext-dev-1.0.4.tgz",
|
||||
"integrity": "sha512-84adRC2BjJ+na66bK8zwXqZt9AJ8Y2qkPwR2GJ0a2pZMtxfEw2z7lvjF3Qvicy7ZbDr2Gwmnw61gphkrdtmt/A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@azure/arm-subscriptions": "^2.0.0",
|
||||
|
@ -13158,9 +13158,9 @@
|
|||
}
|
||||
},
|
||||
"@microsoft/vscode-azext-dev": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/vscode-azext-dev/-/vscode-azext-dev-1.0.2.tgz",
|
||||
"integrity": "sha512-UdZBxGonOLnZVNs7fd0gXVFIg6mVmgXMgkCnTlaC4QZZK/SI7ODfoh2H3CNQd9wPvsebg0JMxOoQpEhf7Ek1qg==",
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/vscode-azext-dev/-/vscode-azext-dev-1.0.4.tgz",
|
||||
"integrity": "sha512-84adRC2BjJ+na66bK8zwXqZt9AJ8Y2qkPwR2GJ0a2pZMtxfEw2z7lvjF3Qvicy7ZbDr2Gwmnw61gphkrdtmt/A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@azure/arm-subscriptions": "^2.0.0",
|
||||
|
|
|
@ -565,7 +565,7 @@
|
|||
"devDependencies": {
|
||||
"@microsoft/api-extractor": "^7.33.8",
|
||||
"@microsoft/eslint-config-azuretools": "^0.1.0",
|
||||
"@microsoft/vscode-azext-dev": "^1.0.2",
|
||||
"@microsoft/vscode-azext-dev": "^1.0.4",
|
||||
"@types/gulp": "^4.0.6",
|
||||
"@types/mocha": "^7.0.2",
|
||||
"@types/node": "^14.0.0",
|
||||
|
|
|
@ -20,6 +20,6 @@ export async function openInPortal(context: IActionContext, node?: ResourceGroup
|
|||
throw new Error(localize('commands.openInPortal.noPortalLocation', 'The selected resource is not associated with location within the Azure portal.'));
|
||||
}
|
||||
|
||||
function hasPortalUrl(node: ResourceGroupsItem): node is { portalUrl: Uri } {
|
||||
export function hasPortalUrl(node: ResourceGroupsItem): node is { portalUrl: Uri } {
|
||||
return !!node && typeof node === 'object' && (node as { portalUrl: unknown }).portalUrl instanceof Uri;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,6 @@ export async function viewProperties(context: IActionContext, node?: ResourceGro
|
|||
await openReadOnlyJson({ fullId: node.id ?? uuidv4(), label: node.viewProperties.label }, node.viewProperties.data);
|
||||
}
|
||||
|
||||
function hasViewProperties(node: unknown): node is { viewProperties: ViewPropertiesModel } {
|
||||
export function hasViewProperties(node: unknown): node is { viewProperties: ViewPropertiesModel } {
|
||||
return !!(node as { viewProperties: ViewPropertiesModel })?.viewProperties;
|
||||
}
|
||||
|
|
|
@ -5,10 +5,11 @@
|
|||
|
||||
import { AzExtTreeDataProvider, IAzExtOutputChannel } from "@microsoft/vscode-azext-utils";
|
||||
import { AppResourceResolver } from "@microsoft/vscode-azext-utils/hostapi";
|
||||
import { DiagnosticCollection, Disposable, Event, EventEmitter, ExtensionContext, TreeView, UIKind, env } from "vscode";
|
||||
import { DiagnosticCollection, Disposable, env, Event, EventEmitter, ExtensionContext, TreeView, UIKind } from "vscode";
|
||||
import { AzureResourcesApiInternal } from "../hostapi.v2.internal";
|
||||
import { ActivityLogTreeItem } from "./activityLog/ActivityLogsTreeItem";
|
||||
import { TagFileSystem } from "./commands/tags/TagFileSystem";
|
||||
import { AzureResourcesServiceFactory } from "./services/AzureResourcesService";
|
||||
import { AzureSubscriptionProvider } from "./services/SubscriptionProvider";
|
||||
import { ResourceGroupsItem } from "./tree/ResourceGroupsItem";
|
||||
import { TreeItemStateStore } from "./tree/TreeItemState";
|
||||
|
@ -65,5 +66,10 @@ export namespace ext {
|
|||
export let api: AzureResourcesApiInternal;
|
||||
}
|
||||
|
||||
export namespace testing {
|
||||
export let overrideAzureServiceFactory: AzureResourcesServiceFactory | undefined;
|
||||
export let overrideAzureSubscriptionProvider: (() => AzureSubscriptionProvider) | undefined;
|
||||
}
|
||||
|
||||
export const actions = extActions;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import type { GenericResource, ResourceGroup, ResourceManagementClient } from "@
|
|||
import { uiUtils } from "@microsoft/vscode-azext-azureutils";
|
||||
import { createSubscriptionContext, IActionContext } from "@microsoft/vscode-azext-utils";
|
||||
import { AzureSubscription } from "api/src/resources/azure";
|
||||
import { ext } from "../extensionVariables";
|
||||
import { createResourceClient } from "../utils/azureClients";
|
||||
|
||||
export interface AzureResourcesService {
|
||||
|
@ -26,6 +27,8 @@ export const defaultAzureResourcesServiceFactory = (): AzureResourcesService =>
|
|||
}
|
||||
}
|
||||
|
||||
export type AzureResourcesServiceFactory = () => AzureResourcesService;
|
||||
|
||||
export function getAzureResourcesService(): AzureResourcesService {
|
||||
return defaultAzureResourcesServiceFactory();
|
||||
return ext.testing.overrideAzureServiceFactory?.() ?? defaultAzureResourcesServiceFactory();
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import { apiUtils } from '@microsoft/vscode-azext-utils';
|
||||
import { AzureSubscription } from 'api/src/resources/azure';
|
||||
import * as vscode from 'vscode';
|
||||
import { AzureAccountExtensionApi, AzureSubscription as AzureAccountSubscription, AzureLoginStatus } from '../tree/azure-account.api';
|
||||
import { AzureAccountExtensionApi, AzureLoginStatus, AzureSubscription as AzureAccountSubscription } from '../../azure-account.api';
|
||||
import { AzureSubscriptionProvider } from "./SubscriptionProvider";
|
||||
|
||||
async function getAzureAccountExtensionApi(): Promise<AzureAccountExtensionApi> {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import { AzureSubscription } from 'api/src/resources/azure';
|
||||
import * as vscode from 'vscode';
|
||||
import { AzureLoginStatus } from '../tree/azure-account.api';
|
||||
import { AzureLoginStatus } from '../../azure-account.api';
|
||||
|
||||
export interface AzureSubscriptionProvider {
|
||||
logIn(): Promise<void>;
|
||||
|
|
|
@ -6,10 +6,10 @@
|
|||
import * as arm from '@azure/arm-subscriptions';
|
||||
import { Environment } from '@azure/ms-rest-azure-env';
|
||||
import { uiUtils } from '@microsoft/vscode-azext-azureutils';
|
||||
import { IActionContext, callWithTelemetryAndErrorHandling } from '@microsoft/vscode-azext-utils';
|
||||
import { callWithTelemetryAndErrorHandling, IActionContext } from '@microsoft/vscode-azext-utils';
|
||||
import * as vscode from 'vscode';
|
||||
import { AzureSubscription } from '../../api/src/index';
|
||||
import { AzureLoginStatus } from '../tree/azure-account.api';
|
||||
import { AzureLoginStatus } from '../../azure-account.api';
|
||||
import { localize } from '../utils/localize';
|
||||
import { settingUtils } from '../utils/settingUtils';
|
||||
import { AzureSubscriptionProvider } from './SubscriptionProvider';
|
||||
|
|
|
@ -140,15 +140,19 @@ export class AzureResourceTreeDataProvider extends ResourceTreeDataProviderBase
|
|||
}
|
||||
|
||||
private async getAzureAccountExtensionApi(): Promise<AzureSubscriptionProvider | undefined> {
|
||||
// override for testing
|
||||
if (ext.testing.overrideAzureSubscriptionProvider) {
|
||||
return ext.testing.overrideAzureSubscriptionProvider();
|
||||
} else {
|
||||
if (!this.subscriptionProvider) {
|
||||
this.subscriptionProvider = await ext.subscriptionProviderFactory();
|
||||
await this.subscriptionProvider.waitForFilters();
|
||||
}
|
||||
|
||||
if (!this.subscriptionProvider) {
|
||||
this.subscriptionProvider = await ext.subscriptionProviderFactory();
|
||||
await this.subscriptionProvider.waitForFilters();
|
||||
this.filtersSubscription = this.subscriptionProvider.onFiltersChanged(() => this.notifyTreeDataChanged());
|
||||
this.statusSubscription = this.subscriptionProvider.onStatusChanged(() => this.notifyTreeDataChanged());
|
||||
|
||||
return this.subscriptionProvider;
|
||||
}
|
||||
|
||||
this.filtersSubscription = this.subscriptionProvider.onFiltersChanged(() => this.notifyTreeDataChanged());
|
||||
this.statusSubscription = this.subscriptionProvider.onStatusChanged(() => this.notifyTreeDataChanged());
|
||||
|
||||
return this.subscriptionProvider;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ISubscriptionContext } from '@microsoft/vscode-azext-utils';
|
||||
import { Event, EventEmitter } from 'vscode';
|
||||
import { AzureLoginStatus, AzureSession, CloudShell } from '../../azure-account.api';
|
||||
import { AzureSubscription, AzureSubscriptionProvider, createSubscriptionContext2 } from '../../extension.bundle';
|
||||
import { MockResources } from './mockServiceFactory';
|
||||
|
||||
export class MockAzureAccount implements AzureSubscriptionProvider {
|
||||
public status: AzureLoginStatus = 'LoggedIn';
|
||||
public onStatusChanged: Event<AzureLoginStatus>;
|
||||
readonly sessions: AzureSession[];
|
||||
public getsessions: AzureSession[] = [];
|
||||
public onSessionsChanged: Event<void>;
|
||||
public onSubscriptionsChanged: Event<void>;
|
||||
public onFiltersChanged: Event<void>;
|
||||
|
||||
get subscriptions(): AzureSubscription[] {
|
||||
return this.resources.subscriptions.map((subscription) => ({
|
||||
authentication: {
|
||||
getSession: () => {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
environment: undefined,
|
||||
isCustomCloud: false,
|
||||
name: subscription.name,
|
||||
tenantId: 'tenantId',
|
||||
subscriptionId: subscription.subscriptionId,
|
||||
} as unknown as AzureSubscription));
|
||||
}
|
||||
|
||||
get filters(): AzureSubscription[] {
|
||||
return this.resources.subscriptions.map((subscription) => ({
|
||||
authentication: {
|
||||
getSession: () => {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
environment: {
|
||||
portalUrl: 'portalUrl',
|
||||
},
|
||||
isCustomCloud: false,
|
||||
name: subscription.name,
|
||||
tenantId: 'tenantId',
|
||||
subscriptionId: subscription.subscriptionId,
|
||||
} as unknown as AzureSubscription));
|
||||
}
|
||||
|
||||
apiVersion = '1.0.0';
|
||||
|
||||
createCloudShell(_os: 'Linux' | 'Windows'): CloudShell {
|
||||
throw new Error('not implemented');
|
||||
}
|
||||
|
||||
private readonly _onStatusChangedEmitter: EventEmitter<AzureLoginStatus>;
|
||||
private readonly _onFiltersChangedEmitter: EventEmitter<void>;
|
||||
private readonly _onSessionsChangedEmitter: EventEmitter<void>;
|
||||
private readonly _onSubscriptionsChangedEmitter: EventEmitter<void>;
|
||||
|
||||
public constructor(vscode: typeof import('vscode'), private readonly resources: MockResources) {
|
||||
this._onStatusChangedEmitter = new vscode.EventEmitter<AzureLoginStatus>();
|
||||
this.onStatusChanged = this._onStatusChangedEmitter.event;
|
||||
this._onFiltersChangedEmitter = new vscode.EventEmitter<void>();
|
||||
this.onFiltersChanged = this._onFiltersChangedEmitter.event;
|
||||
this._onSessionsChangedEmitter = new vscode.EventEmitter<void>();
|
||||
this.onSessionsChanged = this._onSessionsChangedEmitter.event;
|
||||
this._onSubscriptionsChangedEmitter = new vscode.EventEmitter<void>();
|
||||
this.onSubscriptionsChanged = this._onSubscriptionsChangedEmitter.event;
|
||||
}
|
||||
logIn(): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
logOut(): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
selectSubscriptions(): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
allSubscriptions: AzureSubscription[];
|
||||
|
||||
public async signIn(): Promise<void> {
|
||||
this.changeStatus('LoggedIn');
|
||||
}
|
||||
|
||||
public signOut(): void {
|
||||
this.changeStatus('LoggedOut');
|
||||
this.changeFilter();
|
||||
}
|
||||
|
||||
public getSubscriptionContext(): ISubscriptionContext {
|
||||
this.verifySubscription();
|
||||
return createSubscriptionContext2(this.subscriptions[0]);
|
||||
}
|
||||
|
||||
public async waitForLogin(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
public async waitForSubscriptions(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
public async waitForFilters(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
private changeStatus(newStatus: AzureLoginStatus): void {
|
||||
this.status = newStatus;
|
||||
this._onStatusChangedEmitter.fire(this.status);
|
||||
}
|
||||
|
||||
private changeFilter(): void {
|
||||
this._onFiltersChangedEmitter.fire();
|
||||
}
|
||||
|
||||
private verifySubscription(): void {
|
||||
if (this.subscriptions.length === 0) {
|
||||
const noSubscription: string = 'No subscription found. Invoke TestAzureAccount.signIn().';
|
||||
throw new Error(noSubscription);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
import assert = require("assert");
|
||||
import { Event, EventEmitter, TreeItem } from "vscode";
|
||||
import { WorkspaceResource, WorkspaceResourceBranchDataProvider } from "../../extension.bundle";
|
||||
|
||||
export class TestBranchDataProvider implements WorkspaceResourceBranchDataProvider<WorkspaceResource> {
|
||||
|
||||
onDidChangeTreeDataEmitter: EventEmitter<WorkspaceResource | WorkspaceResource[] | undefined | null | void> = new EventEmitter<WorkspaceResource | WorkspaceResource[] | undefined | null | void>();
|
||||
onDidChangeTreeData: Event<WorkspaceResource | WorkspaceResource[] | undefined | null | void> = this.onDidChangeTreeDataEmitter.event;
|
||||
|
||||
private _getChildrenCalled = false;
|
||||
private _getResourceItemCalled = false;
|
||||
private _getTreeItemCalled = false;
|
||||
|
||||
private _childrenMap: Map<WorkspaceResource, WorkspaceResource[]> = new Map();
|
||||
|
||||
registerChildren(parent: WorkspaceResource, children: WorkspaceResource[]): void {
|
||||
this._childrenMap.set(parent, children);
|
||||
}
|
||||
|
||||
async assertGetChildrenCalledAsync(block: () => Promise<void>): Promise<void> {
|
||||
await block();
|
||||
assert.strictEqual(this._getChildrenCalled, true, 'Get children was not called');
|
||||
}
|
||||
|
||||
async assertGetResourceItemCalledAsync(block: () => Promise<void>): Promise<void> {
|
||||
await block();
|
||||
assert.strictEqual(this._getResourceItemCalled, true, 'Get resource item was not called');
|
||||
}
|
||||
|
||||
async assertGetTreeItemCalledAsync(block: () => Promise<void>): Promise<void> {
|
||||
await block();
|
||||
assert.strictEqual(this._getTreeItemCalled, true, 'Get tree item was not called');
|
||||
}
|
||||
|
||||
getChildren(_element: WorkspaceResource): WorkspaceResource[] {
|
||||
this._getChildrenCalled = true;
|
||||
return this._childrenMap.get(_element) ?? [];
|
||||
}
|
||||
|
||||
getResourceItem(element: WorkspaceResource): WorkspaceResource {
|
||||
this._getResourceItemCalled = true;
|
||||
return element;
|
||||
}
|
||||
|
||||
getTreeItem(element: WorkspaceResource): TreeItem {
|
||||
this._getTreeItemCalled = true;
|
||||
return new TreeItem(element.name)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
import { ext } from "../../extension.bundle";
|
||||
import { AzureResourcesHostApiInternal } from "../../hostapi.v2.internal";
|
||||
|
||||
export const api = (): AzureResourcesHostApiInternal => ext.v2.api.resources;
|
|
@ -0,0 +1,125 @@
|
|||
import * as assert from "assert";
|
||||
import { commands, TreeDataProvider, TreeItem } from "vscode";
|
||||
import { AzExtResourceType, AzureResource, AzureResourceItem, BranchDataItemWrapper, BranchDataProvider, ext, GroupingItem, ResourceGroupsItem, ResourceModelBase, SubscriptionItem } from "../../extension.bundle";
|
||||
import { createMockSubscriptionWithFunctions } from "./mockServiceFactory";
|
||||
|
||||
const api = () => {
|
||||
return ext.v2.api.resources;
|
||||
}
|
||||
|
||||
suite('Azure Resource Branch Data Provider tests', async () => {
|
||||
test('Registered Azure resource branch data provider is used', async () => {
|
||||
createMockSubscriptionWithFunctions();
|
||||
let getResourceItemIsCalled = false;
|
||||
const azureResourceBranchDataProvider: BranchDataProvider<AzureResource, ResourceModelBase> = {
|
||||
getResourceItem: (resource: AzureResource): ResourceModelBase => {
|
||||
getResourceItemIsCalled = true;
|
||||
return {
|
||||
id: resource.id,
|
||||
}
|
||||
},
|
||||
getChildren: (_resource: AzureResource): AzureResource[] => {
|
||||
return [];
|
||||
},
|
||||
getTreeItem: (resource: AzureResource): TreeItem => {
|
||||
return new TreeItem(resource.name);
|
||||
}
|
||||
}
|
||||
|
||||
api().registerAzureResourceBranchDataProvider(AzExtResourceType.FunctionApp, azureResourceBranchDataProvider);
|
||||
await commands.executeCommand('azureResourceGroups.groupBy.resourceType');
|
||||
|
||||
const tdp = api().azureResourceTreeDataProvider;
|
||||
const subscriptions = await tdp.getChildren();
|
||||
|
||||
const groups = await tdp.getChildren(subscriptions![0]) as TreeItem[];
|
||||
const functionGroup = groups!.find(g => g.label?.toString().includes('Func'));
|
||||
await tdp.getChildren(functionGroup);
|
||||
|
||||
assert.strictEqual(getResourceItemIsCalled, true);
|
||||
});
|
||||
|
||||
test('Tree should be resilliant to errors thrown in BranchDataProvider.getResourceItem', async () => {
|
||||
const mockResources = createMockSubscriptionWithFunctions();
|
||||
api().registerAzureResourceBranchDataProvider(AzExtResourceType.FunctionApp, {
|
||||
getResourceItem: (resource: AzureResource): ResourceModelBase => {
|
||||
if (resource.id === mockResources.functionApp1.id) {
|
||||
throw new Error('Cannot find resource');
|
||||
}
|
||||
return resource;
|
||||
},
|
||||
getChildren: (_resource: AzureResource): AzureResource[] => {
|
||||
return [];
|
||||
},
|
||||
getTreeItem: (resource: AzureResource): TreeItem => {
|
||||
return {
|
||||
label: resource.name,
|
||||
id: resource.id,
|
||||
contextValue: 'validItem'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await commands.executeCommand('azureResourceGroups.groupBy.resourceType');
|
||||
|
||||
const tdp = api().azureResourceTreeDataProvider;
|
||||
const subscriptions = await tdp.getChildren();
|
||||
|
||||
const groups = await tdp.getChildren(subscriptions![0]) as TreeItem[];
|
||||
const functionGroup = groups!.find(g => g.label?.toString().includes('Func'));
|
||||
const children = await tdp.getChildren(functionGroup) as BranchDataItemWrapper[];
|
||||
|
||||
const childTreeItems: TreeItem[] = await Promise.all(children.map(child => tdp.getTreeItem(child)));
|
||||
|
||||
assert.ok(children);
|
||||
|
||||
const invalidTreeItems = childTreeItems.filter(child => child.contextValue?.includes('invalid'));
|
||||
assert.strictEqual(invalidTreeItems.length, 1, `There should be 1 invalid tree item: ${invalidTreeItems.map(i => i.label).join(', ')}`);
|
||||
});
|
||||
|
||||
test('Should show a custom error message when BranchDataProvider.getResourceItem returns nullish value', async () => {
|
||||
const mockResources = createMockSubscriptionWithFunctions();
|
||||
api().registerAzureResourceBranchDataProvider(AzExtResourceType.FunctionApp, {
|
||||
getResourceItem: (resource: AzureResource): ResourceModelBase => {
|
||||
if (resource.id === mockResources.functionApp1.id) {
|
||||
return undefined as unknown as ResourceModelBase;
|
||||
}
|
||||
return resource;
|
||||
},
|
||||
getChildren: (_resource: AzureResource): AzureResource[] => {
|
||||
return [];
|
||||
},
|
||||
getTreeItem: (resource: AzureResource): TreeItem => {
|
||||
return {
|
||||
label: resource.name,
|
||||
id: resource.id,
|
||||
contextValue: 'validItem'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await commands.executeCommand('azureResourceGroups.groupBy.resourceType');
|
||||
|
||||
const tdp = api().azureResourceTreeDataProvider;
|
||||
const subscriptions = await tdp.getChildren();
|
||||
|
||||
const groups = await tdp.getChildren(subscriptions![0]) as TreeItem[];
|
||||
const functionGroup = groups!.find(g => g.label?.toString().includes('Func'));
|
||||
const children = await tdp.getChildren(functionGroup) as BranchDataItemWrapper[];
|
||||
|
||||
const childTreeItems: TreeItem[] = await Promise.all(children.map(child => tdp.getTreeItem(child)));
|
||||
|
||||
assert.ok(children);
|
||||
|
||||
const invalidTreeItems = childTreeItems.filter(child => child.contextValue?.includes('invalid'));
|
||||
assert.strictEqual(invalidTreeItems.length, 1, `There should be 1 invalid tree item: ${invalidTreeItems.map(i => i.label).join(', ')}`);
|
||||
assert.doesNotMatch(invalidTreeItems[0].label!.toString(), /Cannot read properties of undefined/, `Error should not be a "Cannot read properties of undefined error`);
|
||||
});
|
||||
});
|
||||
|
||||
export interface AzureResourceTreeDataProvider extends TreeDataProvider<ResourceGroupsItem> {
|
||||
getChildren(): Promise<SubscriptionItem[]>;
|
||||
getChildren(element: SubscriptionItem): Promise<GroupingItem[]>;
|
||||
getChildren(element: GroupingItem): Promise<AzureResourceItem<AzureResource>[]>;
|
||||
getChildren(element: AzureResourceItem<AzureResource>): Promise<BranchDataItemWrapper[]>;
|
||||
}
|
|
@ -0,0 +1,232 @@
|
|||
import * as assert from "assert";
|
||||
import { Event, TreeDataProvider, TreeItem } from "vscode";
|
||||
import { BranchDataItemWrapper, ext, isWrapper, ResourceGroupsItem, ResourceModelBase, WorkspaceResource, WorkspaceResourceProvider } from "../../extension.bundle";
|
||||
import { TestBranchDataProvider } from "./TestBranchDataProvider";
|
||||
|
||||
const getWorkspaceResourceProviderStub: (onCalled?: () => void, resources?: WorkspaceResource[]) => WorkspaceResourceProvider = (onCalled, resources) => {
|
||||
return {
|
||||
getResources: async () => {
|
||||
onCalled?.();
|
||||
return resources ?? [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const api = () => {
|
||||
return ext.v2.api.resources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Todo:
|
||||
* - tests for onDidChangeTreeData and refresh
|
||||
* - tests for groupings
|
||||
* - tests for if duplicate resources are returned by the service
|
||||
* - tests for tags
|
||||
* - tests for reveal
|
||||
* - pick experiences tests can easily exist here
|
||||
* - add compat shim tests
|
||||
* - add tests for node ids to match azure resource ids
|
||||
*/
|
||||
|
||||
suite('Branch data provider tests', async () => {
|
||||
|
||||
test('Provided workspace tree items are displayed', async () => {
|
||||
const workspaceResourceType = 'test2';
|
||||
const workspaceResource: WorkspaceResource = {
|
||||
resourceType: workspaceResourceType,
|
||||
id: 'test-resource-2',
|
||||
name: 'Test Resource 2',
|
||||
};
|
||||
const provider = getWorkspaceResourceProviderStub(undefined, [workspaceResource]);
|
||||
api().registerWorkspaceResourceProvider(provider);
|
||||
api().registerWorkspaceResourceBranchDataProvider(workspaceResourceType, {
|
||||
getResourceItem: (resource: WorkspaceResource): ResourceModelBase => {
|
||||
return resource;
|
||||
},
|
||||
getChildren: (_resource: WorkspaceResource): WorkspaceResource[] => {
|
||||
return [];
|
||||
},
|
||||
getTreeItem: (resource: WorkspaceResource): TreeItem => {
|
||||
return new TreeItem(resource.name);
|
||||
}
|
||||
})
|
||||
|
||||
const children = await api().workspaceResourceTreeDataProvider.getChildren() as any[];
|
||||
const testChild = children.find(c => c.id === workspaceResource.id);
|
||||
assert.ok(testChild);
|
||||
});
|
||||
|
||||
test('Provided workspace tree items children are displayed', async () => {
|
||||
const workspaceResourceType = 'test3';
|
||||
const workspaceResource: WorkspaceResource = {
|
||||
resourceType: workspaceResourceType,
|
||||
id: 'test-resource-3',
|
||||
name: 'Test Resource 3',
|
||||
};
|
||||
const provider = getWorkspaceResourceProviderStub(undefined, [workspaceResource]);
|
||||
api().registerWorkspaceResourceProvider(provider);
|
||||
api().registerWorkspaceResourceBranchDataProvider(workspaceResourceType, {
|
||||
getResourceItem: (resource: WorkspaceResource): ResourceModelBase => {
|
||||
return resource;
|
||||
},
|
||||
getChildren: (_resource: WorkspaceResource): WorkspaceResource[] => {
|
||||
return [
|
||||
{
|
||||
id: 'test-resource-3-child',
|
||||
name: 'Test Resource 3 Child',
|
||||
resourceType: workspaceResourceType,
|
||||
}
|
||||
];
|
||||
},
|
||||
getTreeItem: (resource: WorkspaceResource): TreeItem => {
|
||||
return new TreeItem(resource.name);
|
||||
}
|
||||
})
|
||||
|
||||
const children = await api().workspaceResourceTreeDataProvider.getChildren() as any[];
|
||||
const testChild = children.find(c => c.id === workspaceResource.id);
|
||||
|
||||
const testChildChildren = await api().workspaceResourceTreeDataProvider.getChildren(testChild) as any[];
|
||||
assert.strictEqual(testChildChildren.length, 1);
|
||||
assert.strictEqual(testChildChildren[0].id, 'test-resource-3-child');
|
||||
});
|
||||
|
||||
test('BranchDataProvider.getTreeItem should be called for resource items', async () => {
|
||||
const workspaceResourceType = 'test-4';
|
||||
const workspaceResource: WorkspaceResource = {
|
||||
resourceType: workspaceResourceType,
|
||||
id: 'test-resource-3',
|
||||
name: 'Test Resource 3',
|
||||
};
|
||||
const provider = getWorkspaceResourceProviderStub(undefined, [workspaceResource]);
|
||||
api().registerWorkspaceResourceProvider(provider);
|
||||
|
||||
const branchDataProvider = new TestBranchDataProvider();
|
||||
api().registerWorkspaceResourceBranchDataProvider(workspaceResourceType, branchDataProvider);
|
||||
|
||||
const childResource: WorkspaceResource = {
|
||||
id: 'test-resource-child',
|
||||
name: 'Test Resource 3 Child',
|
||||
resourceType: workspaceResourceType,
|
||||
}
|
||||
|
||||
branchDataProvider.registerChildren(workspaceResource, [childResource]);
|
||||
|
||||
const rootChildren = await api().workspaceResourceTreeDataProvider.getChildren() as any[];
|
||||
const testChild = rootChildren.find(c => c.id === workspaceResource.id);
|
||||
branchDataProvider.assertGetTreeItemCalledAsync(async () => {
|
||||
await api().workspaceResourceTreeDataProvider.getTreeItem(testChild);
|
||||
});
|
||||
});
|
||||
|
||||
test('BranchDataProvider.getTreeItem should be called for resource item children', async () => {
|
||||
const workspaceResource: WorkspaceResource = {
|
||||
resourceType: 'test56',
|
||||
id: 'test-resource-56',
|
||||
name: 'Test Resource 56',
|
||||
};
|
||||
|
||||
const workspaceResourceChild: WorkspaceResource = {
|
||||
id: 'test-resource-child',
|
||||
name: 'Test Resource 3 Child',
|
||||
resourceType: workspaceResource.resourceType,
|
||||
};
|
||||
|
||||
api().registerWorkspaceResourceProvider(getWorkspaceResourceProviderStub(undefined, [workspaceResource]));
|
||||
|
||||
const branchDataProvider = new TestBranchDataProvider();
|
||||
branchDataProvider.registerChildren(workspaceResource, [workspaceResourceChild]);
|
||||
|
||||
api().registerWorkspaceResourceBranchDataProvider(workspaceResource.resourceType, branchDataProvider);
|
||||
|
||||
const workspaceResourceNodes = await api().workspaceResourceTreeDataProvider.getChildren() as WorkspaceResource[];
|
||||
const workspaceResourceNode = workspaceResourceNodes.find(c => c.id === workspaceResource.id);
|
||||
|
||||
assert.ok(workspaceResourceNode, `No workspace resource node found with id: ${workspaceResource.id}. Workspace resource nodes: ${workspaceResourceNodes.map(item => item.id).join(', ')}`);
|
||||
|
||||
const workspaceResourceChildNodes = await api().workspaceResourceTreeDataProvider.getChildren(workspaceResourceNode) as WorkspaceResource[];
|
||||
assert.strictEqual(workspaceResourceChildNodes.length, 1);
|
||||
|
||||
const workspaceResourceChildNode = workspaceResourceChildNodes[0];
|
||||
assert.strictEqual(workspaceResourceChildNode.id, workspaceResourceChild.id);
|
||||
|
||||
branchDataProvider.assertGetTreeItemCalledAsync(async () => {
|
||||
await api().workspaceResourceTreeDataProvider.getTreeItem(workspaceResourceChildNode);
|
||||
});
|
||||
});
|
||||
|
||||
test('BranchDataProvider returns wrappers', async () => {
|
||||
const { workspaceResource, tdp } = setupTestBranchDataProvider();
|
||||
const rootChildren = await tdp.getChildren() as any[];
|
||||
const testChild = rootChildren.find(c => c.id === workspaceResource.id);
|
||||
assert.strictEqual(isWrapper(testChild), true);
|
||||
});
|
||||
|
||||
test('Firing BranchDataProvider.onDidChangeTreeData event with a branch item should fire a TreeDataProvider.onDidChangeTreeData evente with the corresponding wrapper item.', async () => {
|
||||
const { testChild, branchDataProvider, tdp } = await getGrandchild();
|
||||
|
||||
const unwrappedChild = testChild.unwrap<WorkspaceResource>();
|
||||
const waitForOnDidChangeTreeDataToFire = waitForEventToFire(tdp.onDidChangeTreeData!);
|
||||
branchDataProvider.onDidChangeTreeDataEmitter.fire(unwrappedChild);
|
||||
|
||||
const eventData = await waitForOnDidChangeTreeDataToFire;
|
||||
assert.strictEqual((eventData as ResourceGroupsItem[])[0], testChild);
|
||||
});
|
||||
|
||||
test('Firing BranchDataProvider.onDidChangeTreeData event with a wrapper item should result in TreeDataProvider.onDidChangeTreeData being fired with an empty array.', async () => {
|
||||
const { testChild, branchDataProvider, tdp } = await getGrandchild();
|
||||
|
||||
const waitForOnDidChangeTreeDataToFire = waitForEventToFire(tdp.onDidChangeTreeData!);
|
||||
branchDataProvider.onDidChangeTreeDataEmitter.fire(testChild as unknown as WorkspaceResource);
|
||||
|
||||
const eventData = await waitForOnDidChangeTreeDataToFire;
|
||||
assert.strictEqual((eventData as []).length, 0);
|
||||
});
|
||||
});
|
||||
|
||||
function setupTestBranchDataProvider() {
|
||||
const workspaceResourceType = 'test-4';
|
||||
const workspaceResource: WorkspaceResource = {
|
||||
resourceType: workspaceResourceType,
|
||||
id: 'test-resource-3',
|
||||
name: 'Test Resource 3',
|
||||
};
|
||||
const provider = getWorkspaceResourceProviderStub(undefined, [workspaceResource]);
|
||||
api().registerWorkspaceResourceProvider(provider);
|
||||
|
||||
const branchDataProvider = new TestBranchDataProvider();
|
||||
api().registerWorkspaceResourceBranchDataProvider(workspaceResourceType, branchDataProvider);
|
||||
|
||||
const childResource: WorkspaceResource = {
|
||||
id: 'test-resource-child',
|
||||
name: 'Test Resource 3 Child',
|
||||
resourceType: workspaceResourceType,
|
||||
}
|
||||
|
||||
branchDataProvider.registerChildren(workspaceResource, [childResource]);
|
||||
|
||||
const tdp = api().workspaceResourceTreeDataProvider as TreeDataProvider<unknown>;
|
||||
return { branchDataProvider, tdp, workspaceResource };
|
||||
}
|
||||
|
||||
async function getGrandchild() {
|
||||
const { branchDataProvider, workspaceResource } = setupTestBranchDataProvider();
|
||||
const tdp = api().workspaceResourceTreeDataProvider as TreeDataProvider<unknown>;
|
||||
|
||||
const rootChildren = await tdp.getChildren() as BranchDataItemWrapper[];
|
||||
const testChild = rootChildren.find(c => c.id === workspaceResource.id);
|
||||
assert.ok(testChild, `No test child found with id: ${workspaceResource.id}. Test children: ${rootChildren.map(item => item.id).join(', ')}`);
|
||||
return { testChild, branchDataProvider, tdp };
|
||||
}
|
||||
|
||||
async function waitForEventToFire<T>(event: Event<T>): Promise<T> {
|
||||
return new Promise<T>((resolve) => {
|
||||
const disposable = event(data => {
|
||||
if (data === data) {
|
||||
disposable.dispose();
|
||||
resolve(data);
|
||||
} else {
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import assert = require('assert');
|
||||
import { randomUUID } from 'crypto';
|
||||
import { commands, TreeItem } from 'vscode';
|
||||
import { AzExtResourceType, AzureResource, BranchDataProvider, ext, IActionContext, ResourceModelBase } from '../../../extension.bundle';
|
||||
import { api } from '../api';
|
||||
import { createMockSubscriptionWithFunctions } from '../mockServiceFactory';
|
||||
|
||||
suite('findItemById', () => {
|
||||
|
||||
async function testFindItemById(): Promise<void> {
|
||||
const mocks = createMockSubscriptionWithFunctions();
|
||||
const azureResourceBranchDataProvider: BranchDataProvider<AzureResource, ResourceModelBase> = {
|
||||
getResourceItem: (resource: AzureResource): ResourceModelBase => {
|
||||
return {
|
||||
id: resource.id,
|
||||
}
|
||||
},
|
||||
getChildren: (_resource: AzureResource): AzureResource[] => {
|
||||
return [];
|
||||
},
|
||||
getTreeItem: (resource: AzureResource): TreeItem => {
|
||||
return new TreeItem(resource.name);
|
||||
}
|
||||
}
|
||||
|
||||
api().registerAzureResourceBranchDataProvider(AzExtResourceType.FunctionApp, azureResourceBranchDataProvider);
|
||||
|
||||
const treeItem = await ext.appResourceTree.findTreeItem(mocks.functionApp1.id, {} as unknown as IActionContext);
|
||||
assert.ok(treeItem);
|
||||
assert.strictEqual(treeItem.id, mocks.functionApp1.id);
|
||||
}
|
||||
|
||||
test('Can find based on ARM id while grouped by type', async () => {
|
||||
await commands.executeCommand('azureResourceGroups.groupBy.resourceType');
|
||||
await testFindItemById();
|
||||
});
|
||||
|
||||
test('Can find based on ARM id while grouped by resource group', async () => {
|
||||
await commands.executeCommand('azureResourceGroups.groupBy.resourceGroup');
|
||||
await testFindItemById();
|
||||
});
|
||||
|
||||
test('Can find based on ARM id while grouped by location', async () => {
|
||||
await commands.executeCommand('azureResourceGroups.groupBy.location');
|
||||
await testFindItemById();
|
||||
});
|
||||
|
||||
// needed for default resource to deploy feature
|
||||
test('Can find item based on v1 tree item id', async () => {
|
||||
const mocks = createMockSubscriptionWithFunctions();
|
||||
const azureResourceBranchDataProvider: BranchDataProvider<AzureResource, ResourceModelBase> = {
|
||||
getResourceItem: (resource: AzureResource): ResourceModelBase => {
|
||||
return {
|
||||
id: resource.id,
|
||||
}
|
||||
},
|
||||
getChildren: (_resource: AzureResource): AzureResource[] => {
|
||||
return [];
|
||||
},
|
||||
getTreeItem: (resource: AzureResource): TreeItem => {
|
||||
return new TreeItem(resource.name);
|
||||
}
|
||||
}
|
||||
|
||||
api().registerAzureResourceBranchDataProvider(AzExtResourceType.FunctionApp, azureResourceBranchDataProvider);
|
||||
await commands.executeCommand('azureResourceGroups.groupBy.resourceType');
|
||||
|
||||
const treeItem = await ext.appResourceTree.findTreeItem(`/subscriptions/${randomUUID()}/FunctionApps/${mocks.functionApp1.id}`, {} as unknown as IActionContext);
|
||||
assert.ok(treeItem);
|
||||
assert.strictEqual(treeItem.id, mocks.functionApp1.id);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,38 @@
|
|||
import { ExtensionContext } from "vscode";
|
||||
import { getAzureResourcesExtensionApi } from "../../extension.bundle";
|
||||
import assert = require("assert");
|
||||
|
||||
suite('getAzureResourcesExtensionApi() tests', async () => {
|
||||
async function getApi() {
|
||||
return await getAzureResourcesExtensionApi({
|
||||
extension: {
|
||||
id: 'ms-azuretools.vscode-azuretestextension'
|
||||
}
|
||||
} as ExtensionContext, '2.0.0');
|
||||
}
|
||||
|
||||
test("getAzureResourcesExtensionApi() should return an API instance which has a 'resources' property", async () => {
|
||||
const api = await getApi();
|
||||
assert.ok(api.resources);
|
||||
});
|
||||
|
||||
test("getAzureResourcesExtensionApi() should return unique instances of the API", async () => {
|
||||
const api1 = await getApi();
|
||||
const api2 = await getApi();
|
||||
assert.notStrictEqual(api1, api2);
|
||||
});
|
||||
|
||||
test("getAzureResourcesExtensionApi() should return an object that throws when modified", async () => {
|
||||
const api = await getApi();
|
||||
assert.throws(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
api.resources = 'foo';
|
||||
});
|
||||
});
|
||||
|
||||
test("getAzureResourcesExtensionApi() should return a frozen object", async () => {
|
||||
const api = await getApi();
|
||||
assert(Object.isFrozen(api));
|
||||
});
|
||||
});
|
|
@ -0,0 +1,74 @@
|
|||
import { AzureResourceModel } from "@microsoft/vscode-azureresources-api";
|
||||
import * as assert from 'assert';
|
||||
import { commands, TreeItem, Uri } from "vscode";
|
||||
import { AzExtResourceType, AzureResource, AzureResourceBranchDataProvider, BranchDataItemWrapper, ext } from "../../extension.bundle";
|
||||
import { createMockSubscriptionWithFunctions } from "./mockServiceFactory";
|
||||
|
||||
const api = () => {
|
||||
return ext.v2.api.resources;
|
||||
}
|
||||
|
||||
type Mutable<T> = {
|
||||
-readonly [k in keyof T]: T[k];
|
||||
};
|
||||
|
||||
type TestAzureResourceModel = Mutable<AzureResourceModel> & AzureResource;
|
||||
|
||||
// BDP that returns a model with a portal url ONLY for the resource with the specified id
|
||||
class PortalUrlBranchDataProvider implements AzureResourceBranchDataProvider<TestAzureResourceModel> {
|
||||
constructor(public readonly resourceId: string) { }
|
||||
|
||||
getResourceItem(resource: AzureResource): TestAzureResourceModel {
|
||||
return resource;
|
||||
}
|
||||
|
||||
getChildren(resource: TestAzureResourceModel): TestAzureResourceModel[] {
|
||||
const childModel: TestAzureResourceModel = {
|
||||
...resource,
|
||||
id: resource.id + '/child',
|
||||
name: resource.name + '-child',
|
||||
};
|
||||
|
||||
if (this.resourceId === resource.id) {
|
||||
childModel.portalUrl = Uri.parse('https://portal.azure.com');
|
||||
}
|
||||
|
||||
return [childModel];
|
||||
}
|
||||
|
||||
getTreeItem(resource: TestAzureResourceModel): TreeItem {
|
||||
return {
|
||||
label: resource.name,
|
||||
id: resource.id,
|
||||
contextValue: 'validItem'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suite('AzureResourceModel.portalUrl tests', async () => {
|
||||
test(`TreeItem.contextValue should include "${BranchDataItemWrapper.hasPortalUrlContextValue}" if AzureResourceModel.portalUrl is defined`, async () => {
|
||||
const mockResources = createMockSubscriptionWithFunctions();
|
||||
api().registerAzureResourceBranchDataProvider(AzExtResourceType.FunctionApp, new PortalUrlBranchDataProvider(mockResources.functionApp1.id));
|
||||
await commands.executeCommand('azureResourceGroups.groupBy.resourceType');
|
||||
|
||||
const tdp = api().azureResourceTreeDataProvider;
|
||||
const subscriptions = await tdp.getChildren();
|
||||
|
||||
const groups = await tdp.getChildren(subscriptions![0]) as TreeItem[];
|
||||
const functionGroup = groups!.find(g => g.label?.toString().includes('Func'));
|
||||
const children = await tdp.getChildren(functionGroup) as BranchDataItemWrapper[];
|
||||
const grandchildren = await Promise.all(children.map(child => tdp.getChildren(child) as Promise<BranchDataItemWrapper[]>));
|
||||
|
||||
const grandChildTreeItems: TreeItem[] = [];
|
||||
for await (const grandchildrenset of grandchildren) {
|
||||
if (grandchildrenset.length > 0) {
|
||||
for await (const grandchild of grandchildrenset) {
|
||||
const treeItem = await tdp.getTreeItem(grandchild);
|
||||
grandChildTreeItems.push(treeItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
const grandchildrenTreeItemsWithPortalUrl = grandChildTreeItems.filter(treeItem => treeItem.contextValue?.includes(BranchDataItemWrapper.hasPortalUrlContextValue));
|
||||
assert.strictEqual(grandchildrenTreeItemsWithPortalUrl.length, 1, `There should be 1 tree item with "${BranchDataItemWrapper.hasPortalUrlContextValue}" context value`);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,125 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { GenericResource, ResourceGroup } from "@azure/arm-resources";
|
||||
import { randomUUID } from "crypto";
|
||||
import * as vscode from 'vscode';
|
||||
import { AzureResourcesServiceFactory, AzureSubscription, ext } from "../../extension.bundle";
|
||||
import { MockAzureAccount } from "./MockAzureAccountApi";
|
||||
|
||||
export class MockResources {
|
||||
get subscriptions(): MockSubscription[] {
|
||||
return Array.from(this.subscriptionsMap.values());
|
||||
}
|
||||
subscriptionsMap: Map<string, MockSubscription> = new Map();
|
||||
}
|
||||
|
||||
class MockSubscription {
|
||||
constructor(public readonly name: string) { }
|
||||
readonly subscriptionId = randomUUID();
|
||||
readonly id: string = `/subscriptions/${this.subscriptionId}`;
|
||||
readonly resourceGroups: MockResourceGroup[] = [];
|
||||
|
||||
get resources(): MockResource[] {
|
||||
return this.resourceGroups.reduce((acc, rg) => acc.concat(rg.resources), [] as MockResource[]);
|
||||
}
|
||||
}
|
||||
|
||||
class MockResourceGroup implements ResourceGroup {
|
||||
readonly type: string = 'microsoft.resources/resourcegroups';
|
||||
constructor(private readonly subscriptionId: string, public readonly name: string, public readonly location: string) { }
|
||||
readonly resources: MockResource[] = [];
|
||||
readonly id: string = `${this.subscriptionId}/resourceGroups/${this.name}`;
|
||||
}
|
||||
|
||||
class MockResource implements GenericResource {
|
||||
constructor(private readonly resourceGroupId: string, public readonly name: string, public readonly type: string, public readonly kind: string) { }
|
||||
readonly id: string = `${this.resourceGroupId}/providers/${this.type}/${this.name}`;
|
||||
}
|
||||
|
||||
function addSubscription(resources: MockResources, name: string) {
|
||||
const subscription = new MockSubscription(name);
|
||||
resources.subscriptionsMap.set(subscription.subscriptionId, subscription);
|
||||
return subscription;
|
||||
}
|
||||
|
||||
function addResourceGroup(subscription: MockSubscription, resourceGroupOptions: { name: string, location?: string }): MockResourceGroup {
|
||||
const mockResourceGroup = new MockResourceGroup(subscription.id, resourceGroupOptions.name, resourceGroupOptions.location ?? 'westus2');
|
||||
subscription.resourceGroups.push(mockResourceGroup);
|
||||
return mockResourceGroup;
|
||||
}
|
||||
|
||||
function addResource(resourceGroup: MockResourceGroup, resourceOptions: { name: string, type: string, kind: string }): MockResource {
|
||||
const resource = new MockResource(resourceGroup.id, resourceOptions.name, resourceOptions.type, resourceOptions.kind);
|
||||
resourceGroup.resources.push(resource);
|
||||
return resource;
|
||||
}
|
||||
|
||||
function addFunctionApp(resoruceGroup: MockResourceGroup, name: string): MockResource {
|
||||
return addResource(resoruceGroup, {
|
||||
name,
|
||||
type: 'microsoft.web/sites',
|
||||
kind: 'functionapp',
|
||||
});
|
||||
}
|
||||
|
||||
export class MockSubscriptionWithFunctions extends MockSubscription {
|
||||
constructor() {
|
||||
super('mock-subscription-with-functions');
|
||||
this.rg1 = addResourceGroup(this, {
|
||||
name: 'test-rg-1',
|
||||
});
|
||||
|
||||
this.functionApp1 = addFunctionApp(this.rg1, 'test-function-1');
|
||||
addFunctionApp(this.rg1, 'test-function-2');
|
||||
addFunctionApp(this.rg1, 'test-function-3');
|
||||
}
|
||||
|
||||
readonly rg1: MockResourceGroup;
|
||||
readonly functionApp1: MockResource;
|
||||
}
|
||||
|
||||
class BasicMockResources extends MockResources {
|
||||
|
||||
readonly sub1: MockSubscriptionWithFunctions;
|
||||
readonly functionApp1: MockResource;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.sub1 = new MockSubscriptionWithFunctions();
|
||||
this.functionApp1 = this.sub1.functionApp1;
|
||||
this.subscriptionsMap.set(this.sub1.subscriptionId, this.sub1);
|
||||
addSubscription(this, 'mock-subscription-1');
|
||||
addSubscription(this, 'mock-subscription-2');
|
||||
}
|
||||
}
|
||||
|
||||
export const createMockSubscriptionWithFunctions = (): BasicMockResources => {
|
||||
const mockResources = new BasicMockResources();
|
||||
ext.testing.overrideAzureServiceFactory = createTestAzureResourcesServiceFactory(mockResources);
|
||||
const mockAzureAccountApi = new MockAzureAccount(vscode, mockResources);
|
||||
ext.testing.overrideAzureSubscriptionProvider = () => mockAzureAccountApi;
|
||||
return mockResources;
|
||||
}
|
||||
|
||||
export function createTestAzureResourcesServiceFactory(mockResources: MockResources): AzureResourcesServiceFactory {
|
||||
return () => {
|
||||
return {
|
||||
async listResources(_context, subscription: AzureSubscription): Promise<GenericResource[]> {
|
||||
if (mockResources.subscriptionsMap.has(subscription.subscriptionId)) {
|
||||
return mockResources.subscriptionsMap.get(subscription.subscriptionId)!.resources;
|
||||
}
|
||||
throw new Error(`Subscription ${subscription.subscriptionId} not found. \n\t${mockResources.subscriptions.map(s => s.subscriptionId).join('\n\t')}`);
|
||||
},
|
||||
async listResourceGroups(_context, subscription: AzureSubscription): Promise<ResourceGroup[]> {
|
||||
if (mockResources.subscriptionsMap.has(subscription.subscriptionId)) {
|
||||
return mockResources.subscriptionsMap.get(subscription.subscriptionId)!.resourceGroups;
|
||||
}
|
||||
throw new Error(`Subscription ${subscription.subscriptionId} not found. \n\t${mockResources.subscriptions.map(s => s.subscriptionId).join('\n\t')}`);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
import * as vscode from 'vscode';
|
||||
import { hasPortalUrl } from '../../extension.bundle';
|
||||
import { api } from "./api";
|
||||
import { AzureResourceTreeDataProvider } from "./azureResourceBranchDataProvider.test";
|
||||
import { createMockSubscriptionWithFunctions } from "./mockServiceFactory";
|
||||
import assert = require("assert");
|
||||
|
||||
async function mockResourcesAndGetSubscriptionItems() {
|
||||
const mockResources = createMockSubscriptionWithFunctions();
|
||||
await vscode.commands.executeCommand('azureResourceGroups.groupBy.resourceGroup');
|
||||
|
||||
const tdp = api().azureResourceTreeDataProvider as AzureResourceTreeDataProvider;
|
||||
const subscriptionItems = await tdp.getChildren();
|
||||
return { mockResources, subscriptionItems };
|
||||
}
|
||||
|
||||
suite('SubscriptionItem tests', async () => {
|
||||
test('Tree should show a subscription item for each Azure subscription', async () => {
|
||||
const { mockResources, subscriptionItems } = await mockResourcesAndGetSubscriptionItems();
|
||||
mockResources.subscriptions.forEach((subscription) => {
|
||||
assert.ok(subscriptionItems?.find((node) => node.id === subscription.id));
|
||||
});
|
||||
|
||||
assert.strictEqual(subscriptionItems.length, mockResources.subscriptions.length, `There should be ${mockResources.subscriptions.length} subscription nodes. Found ${subscriptionItems.length}.`);
|
||||
});
|
||||
|
||||
test('SubscriptionItem.id should be in the "/subscriptions/<subscription id>" format', async () => {
|
||||
const { subscriptionItems } = await mockResourcesAndGetSubscriptionItems();
|
||||
subscriptionItems.forEach((subscriptionItem) => {
|
||||
assert.match(subscriptionItem.id, /^\/subscriptions\/([0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12})/i);
|
||||
});
|
||||
});
|
||||
|
||||
test('SubscriptionItem should have a portal url', async () => {
|
||||
const { subscriptionItems } = await mockResourcesAndGetSubscriptionItems();
|
||||
subscriptionItems.forEach((subscriptionItem) => {
|
||||
assert.ok(hasPortalUrl(subscriptionItem));
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* The subscription property is used by clients when passed to the Create Resource... command
|
||||
*/
|
||||
test('SubscriptionItem.subscription satisfies ISubscriptionContext for v1.5 compatibility', async () => {
|
||||
const { subscriptionItems } = await mockResourcesAndGetSubscriptionItems();
|
||||
subscriptionItems.forEach((subscriptionItem) => {
|
||||
const subscriptonContext = subscriptionItem.subscription;
|
||||
assert.ok(subscriptonContext.credentials);
|
||||
assert.ok(subscriptonContext.subscriptionDisplayName);
|
||||
assert.ok(subscriptonContext.userId !== undefined);
|
||||
assert.ok(subscriptonContext.subscriptionPath);
|
||||
});
|
||||
});
|
||||
})
|
|
@ -0,0 +1,16 @@
|
|||
import { apiUtils } from "../../extension.bundle";
|
||||
import assert = require("assert");
|
||||
|
||||
suite('v1 API tests', async () => {
|
||||
test('v1 API should be defined', async () => {
|
||||
const apiProvider = await apiUtils.getExtensionExports<apiUtils.AzureExtensionApiProvider>('ms-azuretools.vscode-azureresourcegroups');
|
||||
|
||||
assert.ok(apiProvider, 'API provider is undefined');
|
||||
|
||||
const v1Api = apiProvider.getApi('0.0.1', {
|
||||
extensionId: 'ms-azuretools.vscode-azureresourcegroups-tests',
|
||||
});
|
||||
|
||||
assert.ok(v1Api);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,98 @@
|
|||
import { AzureResourceModel } from "@microsoft/vscode-azureresources-api";
|
||||
import { commands, TreeItem } from "vscode";
|
||||
import { AzExtResourceType, AzureResource, AzureResourceBranchDataProvider, BranchDataItemWrapper, ext, hasViewProperties } from "../../extension.bundle";
|
||||
import { createMockSubscriptionWithFunctions } from "./mockServiceFactory";
|
||||
import assert = require("assert");
|
||||
|
||||
const api = () => {
|
||||
return ext.v2.api.resources;
|
||||
}
|
||||
|
||||
type Mutable<T> = {
|
||||
-readonly [k in keyof T]: T[k];
|
||||
};
|
||||
|
||||
type TestAzureResourceModel = Mutable<AzureResourceModel> & AzureResource;
|
||||
|
||||
// BDP that returns a model with a view properties model ONLY for a specific resource
|
||||
class ViewPropertiesBranchDataProvider implements AzureResourceBranchDataProvider<TestAzureResourceModel> {
|
||||
|
||||
constructor(public readonly resourceId: string) { }
|
||||
|
||||
getResourceItem(resource: AzureResource): TestAzureResourceModel {
|
||||
return resource;
|
||||
}
|
||||
|
||||
getChildren(resource: TestAzureResourceModel): TestAzureResourceModel[] {
|
||||
const childModel: TestAzureResourceModel = {
|
||||
...resource,
|
||||
id: resource.id + '/child',
|
||||
name: resource.name + '-child',
|
||||
};
|
||||
|
||||
if (this.resourceId === resource.id) {
|
||||
childModel.viewProperties = {
|
||||
data: {
|
||||
foo: 'bar',
|
||||
},
|
||||
label: 'properties',
|
||||
}
|
||||
}
|
||||
|
||||
return [childModel];
|
||||
}
|
||||
|
||||
getTreeItem(resource: TestAzureResourceModel): TreeItem {
|
||||
return {
|
||||
label: resource.name,
|
||||
id: resource.id,
|
||||
contextValue: 'validItem'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suite('AzureResourceModel.viewProperties tests', async () => {
|
||||
test(`TreeItem.contextValue should only include "${BranchDataItemWrapper.hasPropertiesContextValue}" if AzureResourceModel.viewProperties is defined`, async () => {
|
||||
const mockResources = createMockSubscriptionWithFunctions();
|
||||
api().registerAzureResourceBranchDataProvider(AzExtResourceType.FunctionApp, new ViewPropertiesBranchDataProvider(mockResources.functionApp1.id));
|
||||
await commands.executeCommand('azureResourceGroups.groupBy.resourceType');
|
||||
|
||||
const tdp = api().azureResourceTreeDataProvider;
|
||||
const subscriptions = await tdp.getChildren();
|
||||
|
||||
const groups = await tdp.getChildren(subscriptions![0]) as TreeItem[];
|
||||
const functionGroup = groups!.find(g => g.label?.toString().includes('Func'));
|
||||
const children = await tdp.getChildren(functionGroup) as BranchDataItemWrapper[];
|
||||
const grandchildren = await Promise.all(children.map(child => tdp.getChildren(child) as Promise<BranchDataItemWrapper[]>));
|
||||
|
||||
const grandChildTreeItems: TreeItem[] = [];
|
||||
for await (const grandchildrenset of grandchildren) {
|
||||
if (grandchildrenset.length > 0) {
|
||||
for await (const grandchild of grandchildrenset) {
|
||||
const treeItem = await tdp.getTreeItem(grandchild);
|
||||
grandChildTreeItems.push(treeItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
const grandchildrenTreeItemsWithPortalUrl = grandChildTreeItems.filter(treeItem => treeItem.contextValue?.includes(BranchDataItemWrapper.hasPropertiesContextValue));
|
||||
assert.strictEqual(grandchildrenTreeItemsWithPortalUrl.length, 1, `There should be 1 tree item with "${BranchDataItemWrapper.hasPropertiesContextValue}" context value`);
|
||||
});
|
||||
|
||||
test(`BrachDataItemWrapper should have viewPropertiesModel`, async () => {
|
||||
const mockResources = createMockSubscriptionWithFunctions();
|
||||
api().registerAzureResourceBranchDataProvider(AzExtResourceType.FunctionApp, new ViewPropertiesBranchDataProvider(mockResources.functionApp1.id));
|
||||
await commands.executeCommand('azureResourceGroups.groupBy.resourceType');
|
||||
|
||||
const tdp = api().azureResourceTreeDataProvider;
|
||||
const subscriptions = await tdp.getChildren();
|
||||
|
||||
const groups = await tdp.getChildren(subscriptions![0]) as TreeItem[];
|
||||
const functionGroup = groups!.find(g => g.label?.toString().includes('Func'));
|
||||
const children = await tdp.getChildren(functionGroup) as BranchDataItemWrapper[];
|
||||
|
||||
const functionApp1Item = children.find(child => child.id === mockResources.functionApp1.id);
|
||||
assert.ok(functionApp1Item);
|
||||
|
||||
assert.ok(hasViewProperties(functionApp1Item));
|
||||
});
|
||||
});
|
|
@ -0,0 +1,64 @@
|
|||
import { TreeItem } from "vscode";
|
||||
import { ResourceModelBase, WorkspaceResource, WorkspaceResourceProvider } from "../../extension.bundle";
|
||||
import { api } from "./api";
|
||||
import assert = require("assert");
|
||||
|
||||
class TestWorkspaceResourceProvider implements WorkspaceResourceProvider {
|
||||
constructor(private readonly _resources?: WorkspaceResource[]) { }
|
||||
private _getResourcesCalled = false;
|
||||
|
||||
/**
|
||||
* Asserts that `getResources` was called at least once.
|
||||
*/
|
||||
assertGetResourcesWasCalled(): void {
|
||||
assert.ok(this._getResourcesCalled, 'Get resources was not called');
|
||||
}
|
||||
|
||||
getResources(): WorkspaceResource[] {
|
||||
this._getResourcesCalled = true;
|
||||
return this._resources ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
suite('workspace resource provider tests', async () => {
|
||||
test("Registering a workspace resource provider doesn't throw an error", () => {
|
||||
const provider = new TestWorkspaceResourceProvider();
|
||||
assert.doesNotThrow(() => {
|
||||
api().registerWorkspaceResourceProvider(provider);
|
||||
});
|
||||
});
|
||||
|
||||
test('Registered workspace resource provider is used', async () => {
|
||||
const provider = new TestWorkspaceResourceProvider();
|
||||
api().registerWorkspaceResourceProvider(provider);
|
||||
await api().workspaceResourceTreeDataProvider.getChildren();
|
||||
provider.assertGetResourcesWasCalled();
|
||||
});
|
||||
|
||||
test('Resources provided by workspace resource provider are then passed to the corresponding branch data provider', async () => {
|
||||
const workspaceResource: WorkspaceResource = {
|
||||
resourceType: 'test1',
|
||||
id: 'test-resource-1',
|
||||
name: 'Test Resource 1',
|
||||
};
|
||||
|
||||
const resourceProvider = new TestWorkspaceResourceProvider([workspaceResource]);
|
||||
api().registerWorkspaceResourceProvider(resourceProvider);
|
||||
|
||||
api().registerWorkspaceResourceBranchDataProvider(workspaceResource.resourceType, {
|
||||
getResourceItem: (resource: WorkspaceResource): ResourceModelBase => {
|
||||
assert.strictEqual(resource, workspaceResource);
|
||||
return resource;
|
||||
},
|
||||
getChildren: (_resource: WorkspaceResource): WorkspaceResource[] => {
|
||||
return [];
|
||||
},
|
||||
getTreeItem: (resource: WorkspaceResource): TreeItem => {
|
||||
return new TreeItem(resource.name);
|
||||
}
|
||||
});
|
||||
|
||||
await api().workspaceResourceTreeDataProvider.getChildren();
|
||||
resourceProvider.assertGetResourcesWasCalled();
|
||||
});
|
||||
});
|
|
@ -13,6 +13,8 @@ export let longRunningTestsEnabled: boolean;
|
|||
suiteSetup(async function (this: Mocha.Context): Promise<void> {
|
||||
this.timeout(1 * 60 * 1000);
|
||||
|
||||
await vscode.extensions.getExtension('ms-azuretools.vscode-azureresourcegroups')?.activate();
|
||||
|
||||
await vscode.commands.executeCommand('azureResourceGroups.refresh'); // activate the extension before tests begin
|
||||
ext.outputChannel = new TestOutputChannel();
|
||||
|
||||
|
|
|
@ -29,11 +29,12 @@ async function main(): Promise<void> {
|
|||
extensionDevelopmentPath: repoRoot,
|
||||
launchArgs: [
|
||||
path.resolve(repoRoot, 'test', 'test.code-workspace'),
|
||||
'--disable-workspace-trust'
|
||||
'--disable-workspace-trust',
|
||||
|
||||
],
|
||||
extensionTestsPath: path.resolve(repoRoot, 'dist', 'test', 'index'),
|
||||
extensionTestsEnv: {
|
||||
DEBUGTELEMETRY: 'v',
|
||||
DEBUGTELEMETRY: process.env.DEBUGTELEMETRY,
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
|
|
|
@ -42,7 +42,6 @@ suite('wrapFunctionsInTelemetry', () => {
|
|||
// ignore error
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(wrapperContext?.telemetry));
|
||||
assert.strictEqual(wrapperContext?.telemetry.properties.result, 'Failed', 'Expected result to be "Failed"');
|
||||
});
|
||||
});
|
||||
|
@ -79,7 +78,6 @@ suite('wrapFunctionsInTelemetrySync', () => {
|
|||
// ignore error
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(wrapperContext?.telemetry));
|
||||
assert.strictEqual(wrapperContext?.telemetry.properties.result, 'Failed', 'Expected result to be "Failed"');
|
||||
});
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче