Feature/extensible azure resource explorer (#3504)

Extensible Azure Resource Explorer
This commit is contained in:
Vincent Feng
2018-12-18 15:44:08 +08:00
committed by GitHub
parent 33d5455b6f
commit 50e2251e74
62 changed files with 2924 additions and 1705 deletions

View File

@@ -17,12 +17,12 @@
"configuration": [
{
"type": "object",
"title": "%azure.config.title%",
"title": "%azure.resource.config.title%",
"properties": {
"azureResource.resourceFilter": {
"azure.resource.config.filter": {
"type": "array",
"default": null,
"description": "%azure.resourceFilter.description%"
"description": "%azure.resource.config.filter.description%"
}
}
},
@@ -61,39 +61,47 @@
"category": "Azure Accounts"
},
{
"command": "azureresource.refreshall",
"title": "%azureresource.refreshall%",
"command": "azure.resource.refreshall",
"title": "%azure.resource.refreshall.title%",
"icon": {
"dark": "resources/dark/refresh_inverse.svg",
"light": "resources/light/refresh.svg"
}
},
{
"command": "azureresource.refresh",
"title": "%azureresource.refresh%",
"command": "azure.resource.refresh",
"title": "%azure.resource.refresh.title%",
"icon": {
"dark": "resources/dark/refresh_inverse.svg",
"light": "resources/light/refresh.svg"
}
},
{
"command": "azureresource.signin",
"title": "%azureresource.signin%"
"command": "azure.resource.signin",
"title": "%azure.resource.signin.title%"
},
{
"command": "azureresource.connectsqldb",
"title": "%azureresource.connectsqldb%",
"command": "azure.resource.selectsubscriptions",
"title": "%azure.resource.selectsubscriptions.title%",
"icon": {
"dark": "resources/dark/filter_inverse.svg",
"light": "resources/light/filter.svg"
}
},
{
"command": "azure.resource.connectsqlserver",
"title": "%azure.resource.connectsqlserver.title%",
"icon": {
"dark": "resources/dark/connect_to_inverse.svg",
"light": "resources/light/connect_to.svg"
}
},
{
"command": "azureresource.selectsubscriptions",
"title": "%azureresource.selectsubscriptions%",
"command": "azure.resource.connectsqldb",
"title": "%azure.resource.connectsqldb.title%",
"icon": {
"dark": "resources/dark/filter_inverse.svg",
"light": "resources/light/filter.svg"
"dark": "resources/dark/connect_to_inverse.svg",
"light": "resources/light/connect_to.svg"
}
}
],
@@ -110,46 +118,47 @@
"azureResource": [
{
"id": "azureResourceExplorer",
"name": "%azure.resourceExplorer.title%"
"name": "%azure.resource.explorer.title%"
}
]
},
"menus": {
"view/title": [
{
"command": "azureresource.refreshall",
"command": "azure.resource.refreshall",
"when": "view == azureResourceExplorer",
"group": "navigation@1"
}
],
"view/item/context": [
{
"command": "azureresource.connectsqldb",
"when": "viewItem =~ /^azureResource\\.itemType\\.database(?:Server){0,1}$/",
"group": "1azureresource@1"
},
{
"command": "azureresource.connectsqldb",
"when": "viewItem =~ /^azureResource\\.itemType\\.database(?:Server){0,1}$/",
"command": "azure.resource.selectsubscriptions",
"when": "viewItem == azure.resource.itemType.account",
"group": "inline"
},
{
"command": "azureresource.selectsubscriptions",
"when": "viewItem == azureResource.itemType.account",
"command": "azure.resource.refresh",
"when": "viewItem =~ /^azure\\.resource\\.itemType\\.(?:account|subscription|databaseContainer|databaseServerContainer)$/",
"group": "inline"
},
{
"command": "azureresource.refresh",
"when": "viewItem =~ /^azureResource\\.itemType\\.(?:account|subscription|databaseContainer|databaseServerContainer)$/",
"command": "azure.resource.connectsqlserver",
"when": "viewItem == azure.resource.itemType.databaseServer",
"group": "inline"
},
{
"command": "azure.resource.connectsqldb",
"when": "viewItem == azure.resource.itemType.database",
"group": "inline"
}
]
}
},
"hasAzureResourceProviders": true
},
"dependencies": {
"request": "2.88.0",
"azure-arm-resource": "^7.0.0",
"azure-arm-sql": "^5.0.1",
"request": "2.88.0",
"vscode-nls": "^4.0.0"
},
"devDependencies": {
@@ -157,6 +166,7 @@
"@types/node": "^8.0.24",
"mocha": "^5.2.0",
"should": "^13.2.1",
"vscode": "^1.1.26",
"typemoq": "^2.1.0"
}
}
}

View File

@@ -1,16 +1,20 @@
{
"azure.displayName": "Azure (Core)",
"azure.description": "Browse and work with Azure resources",
"azure.config.title": "Azure Resource Configuration",
"azure.resourceFilter.description": "The resource filter, each element is an account id, a subscription id and name separated by a slash",
"azureresource.refreshall": "Refresh All",
"azureresource.refresh": "Refresh",
"azureresource.signin": "Sign In",
"azureresource.connectsqldb": "Connect",
"azureresource.selectsubscriptions": "Select Subscriptions",
"azure.title": "Azure",
"azure.resourceExplorer.title": "Resource Explorer",
"azure.resource.config.title": "Azure Resource Configuration",
"azure.resource.config.filter.description": "The resource filter, each element is an account id, a subscription id and name separated by a slash",
"azure.resource.explorer.title": "Resource Explorer",
"azure.resource.refreshall.title": "Refresh All",
"azure.resource.refresh.title": "Refresh",
"azure.resource.signin.title": "Sign In",
"azure.resource.selectsubscriptions.title": "Select Subscriptions",
"azure.resource.connectsqlserver.title": "Connect",
"azure.resource.connectsqldb.title": "Connect",
"accounts.clearTokenCache": "Clear Azure Account Token Cache",
"config.enablePublicCloudDescription": "Should Azure public cloud integration be enabled",
"config.enableUsGovCloudDescription": "Should US Government Azure cloud (Fairfax) integration be enabled",
"config.enableChinaCloudDescription": "Should Azure China integration be enabled",

View File

@@ -0,0 +1,28 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TreeDataProvider, TreeItem } from 'vscode';
import { DataProvider, Account } from 'sqlops';
export namespace azureResource {
export interface IAzureResourceProvider extends DataProvider {
getTreeDataProvider(): IAzureResourceTreeDataProvider;
}
export interface IAzureResourceTreeDataProvider extends TreeDataProvider<IAzureResourceNode> {
}
export interface IAzureResourceNode {
readonly account: Account;
readonly subscription: AzureResourceSubscription;
readonly tenantId: string;
readonly treeItem: TreeItem;
}
export interface AzureResourceSubscription {
id: string;
name: string;
}
}

View File

@@ -6,35 +6,48 @@
'use strict';
import { window, QuickPickItem } from 'vscode';
import * as sqlops from 'sqlops';
import { generateGuid } from './utils';
import { ApiWrapper } from '../apiWrapper';
import { TreeNode } from '../treeNodes';
import { AzureResource } from 'sqlops';
import { TokenCredentials } from 'ms-rest';
import { AppContext } from '../appContext';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { azureResource } from './azure-resource';
import { TreeNode } from './treeNode';
import { AzureResourceCredentialError } from './errors';
import { AzureResourceTreeProvider } from './tree/treeProvider';
import { AzureResourceDatabaseServerTreeNode } from './tree/databaseServerTreeNode';
import { AzureResourceDatabaseTreeNode } from './tree/databaseTreeNode';
import { AzureResourceAccountTreeNode } from './tree/accountTreeNode';
import { AzureResourceServicePool } from './servicePool';
import { AzureResourceSubscription } from './models';
import { IAzureResourceSubscriptionService, IAzureResourceSubscriptionFilterService } from '../azureResource/interfaces';
import { AzureResourceServiceNames } from './constants';
export function registerAzureResourceCommands(apiWrapper: ApiWrapper, tree: AzureResourceTreeProvider): void {
apiWrapper.registerCommand('azureresource.selectsubscriptions', async (node?: TreeNode) => {
export function registerAzureResourceCommands(appContext: AppContext, tree: AzureResourceTreeProvider): void {
appContext.apiWrapper.registerCommand('azure.resource.selectsubscriptions', async (node?: TreeNode) => {
if (!(node instanceof AzureResourceAccountTreeNode)) {
return;
}
const subscriptionService = appContext.getService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService);
const subscriptionFilterService = appContext.getService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService);
const accountNode = node as AzureResourceAccountTreeNode;
const servicePool = AzureResourceServicePool.getInstance();
const subscriptions = (await accountNode.getCachedSubscriptions()) || <azureResource.AzureResourceSubscription[]>[];
if (subscriptions.length === 0) {
try {
const tokens = await this.servicePool.apiWrapper.getSecurityToken(this.account, AzureResource.ResourceManagement);
let subscriptions = await accountNode.getCachedSubscriptions();
if (!subscriptions || subscriptions.length === 0) {
const credentials = await servicePool.credentialService.getCredentials(accountNode.account, sqlops.AzureResource.ResourceManagement);
subscriptions = await servicePool.subscriptionService.getSubscriptions(accountNode.account, credentials);
for (const tenant of this.account.properties.tenants) {
const token = tokens[tenant.id].token;
const tokenType = tokens[tenant.id].tokenType;
subscriptions.push(...await subscriptionService.getSubscriptions(accountNode.account, new TokenCredentials(token, tokenType)));
}
} catch (error) {
throw new AzureResourceCredentialError(localize('azure.resource.selectsubscriptions.credentialError', 'Failed to get credential for account {0}. Please refresh the account.', this.account.key.accountId), error);
}
}
const selectedSubscriptions = (await servicePool.subscriptionFilterService.getSelectedSubscriptions(accountNode.account)) || <AzureResourceSubscription[]>[];
let selectedSubscriptions = (await subscriptionFilterService.getSelectedSubscriptions(accountNode.account)) || <azureResource.AzureResourceSubscription[]>[];
const selectedSubscriptionIds: string[] = [];
if (selectedSubscriptions.length > 0) {
selectedSubscriptionIds.push(...selectedSubscriptions.map((subscription) => subscription.id));
@@ -43,11 +56,11 @@ export function registerAzureResourceCommands(apiWrapper: ApiWrapper, tree: Azur
selectedSubscriptionIds.push(...subscriptions.map((subscription) => subscription.id));
}
interface SubscriptionQuickPickItem extends QuickPickItem {
subscription: AzureResourceSubscription;
interface AzureResourceSubscriptionQuickPickItem extends QuickPickItem {
subscription: azureResource.AzureResourceSubscription;
}
const subscriptionItems: SubscriptionQuickPickItem[] = subscriptions.map((subscription) => {
const subscriptionQuickPickItems: AzureResourceSubscriptionQuickPickItem[] = subscriptions.map((subscription) => {
return {
label: subscription.name,
picked: selectedSubscriptionIds.indexOf(subscription.id) !== -1,
@@ -55,66 +68,22 @@ export function registerAzureResourceCommands(apiWrapper: ApiWrapper, tree: Azur
};
});
const pickedSubscriptionItems = (await window.showQuickPick(subscriptionItems, { canPickMany: true }));
if (pickedSubscriptionItems && pickedSubscriptionItems.length > 0) {
const selectedSubscriptionQuickPickItems = (await window.showQuickPick(subscriptionQuickPickItems, { canPickMany: true }));
if (selectedSubscriptionQuickPickItems && selectedSubscriptionQuickPickItems.length > 0) {
tree.refresh(node, false);
const pickedSubscriptions = pickedSubscriptionItems.map((subscriptionItem) => subscriptionItem.subscription);
await servicePool.subscriptionFilterService.saveSelectedSubscriptions(accountNode.account, pickedSubscriptions);
selectedSubscriptions = selectedSubscriptionQuickPickItems.map((subscriptionItem) => subscriptionItem.subscription);
await subscriptionFilterService.saveSelectedSubscriptions(accountNode.account, selectedSubscriptions);
}
});
apiWrapper.registerCommand('azureresource.refreshall', () => tree.notifyNodeChanged(undefined));
appContext.apiWrapper.registerCommand('azure.resource.refreshall', () => tree.notifyNodeChanged(undefined));
apiWrapper.registerCommand('azureresource.refresh', async (node?: TreeNode) => {
appContext.apiWrapper.registerCommand('azure.resource.refresh', async (node?: TreeNode) => {
tree.refresh(node, true);
});
apiWrapper.registerCommand('azureresource.connectsqldb', async (node?: TreeNode) => {
let connectionProfile: sqlops.IConnectionProfile = {
id: generateGuid(),
connectionName: undefined,
serverName: undefined,
databaseName: undefined,
userName: undefined,
password: '',
authenticationType: undefined,
savePassword: true,
groupFullName: '',
groupId: '',
providerName: undefined,
saveProfile: true,
options: {
}
};
if (node instanceof AzureResourceDatabaseServerTreeNode) {
let databaseServer = node.databaseServer;
connectionProfile.connectionName = `connection to '${databaseServer.defaultDatabaseName}' on '${databaseServer.fullName}'`;
connectionProfile.serverName = databaseServer.fullName;
connectionProfile.databaseName = databaseServer.defaultDatabaseName;
connectionProfile.userName = databaseServer.loginName;
connectionProfile.authenticationType = 'SqlLogin';
connectionProfile.providerName = 'MSSQL';
}
if (node instanceof AzureResourceDatabaseTreeNode) {
let database = node.database;
connectionProfile.connectionName = `connection to '${database.name}' on '${database.serverFullName}'`;
connectionProfile.serverName = database.serverFullName;
connectionProfile.databaseName = database.name;
connectionProfile.userName = database.loginName;
connectionProfile.authenticationType = 'SqlLogin';
connectionProfile.providerName = 'MSSQL';
}
const conn = await apiWrapper.openConnectionDialog(undefined, connectionProfile, { saveConnection: true, showDashboard: true });
if (conn) {
apiWrapper.executeCommand('workbench.view.connections');
}
});
apiWrapper.registerCommand('azureresource.signin', async (node?: TreeNode) => {
apiWrapper.executeCommand('sql.action.accounts.manageLinkedAccount');
appContext.apiWrapper.registerCommand('azure.resource.signin', async (node?: TreeNode) => {
appContext.apiWrapper.executeCommand('sql.action.accounts.manageLinkedAccount');
});
}

View File

@@ -6,11 +6,19 @@
'use strict';
export enum AzureResourceItemType {
account = 'azureResource.itemType.account',
subscription = 'azureResource.itemType.subscription',
databaseContainer = 'azureResource.itemType.databaseContainer',
database = 'azureResource.itemType.database',
databaseServerContainer = 'azureResource.itemType.databaseServerContainer',
databaseServer = 'azureResource.itemType.databaseServer',
message = 'azureResource.itemType.message'
account = 'azure.resource.itemType.account',
subscription = 'azure.resource.itemType.subscription',
databaseContainer = 'azure.resource.itemType.databaseContainer',
database = 'azure.resource.itemType.database',
databaseServerContainer = 'azure.resource.itemType.databaseServerContainer',
databaseServer = 'azure.resource.itemType.databaseServer',
message = 'azure.resource.itemType.message'
}
export enum AzureResourceServiceNames {
cacheService = 'AzureResourceCacheService',
accountService = 'AzureResourceAccountService',
subscriptionService = 'AzureResourceSubscriptionService',
subscriptionFilterService = 'AzureResourceSubscriptionFilterService',
tenantService = 'AzureResourceTenantService'
}

View File

@@ -8,7 +8,7 @@
export class AzureResourceCredentialError extends Error {
constructor(
message: string,
public innerError: Error
public readonly innerError: Error
) {
super(message);
}

View File

@@ -6,49 +6,40 @@
'use strict';
import { ServiceClientCredentials } from 'ms-rest';
import * as sqlops from 'sqlops';
import { Account, DidChangeAccountsParams } from 'sqlops';
import { Event } from 'vscode';
import { AzureResourceSubscription, AzureResourceDatabaseServer, AzureResourceDatabase } from './models';
import { azureResource } from './azure-resource';
export interface IAzureResourceAccountService {
getAccounts(): Promise<sqlops.Account[]>;
getAccounts(): Promise<Account[]>;
readonly onDidChangeAccounts: Event<sqlops.DidChangeAccountsParams>;
}
export interface IAzureResourceCredentialService {
getCredentials(account: sqlops.Account, resource: sqlops.AzureResource): Promise<ServiceClientCredentials[]>;
readonly onDidChangeAccounts: Event<DidChangeAccountsParams>;
}
export interface IAzureResourceSubscriptionService {
getSubscriptions(account: sqlops.Account, credentials: ServiceClientCredentials[]): Promise<AzureResourceSubscription[]>;
getSubscriptions(account: Account, credential: ServiceClientCredentials): Promise<azureResource.AzureResourceSubscription[]>;
}
export interface IAzureResourceSubscriptionFilterService {
getSelectedSubscriptions(account: sqlops.Account): Promise<AzureResourceSubscription[]>;
getSelectedSubscriptions(account: Account): Promise<azureResource.AzureResourceSubscription[]>;
saveSelectedSubscriptions(account: sqlops.Account, selectedSubscriptions: AzureResourceSubscription[]): Promise<void>;
}
export interface IAzureResourceDatabaseServerService {
getDatabaseServers(subscription: AzureResourceSubscription, credentials: ServiceClientCredentials[]): Promise<AzureResourceDatabaseServer[]>;
}
export interface IAzureResourceDatabaseService {
getDatabases(subscription: AzureResourceSubscription, credentials: ServiceClientCredentials[]): Promise<AzureResourceDatabase[]>;
saveSelectedSubscriptions(account: Account, selectedSubscriptions: azureResource.AzureResourceSubscription[]): Promise<void>;
}
export interface IAzureResourceCacheService {
generateKey(id: string): string;
get<T>(key: string): T | undefined;
update<T>(key: string, value: T): void;
}
export interface IAzureResourceContextService {
getAbsolutePath(relativePath: string): string;
executeCommand(commandId: string, ...args: any[]): void;
showErrorMessage(errorMessage: string): void;
export interface IAzureResourceTenantService {
getTenantId(subscription: azureResource.AzureResourceSubscription): Promise<string>;
}
export interface IAzureResourceNodeWithProviderId {
resourceProviderId: string;
resourceNode: azureResource.IAzureResourceNode;
}

View File

@@ -7,9 +7,9 @@
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import { NodeInfo } from 'sqlops';
import { TreeNode } from '../../treeNodes';
import { AzureResourceItemType } from '../constants';
import { TreeNode } from './treeNode';
import { AzureResourceItemType } from './constants';
export class AzureResourceMessageTreeNode extends TreeNode {
public constructor(

View File

@@ -0,0 +1,53 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { IConnectionProfile } from 'sqlops';
import { AppContext } from '../../../appContext';
import { TreeNode } from '../../treeNode';
import { generateGuid } from '../../utils';
import { AzureResourceItemType } from '../../constants';
import { IAzureResourceDatabaseNode } from './interfaces';
import { AzureResourceResourceTreeNode } from '../../resourceTreeNode';
export function registerAzureResourceDatabaseCommands(appContext: AppContext): void {
appContext.apiWrapper.registerCommand('azure.resource.connectsqldb', async (node?: TreeNode) => {
if (!node)
{
return;
}
const treeItem = await node.getTreeItem();
if (treeItem.contextValue !== AzureResourceItemType.database) {
return;
}
const resourceNode = (node as AzureResourceResourceTreeNode).resourceNodeWithProviderId.resourceNode;
const database = (resourceNode as IAzureResourceDatabaseNode).database;
let connectionProfile: IConnectionProfile = {
id: generateGuid(),
connectionName: undefined,
serverName: database.serverFullName,
databaseName: database.name,
userName: database.loginName,
password: '',
authenticationType: 'SqlLogin',
savePassword: true,
groupFullName: '',
groupId: '',
providerName: 'MSSQL',
saveProfile: true,
options: {}
};
const conn = await appContext.apiWrapper.openConnectionDialog(undefined, connectionProfile, { saveConnection: true, showDashboard: true });
if (conn) {
appContext.apiWrapper.executeCommand('workbench.view.connections');
}
});
}

View File

@@ -0,0 +1,37 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ExtensionContext } from 'vscode';
import { ApiWrapper } from '../../../apiWrapper';
import { azureResource } from '../../azure-resource';
import { IAzureResourceDatabaseService } from './interfaces';
import { AzureResourceDatabaseTreeDataProvider } from './databaseTreeDataProvider';
export class AzureResourceDatabaseProvider implements azureResource.IAzureResourceProvider {
public constructor(
databaseService: IAzureResourceDatabaseService,
apiWrapper: ApiWrapper,
extensionContext: ExtensionContext
) {
this._databaseService = databaseService;
this._apiWrapper = apiWrapper;
this._extensionContext = extensionContext;
}
public getTreeDataProvider(): azureResource.IAzureResourceTreeDataProvider {
return new AzureResourceDatabaseTreeDataProvider(this._databaseService, this._apiWrapper, this._extensionContext);
}
public get providerId(): string {
return 'azure.resource.providers.database';
}
private _databaseService: IAzureResourceDatabaseService = undefined;
private _apiWrapper: ApiWrapper = undefined;
private _extensionContext: ExtensionContext = undefined;
}

View File

@@ -0,0 +1,41 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ServiceClientCredentials } from 'ms-rest';
import { SqlManagementClient } from 'azure-arm-sql';
import { azureResource } from '../../azure-resource';
import { IAzureResourceDatabaseService } from './interfaces';
import { AzureResourceDatabase } from './models';
export class AzureResourceDatabaseService implements IAzureResourceDatabaseService {
public async getDatabases(subscription: azureResource.AzureResourceSubscription, credential: ServiceClientCredentials): Promise<AzureResourceDatabase[]> {
const databases: AzureResourceDatabase[] = [];
const sqlManagementClient = new SqlManagementClient(credential, subscription.id);
const svrs = await sqlManagementClient.servers.list();
for (const svr of svrs) {
// Extract resource group name from svr.id
const svrIdRegExp = new RegExp(`\/subscriptions\/${subscription.id}\/resourceGroups\/(.+)\/providers\/Microsoft\.Sql\/servers\/${svr.name}`);
if (!svrIdRegExp.test(svr.id)) {
continue;
}
const founds = svrIdRegExp.exec(svr.id);
const resouceGroup = founds[1];
const dbs = await sqlManagementClient.databases.listByServer(resouceGroup, svr.name);
dbs.forEach((db) => databases.push({
name: db.name,
serverName: svr.name,
serverFullName: svr.fullyQualifiedDomainName,
loginName: svr.administratorLogin
}));
}
return databases;
}
}

View File

@@ -0,0 +1,87 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { AzureResource } from 'sqlops';
import { TreeItem, TreeItemCollapsibleState, ExtensionContext } from 'vscode';
import { TokenCredentials } from 'ms-rest';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { azureResource } from '../../azure-resource';
import { IAzureResourceDatabaseService, IAzureResourceDatabaseNode } from './interfaces';
import { AzureResourceDatabase } from './models';
import { AzureResourceItemType } from '../../../azureResource/constants';
import { ApiWrapper } from '../../../apiWrapper';
export class AzureResourceDatabaseTreeDataProvider implements azureResource.IAzureResourceTreeDataProvider {
public constructor(
databaseService: IAzureResourceDatabaseService,
apiWrapper: ApiWrapper,
extensionContext: ExtensionContext
) {
this._databaseService = databaseService;
this._apiWrapper = apiWrapper;
this._extensionContext = extensionContext;
}
public getTreeItem(element: azureResource.IAzureResourceNode): TreeItem | Thenable<TreeItem> {
return element.treeItem;
}
public async getChildren(element?: azureResource.IAzureResourceNode): Promise<azureResource.IAzureResourceNode[]> {
if (!element) {
return [this.createContainerNode()];
}
const tokens = await this._apiWrapper.getSecurityToken(element.account, AzureResource.ResourceManagement);
const credential = new TokenCredentials(tokens[element.tenantId].token, tokens[element.tenantId].tokenType);
const databases: AzureResourceDatabase[] = (await this._databaseService.getDatabases(element.subscription, credential)) || <AzureResourceDatabase[]>[];
return databases.map((database) => <IAzureResourceDatabaseNode>{
account: element.account,
subscription: element.subscription,
tenantId: element.tenantId,
database: database,
treeItem: {
id: `databaseServer_${database.serverFullName}.database_${database.name}`,
label: `${database.name} (${database.serverName})`,
iconPath: {
dark: this._extensionContext.asAbsolutePath('resources/dark/sql_database_inverse.svg'),
light: this._extensionContext.asAbsolutePath('resources/light/sql_database.svg')
},
collapsibleState: TreeItemCollapsibleState.None,
contextValue: AzureResourceItemType.database
}
});
}
private createContainerNode(): azureResource.IAzureResourceNode {
return {
account: undefined,
subscription: undefined,
tenantId: undefined,
treeItem: {
id: AzureResourceDatabaseTreeDataProvider.containerId,
label: AzureResourceDatabaseTreeDataProvider.containerLabel,
iconPath: {
dark: this._extensionContext.asAbsolutePath('resources/dark/folder_inverse.svg'),
light: this._extensionContext.asAbsolutePath('resources/light/folder.svg')
},
collapsibleState: TreeItemCollapsibleState.Collapsed,
contextValue: AzureResourceItemType.databaseContainer
}
};
}
private _databaseService: IAzureResourceDatabaseService = undefined;
private _apiWrapper: ApiWrapper = undefined;
private _extensionContext: ExtensionContext = undefined;
private static readonly containerId = 'azure.resource.providers.database.treeDataProvider.databaseContainer';
private static readonly containerLabel = localize('azure.resource.providers.database.treeDataProvider.databaseContainerLabel', 'SQL Databases');
}

View File

@@ -0,0 +1,19 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ServiceClientCredentials } from 'ms-rest';
import { azureResource } from '../../azure-resource';
import { AzureResourceDatabase } from './models';
export interface IAzureResourceDatabaseService {
getDatabases(subscription: azureResource.AzureResourceSubscription, credential: ServiceClientCredentials): Promise<AzureResourceDatabase[]>;
}
export interface IAzureResourceDatabaseNode extends azureResource.IAzureResourceNode {
readonly database: AzureResourceDatabase;
}

View File

@@ -0,0 +1,13 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
export interface AzureResourceDatabase {
name: string;
serverName: string;
serverFullName: string;
loginName: string;
}

View File

@@ -0,0 +1,53 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { IConnectionProfile } from 'sqlops';
import { AppContext } from '../../../appContext';
import { TreeNode } from '../../treeNode';
import { generateGuid } from '../../utils';
import { AzureResourceItemType } from '../../constants';
import { IAzureResourceDatabaseServerNode } from './interfaces';
import { AzureResourceResourceTreeNode } from '../../resourceTreeNode';
export function registerAzureResourceDatabaseServerCommands(appContext: AppContext): void {
appContext.apiWrapper.registerCommand('azure.resource.connectsqlserver', async (node?: TreeNode) => {
if (!node)
{
return;
}
const treeItem = await node.getTreeItem();
if (treeItem.contextValue !== AzureResourceItemType.databaseServer) {
return;
}
const resourceNode = (node as AzureResourceResourceTreeNode).resourceNodeWithProviderId.resourceNode;
const databaseServer = (resourceNode as IAzureResourceDatabaseServerNode).databaseServer;
let connectionProfile: IConnectionProfile = {
id: generateGuid(),
connectionName: undefined,
serverName: databaseServer.fullName,
databaseName: databaseServer.defaultDatabaseName,
userName: databaseServer.loginName,
password: '',
authenticationType: 'SqlLogin',
savePassword: true,
groupFullName: '',
groupId: '',
providerName: 'MSSQL',
saveProfile: true,
options: {}
};
const conn = await appContext.apiWrapper.openConnectionDialog(undefined, connectionProfile, { saveConnection: true, showDashboard: true });
if (conn) {
appContext.apiWrapper.executeCommand('workbench.view.connections');
}
});
}

View File

@@ -0,0 +1,37 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ExtensionContext } from 'vscode';
import { ApiWrapper } from '../../../apiWrapper';
import { azureResource } from '../../azure-resource';
import { IAzureResourceDatabaseServerService } from './interfaces';
import { AzureResourceDatabaseServerTreeDataProvider } from './databaseServerTreeDataProvider';
export class AzureResourceDatabaseServerProvider implements azureResource.IAzureResourceProvider {
public constructor(
databaseServerService: IAzureResourceDatabaseServerService,
apiWrapper: ApiWrapper,
extensionContext: ExtensionContext
) {
this._databaseServerService = databaseServerService;
this._apiWrapper = apiWrapper;
this._extensionContext = extensionContext;
}
public getTreeDataProvider(): azureResource.IAzureResourceTreeDataProvider {
return new AzureResourceDatabaseServerTreeDataProvider(this._databaseServerService, this._apiWrapper, this._extensionContext);
}
public get providerId(): string {
return 'azure.resource.providers.databaseServer';
}
private _databaseServerService: IAzureResourceDatabaseServerService = undefined;
private _apiWrapper: ApiWrapper = undefined;
private _extensionContext: ExtensionContext = undefined;
}

View File

@@ -0,0 +1,31 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ServiceClientCredentials } from 'ms-rest';
import { SqlManagementClient } from 'azure-arm-sql';
import { azureResource } from '../../azure-resource';
import { IAzureResourceDatabaseServerService } from './interfaces';
import { AzureResourceDatabaseServer } from './models';
export class AzureResourceDatabaseServerService implements IAzureResourceDatabaseServerService {
public async getDatabaseServers(subscription: azureResource.AzureResourceSubscription, credential: ServiceClientCredentials): Promise<AzureResourceDatabaseServer[]> {
const databaseServers: AzureResourceDatabaseServer[] = [];
const sqlManagementClient = new SqlManagementClient(credential, subscription.id);
const svrs = await sqlManagementClient.servers.list();
svrs.forEach((svr) => databaseServers.push({
name: svr.name,
fullName: svr.fullyQualifiedDomainName,
loginName: svr.administratorLogin,
defaultDatabaseName: 'master'
}));
return databaseServers;
}
}

View File

@@ -0,0 +1,87 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { AzureResource } from 'sqlops';
import { TreeItem, TreeItemCollapsibleState, ExtensionContext } from 'vscode';
import { TokenCredentials } from 'ms-rest';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { azureResource } from '../../azure-resource';
import { IAzureResourceDatabaseServerService, IAzureResourceDatabaseServerNode } from './interfaces';
import { AzureResourceDatabaseServer } from './models';
import { AzureResourceItemType } from '../../../azureResource/constants';
import { ApiWrapper } from '../../../apiWrapper';
export class AzureResourceDatabaseServerTreeDataProvider implements azureResource.IAzureResourceTreeDataProvider {
public constructor(
databaseServerService: IAzureResourceDatabaseServerService,
apiWrapper: ApiWrapper,
extensionContext: ExtensionContext
) {
this._databaseServerService = databaseServerService;
this._apiWrapper = apiWrapper;
this._extensionContext = extensionContext;
}
public getTreeItem(element: azureResource.IAzureResourceNode): TreeItem | Thenable<TreeItem> {
return element.treeItem;
}
public async getChildren(element?: azureResource.IAzureResourceNode): Promise<azureResource.IAzureResourceNode[]> {
if (!element) {
return [this.createContainerNode()];
}
const tokens = await this._apiWrapper.getSecurityToken(element.account, AzureResource.ResourceManagement);
const credential = new TokenCredentials(tokens[element.tenantId].token, tokens[element.tenantId].tokenType);
const databaseServers: AzureResourceDatabaseServer[] = (await this._databaseServerService.getDatabaseServers(element.subscription, credential)) || <AzureResourceDatabaseServer[]>[];
return databaseServers.map((databaseServer) => <IAzureResourceDatabaseServerNode>{
account: element.account,
subscription: element.subscription,
tenantId: element.tenantId,
databaseServer: databaseServer,
treeItem: {
id: `databaseServer_${databaseServer.name}`,
label: databaseServer.name,
iconPath: {
dark: this._extensionContext.asAbsolutePath('resources/dark/sql_server_inverse.svg'),
light: this._extensionContext.asAbsolutePath('resources/light/sql_server.svg')
},
collapsibleState: TreeItemCollapsibleState.None,
contextValue: AzureResourceItemType.databaseServer
}
});
}
private createContainerNode(): azureResource.IAzureResourceNode {
return {
account: undefined,
subscription: undefined,
tenantId: undefined,
treeItem: {
id: AzureResourceDatabaseServerTreeDataProvider.containerId,
label: AzureResourceDatabaseServerTreeDataProvider.containerLabel,
iconPath: {
dark: this._extensionContext.asAbsolutePath('resources/dark/folder_inverse.svg'),
light: this._extensionContext.asAbsolutePath('resources/light/folder.svg')
},
collapsibleState: TreeItemCollapsibleState.Collapsed,
contextValue: AzureResourceItemType.databaseServerContainer
}
};
}
private _databaseServerService: IAzureResourceDatabaseServerService = undefined;
private _apiWrapper: ApiWrapper = undefined;
private _extensionContext: ExtensionContext = undefined;
private static readonly containerId = 'azure.resource.providers.databaseServer.treeDataProvider.databaseServerContainer';
private static readonly containerLabel = localize('azure.resource.providers.databaseServer.treeDataProvider.databaseServerContainerLabel', 'SQL Servers');
}

View File

@@ -0,0 +1,19 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ServiceClientCredentials } from 'ms-rest';
import { azureResource } from '../../azure-resource';
import { AzureResourceDatabaseServer } from './models';
export interface IAzureResourceDatabaseServerService {
getDatabaseServers(subscription: azureResource.AzureResourceSubscription, credentials: ServiceClientCredentials): Promise<AzureResourceDatabaseServer[]>;
}
export interface IAzureResourceDatabaseServerNode extends azureResource.IAzureResourceNode {
readonly databaseServer: AzureResourceDatabaseServer;
}

View File

@@ -5,21 +5,9 @@
'use strict';
export interface AzureResourceSubscription {
id: string;
name: string;
}
export interface AzureResourceDatabaseServer {
name: string;
fullName: string;
loginName: string;
defaultDatabaseName: string;
}
export interface AzureResourceDatabase {
name: string;
serverName: string;
serverFullName: string;
loginName: string;
}
}

View File

@@ -0,0 +1,129 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { extensions, TreeItem } from 'vscode';
import { Account } from 'sqlops';
import { azureResource } from './azure-resource';
import { IAzureResourceNodeWithProviderId } from './interfaces';
export class AzureResourceService {
private constructor() {
}
public static getInstance(): AzureResourceService {
return AzureResourceService._instance;
}
public async listResourceProviderIds(): Promise<string[]> {
await this.ensureResourceProvidersRegistered();
return Object.keys(this._resourceProviders);
}
public registerResourceProvider(resourceProvider: azureResource.IAzureResourceProvider): void {
this.doRegisterResourceProvider(resourceProvider);
}
public clearResourceProviders(): void {
this._resourceProviders = {};
this._treeDataProviders = {};
this._areResourceProvidersLoaded = false;
}
public async getRootChildren(resourceProviderId: string, account: Account, subscription: azureResource.AzureResourceSubscription, tenatId: string): Promise<IAzureResourceNodeWithProviderId[]> {
await this.ensureResourceProvidersRegistered();
if (!(resourceProviderId in this._resourceProviders)) {
throw new Error(`Azure resource provider doesn't exist. Id: ${resourceProviderId}`);
}
const treeDataProvider = this._treeDataProviders[resourceProviderId];
const children = await treeDataProvider.getChildren();
return children.map((child) => <IAzureResourceNodeWithProviderId>{
resourceProviderId: resourceProviderId,
resourceNode: <azureResource.IAzureResourceNode>{
account: account,
subscription: subscription,
tenantId: tenatId,
treeItem: child.treeItem
}
});
}
public async getChildren(resourceProviderId: string, element: azureResource.IAzureResourceNode): Promise<IAzureResourceNodeWithProviderId[]> {
await this.ensureResourceProvidersRegistered();
if (!(resourceProviderId in this._resourceProviders)) {
throw new Error(`Azure resource provider doesn't exist. Id: ${resourceProviderId}`);
}
const treeDataProvider = this._treeDataProviders[resourceProviderId];
const children = await treeDataProvider.getChildren(element);
return children.map((child) => <IAzureResourceNodeWithProviderId>{
resourceProviderId: resourceProviderId,
resourceNode: child
});
}
public async getTreeItem(resourceProviderId: string, element?: azureResource.IAzureResourceNode): Promise<TreeItem> {
await this.ensureResourceProvidersRegistered();
if (!(resourceProviderId in this._resourceProviders)) {
throw new Error(`Azure resource provider doesn't exist. Id: ${resourceProviderId}`);
}
const treeDataProvider = this._treeDataProviders[resourceProviderId];
return treeDataProvider.getTreeItem(element);
}
public get areResourceProvidersLoaded(): boolean {
return this._areResourceProvidersLoaded;
}
public set areResourceProvidersLoaded(value: boolean) {
this._areResourceProvidersLoaded = value;
}
private async ensureResourceProvidersRegistered(): Promise<void> {
if (this._areResourceProvidersLoaded) {
return;
}
for (const extension of extensions.all) {
const contributes = extension.packageJSON && extension.packageJSON.contributes;
if (!contributes) {
continue;
}
if (contributes['hasAzureResourceProviders']) {
await extension.activate();
if (extension.exports && extension.exports.provideResources) {
for (const resourceProvider of <azureResource.IAzureResourceProvider[]>extension.exports.provideResources()) {
this.doRegisterResourceProvider(resourceProvider);
}
}
}
}
this._areResourceProvidersLoaded = true;
}
private doRegisterResourceProvider(resourceProvider: azureResource.IAzureResourceProvider): void {
this._resourceProviders[resourceProvider.providerId] = resourceProvider;
this._treeDataProviders[resourceProvider.providerId] = resourceProvider.getTreeDataProvider();
}
private _areResourceProvidersLoaded: boolean = false;
private _resourceProviders: { [resourceProviderId: string]: azureResource.IAzureResourceProvider } = {};
private _treeDataProviders: { [resourceProviderId: string]: azureResource.IAzureResourceTreeDataProvider } = {};
private static readonly _instance = new AzureResourceService();
}

View File

@@ -0,0 +1,79 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { NodeInfo } from 'sqlops';
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { TreeNode } from './treeNode';
import { AzureResourceService } from './resourceService';
import { IAzureResourceNodeWithProviderId } from './interfaces';
import { AzureResourceMessageTreeNode } from './messageTreeNode';
import { AzureResourceErrorMessageUtil } from './utils';
export class AzureResourceResourceTreeNode extends TreeNode {
public constructor(
public readonly resourceNodeWithProviderId: IAzureResourceNodeWithProviderId,
parent: TreeNode
) {
super();
this.parent = parent;
}
public async getChildren(): Promise<TreeNode[]> {
// It is a leaf node.
if (this.resourceNodeWithProviderId.resourceNode.treeItem.collapsibleState === TreeItemCollapsibleState.None) {
return <TreeNode[]>[];
}
try {
const children = await this._resourceService.getChildren(this.resourceNodeWithProviderId.resourceProviderId, this.resourceNodeWithProviderId.resourceNode);
if (children.length === 0) {
return [AzureResourceMessageTreeNode.create(AzureResourceResourceTreeNode.noResourcesLabel, this)];
} else {
return children.map((child) => {
// To make tree node's id unique, otherwise, treeModel.js would complain 'item already registered'
child.resourceNode.treeItem.id = `${this.resourceNodeWithProviderId.resourceNode.treeItem.id}.${child.resourceNode.treeItem.id}`;
return new AzureResourceResourceTreeNode(child, this);
});
}
} catch (error) {
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), this)];
}
}
public getTreeItem(): TreeItem | Promise<TreeItem> {
return this._resourceService.getTreeItem(this.resourceNodeWithProviderId.resourceProviderId, this.resourceNodeWithProviderId.resourceNode);
}
public getNodeInfo(): NodeInfo {
const treeItem = this.resourceNodeWithProviderId.resourceNode.treeItem;
return {
label: treeItem.label,
isLeaf: treeItem.collapsibleState === TreeItemCollapsibleState.None ? true : false,
errorMessage: undefined,
metadata: undefined,
nodePath: this.generateNodePath(),
nodeStatus: undefined,
nodeType: treeItem.contextValue,
nodeSubType: undefined,
iconType: treeItem.contextValue
};
}
public get nodePathValue(): string {
return this.resourceNodeWithProviderId.resourceNode.treeItem.id;
}
private _resourceService = AzureResourceService.getInstance();
private static readonly noResourcesLabel = localize('azure.resource.resourceTreeNode.noResourcesLabel', 'No Resources found.');
}

View File

@@ -1,35 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import {
IAzureResourceAccountService,
IAzureResourceCredentialService,
IAzureResourceSubscriptionService,
IAzureResourceSubscriptionFilterService,
IAzureResourceDatabaseService,
IAzureResourceDatabaseServerService,
IAzureResourceCacheService,
IAzureResourceContextService } from './interfaces';
export class AzureResourceServicePool {
private constructor() { }
public static getInstance(): AzureResourceServicePool {
return AzureResourceServicePool._instance;
}
public contextService: IAzureResourceContextService;
public cacheService: IAzureResourceCacheService;
public accountService: IAzureResourceAccountService;
public credentialService: IAzureResourceCredentialService;
public subscriptionService: IAzureResourceSubscriptionService;
public subscriptionFilterService: IAzureResourceSubscriptionFilterService;
public databaseService: IAzureResourceDatabaseService;
public databaseServerService: IAzureResourceDatabaseServerService;
private static readonly _instance = new AzureResourceServicePool();
}

View File

@@ -5,21 +5,30 @@
'use strict';
import { ExtensionContext } from "vscode";
import { ExtensionContext } from 'vscode';
import { IAzureResourceCacheService } from "../interfaces";
import { IAzureResourceCacheService } from '../interfaces';
export class AzureResourceCacheService implements IAzureResourceCacheService {
public constructor(
public readonly context: ExtensionContext
context: ExtensionContext
) {
this._context = context;
}
public get<T>(key: string): T | undefined {
return this.context.workspaceState.get(key);
public generateKey(id: string): string {
return `${AzureResourceCacheService.cacheKeyPrefix}.${id}`;
}
public get<T>(key: string): T | undefined {
return this._context.workspaceState.get(key);
}
public update<T>(key: string, value: T): void {
this.context.workspaceState.update(key, value);
this._context.workspaceState.update(key, value);
}
private _context: ExtensionContext = undefined;
private static readonly cacheKeyPrefix = 'azure.resource.cache';
}

View File

@@ -1,36 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ExtensionContext } from "vscode";
import { ApiWrapper } from "../../apiWrapper";
import { IAzureResourceContextService } from "../interfaces";
export class AzureResourceContextService implements IAzureResourceContextService {
public constructor(
context: ExtensionContext,
apiWrapper: ApiWrapper
) {
this._context = context;
this._apiWrapper = apiWrapper;
}
public getAbsolutePath(relativePath: string): string {
return this._context.asAbsolutePath(relativePath);
}
public executeCommand(commandId: string, ...args: any[]): void {
this._apiWrapper.executeCommand(commandId, args);
}
public showErrorMessage(errorMessage: string): void {
this._apiWrapper.showErrorMessage(errorMessage);
}
private _context: ExtensionContext = undefined;
private _apiWrapper: ApiWrapper = undefined;
}

View File

@@ -1,43 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as sqlops from 'sqlops';
import { TokenCredentials, ServiceClientCredentials } from 'ms-rest';
import { ApiWrapper } from '../../apiWrapper';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { IAzureResourceCredentialService } from '../interfaces';
import { AzureResourceCredentialError } from '../errors';
export class AzureResourceCredentialService implements IAzureResourceCredentialService {
public constructor(
apiWrapper: ApiWrapper
) {
this._apiWrapper = apiWrapper;
}
public async getCredentials(account: sqlops.Account, resource: sqlops.AzureResource): Promise<ServiceClientCredentials[]> {
try {
let credentials: TokenCredentials[] = [];
let tokens = await this._apiWrapper.getSecurityToken(account, resource);
for (let tenant of account.properties.tenants) {
let token = tokens[tenant.id].token;
let tokenType = tokens[tenant.id].tokenType;
credentials.push(new TokenCredentials(token, tokenType));
}
return credentials;
} catch (error) {
throw new AzureResourceCredentialError(localize('azureResource.services.credentialService.credentialError', 'Failed to get credential for account {0}. Please refresh the account.', account.key.accountId), error);
}
}
private _apiWrapper: ApiWrapper = undefined;
}

View File

@@ -1,39 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ServiceClientCredentials } from 'ms-rest';
import { SqlManagementClient } from 'azure-arm-sql';
import { IAzureResourceDatabaseServerService } from '../interfaces';
import { AzureResourceSubscription, AzureResourceDatabaseServer } from '../models';
export class AzureResourceDatabaseServerService implements IAzureResourceDatabaseServerService {
public async getDatabaseServers(subscription: AzureResourceSubscription, credentials: ServiceClientCredentials[]): Promise<AzureResourceDatabaseServer[]> {
let databaseServers: AzureResourceDatabaseServer[] = [];
for (let cred of credentials) {
let sqlManagementClient = new SqlManagementClient(cred, subscription.id);
try {
let svrs = await sqlManagementClient.servers.list();
svrs.forEach((svr) => databaseServers.push({
name: svr.name,
fullName: svr.fullyQualifiedDomainName,
loginName: svr.administratorLogin,
defaultDatabaseName: 'master'
}));
} catch (error) {
if (error.code === 'InvalidAuthenticationTokenTenant' && error.statusCode === 401) {
/**
* There may be multiple tenants for an account and it may throw exceptions like following. Just swallow the exception here.
* The access token is from the wrong issuer. It must match one of the tenants associated with this subscription.
*/
}
}
}
return databaseServers;
}
}

View File

@@ -1,51 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ServiceClientCredentials } from 'ms-rest';
import { SqlManagementClient } from 'azure-arm-sql';
import { IAzureResourceDatabaseService } from '../interfaces';
import { AzureResourceSubscription, AzureResourceDatabase } from '../models';
export class AzureResourceDatabaseService implements IAzureResourceDatabaseService {
public async getDatabases(subscription: AzureResourceSubscription, credentials: ServiceClientCredentials[]): Promise<AzureResourceDatabase[]> {
let databases: AzureResourceDatabase[] = [];
for (let cred of credentials) {
let sqlManagementClient = new SqlManagementClient(cred, subscription.id);
try {
let svrs = await sqlManagementClient.servers.list();
for (let svr of svrs) {
// Extract resource group name from svr.id
let svrIdRegExp = new RegExp(`\/subscriptions\/${subscription.id}\/resourceGroups\/(.+)\/providers\/Microsoft\.Sql\/servers\/${svr.name}`);
if (!svrIdRegExp.test(svr.id)) {
continue;
}
let founds = svrIdRegExp.exec(svr.id);
let resouceGroup = founds[1];
let dbs = await sqlManagementClient.databases.listByServer(resouceGroup, svr.name);
dbs.forEach((db) => databases.push({
name: db.name,
serverName: svr.name,
serverFullName: svr.fullyQualifiedDomainName,
loginName: svr.administratorLogin
}));
}
} catch (error) {
if (error.code === 'InvalidAuthenticationTokenTenant' && error.statusCode === 401) {
/**
* There may be multiple tenants for an account and it may throw exceptions like following. Just swallow the exception here.
* The access token is from the wrong issuer. It must match one of the tenants associated with this subscription.
*/
}
}
}
return databases;
}
}

View File

@@ -8,11 +8,11 @@
import { WorkspaceConfiguration, ConfigurationTarget } from 'vscode';
import { Account } from 'sqlops';
import { azureResource } from '../azure-resource';
import { IAzureResourceSubscriptionFilterService, IAzureResourceCacheService } from '../interfaces';
import { AzureResourceSubscription } from '../models';
interface AzureResourceSelectedSubscriptionsCache {
selectedSubscriptions: { [accountId: string]: AzureResourceSubscription[]};
selectedSubscriptions: { [accountId: string]: azureResource.AzureResourceSubscription[]};
}
export class AzureResourceSubscriptionFilterService implements IAzureResourceSubscriptionFilterService {
@@ -20,12 +20,14 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
cacheService: IAzureResourceCacheService
) {
this._cacheService = cacheService;
this._cacheKey = this._cacheService.generateKey('selectedSubscriptions');
}
public async getSelectedSubscriptions(account: Account): Promise<AzureResourceSubscription[]> {
let selectedSubscriptions: AzureResourceSubscription[] = [];
public async getSelectedSubscriptions(account: Account): Promise<azureResource.AzureResourceSubscription[]> {
let selectedSubscriptions: azureResource.AzureResourceSubscription[] = [];
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(AzureResourceSubscriptionFilterService.CacheKey);
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(this._cacheKey);
if (cache) {
selectedSubscriptions = cache.selectedSubscriptions[account.key.accountId];
}
@@ -33,10 +35,10 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
return selectedSubscriptions;
}
public async saveSelectedSubscriptions(account: Account, selectedSubscriptions: AzureResourceSubscription[]): Promise<void> {
let selectedSubscriptionsCache: { [accountId: string]: AzureResourceSubscription[]} = {};
public async saveSelectedSubscriptions(account: Account, selectedSubscriptions: azureResource.AzureResourceSubscription[]): Promise<void> {
let selectedSubscriptionsCache: { [accountId: string]: azureResource.AzureResourceSubscription[]} = {};
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(AzureResourceSubscriptionFilterService.CacheKey);
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(this._cacheKey);
if (cache) {
selectedSubscriptionsCache = cache.selectedSubscriptions;
}
@@ -47,14 +49,14 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
selectedSubscriptionsCache[account.key.accountId] = selectedSubscriptions;
this._cacheService.update<AzureResourceSelectedSubscriptionsCache>(AzureResourceSubscriptionFilterService.CacheKey, { selectedSubscriptions: selectedSubscriptionsCache });
this._cacheService.update<AzureResourceSelectedSubscriptionsCache>(this._cacheKey, { selectedSubscriptions: selectedSubscriptionsCache });
const filters: string[] = [];
for (const accountId in selectedSubscriptionsCache) {
filters.push(...selectedSubscriptionsCache[accountId].map((subcription) => `${accountId}/${subcription.id}/${subcription.name}`));
}
const resourceFilterConfig = this._config.inspect<string[]>(AzureResourceSubscriptionFilterService.FilterConfigName);
const resourceFilterConfig = this._config.inspect<string[]>(AzureResourceSubscriptionFilterService.filterConfigName);
let configTarget = ConfigurationTarget.Global;
if (resourceFilterConfig) {
if (resourceFilterConfig.workspaceFolderValue) {
@@ -66,12 +68,12 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
}
}
await this._config.update(AzureResourceSubscriptionFilterService.FilterConfigName, filters, configTarget);
await this._config.update(AzureResourceSubscriptionFilterService.filterConfigName, filters, configTarget);
}
private _config: WorkspaceConfiguration = undefined;
private _cacheService: IAzureResourceCacheService = undefined;
private _cacheKey: string = undefined;
private static readonly FilterConfigName = 'resourceFilter';
private static readonly CacheKey = 'azureResource.cache.selectedSubscriptions';
private static readonly filterConfigName = 'azure.resource.config.filter';
}

View File

@@ -9,24 +9,19 @@ import { Account } from 'sqlops';
import { ServiceClientCredentials } from 'ms-rest';
import { SubscriptionClient } from 'azure-arm-resource';
import { azureResource } from '../azure-resource';
import { IAzureResourceSubscriptionService } from '../interfaces';
import { AzureResourceSubscription } from '../models';
export class AzureResourceSubscriptionService implements IAzureResourceSubscriptionService {
public async getSubscriptions(account: Account, credentials: ServiceClientCredentials[]): Promise<AzureResourceSubscription[]> {
let subscriptions: AzureResourceSubscription[] = [];
for (let cred of credentials) {
let subClient = new SubscriptionClient.SubscriptionClient(cred);
try {
let subs = await subClient.subscriptions.list();
subs.forEach((sub) => subscriptions.push({
id: sub.subscriptionId,
name: sub.displayName
}));
} catch (error) {
// Swallow the exception here.
}
}
public async getSubscriptions(account: Account, credential: ServiceClientCredentials): Promise<azureResource.AzureResourceSubscription[]> {
const subscriptions: azureResource.AzureResourceSubscription[] = [];
const subClient = new SubscriptionClient.SubscriptionClient(credential);
const subs = await subClient.subscriptions.list();
subs.forEach((sub) => subscriptions.push({
id: sub.subscriptionId,
name: sub.displayName
}));
return subscriptions;
}

View File

@@ -0,0 +1,32 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as request from 'request';
import { azureResource } from '../azure-resource';
import { IAzureResourceTenantService } from '../interfaces';
export class AzureResourceTenantService implements IAzureResourceTenantService {
public async getTenantId(subscription: azureResource.AzureResourceSubscription): Promise<string> {
const requestPromisified = new Promise<string>((resolve, reject) => {
const url = `https://management.azure.com/subscriptions/${subscription.id}?api-version=2014-04-01`;
request(url, function (error, response, body) {
if (response.statusCode === 401) {
const tenantIdRegEx = /authorization_uri="https:\/\/login\.windows\.net\/([0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12})"/;
const teantIdString = response.headers['www-authenticate'];
if (tenantIdRegEx.test(teantIdString)) {
resolve(tenantIdRegEx.exec(teantIdString)[1]);
} else {
reject();
}
}
});
});
return await requestPromisified;
}
}

View File

@@ -7,10 +7,10 @@
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import { NodeInfo } from 'sqlops';
import { TreeNode } from '../../treeNodes';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { TreeNode } from '../treeNode';
import { AzureResourceItemType } from '../constants';
export class AzureResourceAccountNotSignedInTreeNode extends TreeNode {
@@ -19,11 +19,11 @@ export class AzureResourceAccountNotSignedInTreeNode extends TreeNode {
}
public getTreeItem(): TreeItem | Promise<TreeItem> {
let item = new TreeItem(AzureResourceAccountNotSignedInTreeNode.SignInLabel, TreeItemCollapsibleState.None);
let item = new TreeItem(AzureResourceAccountNotSignedInTreeNode.signInLabel, TreeItemCollapsibleState.None);
item.contextValue = AzureResourceItemType.message;
item.command = {
title: AzureResourceAccountNotSignedInTreeNode.SignInLabel,
command: 'azureresource.signin',
title: AzureResourceAccountNotSignedInTreeNode.signInLabel,
command: 'azure.resource.signin',
arguments: [this]
};
return item;
@@ -31,7 +31,7 @@ export class AzureResourceAccountNotSignedInTreeNode extends TreeNode {
public getNodeInfo(): NodeInfo {
return {
label: AzureResourceAccountNotSignedInTreeNode.SignInLabel,
label: AzureResourceAccountNotSignedInTreeNode.signInLabel,
isLeaf: true,
errorMessage: undefined,
metadata: undefined,
@@ -47,5 +47,5 @@ export class AzureResourceAccountNotSignedInTreeNode extends TreeNode {
return 'message_accountNotSignedIn';
}
private static readonly SignInLabel = localize('azureResource.tree.accountNotSignedInTreeNode.signIn', 'Sign in to Azure ...');
private static readonly signInLabel = localize('azure.resource.tree.accountNotSignedInTreeNode.signInLabel', 'Sign in to Azure ...');
}

View File

@@ -6,44 +6,59 @@
'use strict';
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import { Account, NodeInfo } from 'sqlops';
import { TreeNode } from '../../treeNodes';
import { Account, NodeInfo, AzureResource } from 'sqlops';
import { TokenCredentials } from 'ms-rest';
import { AppContext } from '../../appContext';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { azureResource } from '../azure-resource';
import { TreeNode } from '../treeNode';
import { AzureResourceCredentialError } from '../errors';
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
import { AzureResourceItemType } from '../constants';
import { AzureResourceItemType, AzureResourceServiceNames } from '../constants';
import { AzureResourceSubscriptionTreeNode } from './subscriptionTreeNode';
import { AzureResourceMessageTreeNode } from './messageTreeNode';
import { AzureResourceMessageTreeNode } from '../messageTreeNode';
import { AzureResourceErrorMessageUtil } from '../utils';
import { AzureResourceSubscription } from '../models';
import { IAzureResourceTreeChangeHandler } from './treeProvider';
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
import { IAzureResourceSubscriptionService, IAzureResourceSubscriptionFilterService, IAzureResourceTenantService } from '../../azureResource/interfaces';
export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNodeBase {
public constructor(
account: Account,
public readonly account: Account,
appContext: AppContext,
treeChangeHandler: IAzureResourceTreeChangeHandler
) {
super(account, treeChangeHandler, undefined);
super(appContext, treeChangeHandler, undefined);
this._subscriptionService = this.appContext.getService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService);
this._subscriptionFilterService = this.appContext.getService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService);
this._tenantService = this.appContext.getService<IAzureResourceTenantService>(AzureResourceServiceNames.tenantService);
this._id = `account_${this.account.key.accountId}`;
this.setCacheKey(`${this._id}.subscriptions`);
this._label = this.generateLabel();
}
public async getChildren(): Promise<TreeNode[]> {
try {
let subscriptions: AzureResourceSubscription[] = [];
let subscriptions: azureResource.AzureResourceSubscription[] = [];
if (this._isClearingCache) {
const credentials = await this.getCredentials();
subscriptions = (await this.servicePool.subscriptionService.getSubscriptions(this.account, credentials)) || <AzureResourceSubscription[]>[];
try {
const tokens = await this.appContext.apiWrapper.getSecurityToken(this.account, AzureResource.ResourceManagement);
let cache = this.getCache<AzureResourceSubscriptionsCache>();
if (!cache) {
cache = { subscriptions: { } };
for (const tenant of this.account.properties.tenants) {
const token = tokens[tenant.id].token;
const tokenType = tokens[tenant.id].tokenType;
subscriptions.push(...(await this._subscriptionService.getSubscriptions(this.account, new TokenCredentials(token, tokenType)) || <azureResource.AzureResourceSubscription[]>[]));
}
} catch (error) {
throw new AzureResourceCredentialError(localize('azure.resource.tree.accountTreeNode.credentialError', 'Failed to get credential for account {0}. Please refresh the account.', this.account.key.accountId), error);
}
cache.subscriptions[this.account.key.accountId] = subscriptions;
this.updateCache<AzureResourceSubscriptionsCache>(cache);
this.updateCache<azureResource.AzureResourceSubscription[]>(subscriptions);
this._isClearingCache = false;
} else {
@@ -52,8 +67,8 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
this._totalSubscriptionCount = subscriptions.length;
let selectedSubscriptions = await this.servicePool.subscriptionFilterService.getSelectedSubscriptions(this.account);
let selectedSubscriptionIds = (selectedSubscriptions || <AzureResourceSubscription[]>[]).map((subscription) => subscription.id);
const selectedSubscriptions = await this._subscriptionFilterService.getSelectedSubscriptions(this.account);
const selectedSubscriptionIds = (selectedSubscriptions || <azureResource.AzureResourceSubscription[]>[]).map((subscription) => subscription.id);
if (selectedSubscriptionIds.length > 0) {
subscriptions = subscriptions.filter((subscription) => selectedSubscriptionIds.indexOf(subscription.id) !== -1);
this._selectedSubscriptionCount = selectedSubscriptionIds.length;
@@ -65,31 +80,36 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
this.refreshLabel();
if (subscriptions.length === 0) {
return [AzureResourceMessageTreeNode.create(AzureResourceAccountTreeNode.NoSubscriptions, this)];
return [AzureResourceMessageTreeNode.create(AzureResourceAccountTreeNode.noSubscriptionsLabel, this)];
} else {
return subscriptions.map((subscription) => new AzureResourceSubscriptionTreeNode(subscription, this.account, this.treeChangeHandler, this));
return await Promise.all(subscriptions.map(async (subscription) => {
const tenantId = await this._tenantService.getTenantId(subscription);
return new AzureResourceSubscriptionTreeNode(this.account, subscription, tenantId, this.appContext, this.treeChangeHandler, this);
}));
}
} catch (error) {
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), this)];
if (error instanceof AzureResourceCredentialError) {
this.appContext.apiWrapper.showErrorMessage(error.message);
this.appContext.apiWrapper.executeCommand('azure.resource.signin');
} else {
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), this)];
}
}
}
public async getCachedSubscriptions(): Promise<AzureResourceSubscription[]> {
const subscriptions: AzureResourceSubscription[] = [];
const cache = this.getCache<AzureResourceSubscriptionsCache>();
if (cache) {
subscriptions.push(...cache.subscriptions[this.account.key.accountId]);
}
return subscriptions;
public async getCachedSubscriptions(): Promise<azureResource.AzureResourceSubscription[]> {
return this.getCache<azureResource.AzureResourceSubscription[]>();
}
public getTreeItem(): TreeItem | Promise<TreeItem> {
let item = new TreeItem(this._label, TreeItemCollapsibleState.Collapsed);
const item = new TreeItem(this._label, TreeItemCollapsibleState.Collapsed);
item.id = this._id;
item.contextValue = AzureResourceItemType.account;
item.iconPath = {
dark: this.servicePool.contextService.getAbsolutePath('resources/dark/account_inverse.svg'),
light: this.servicePool.contextService.getAbsolutePath('resources/light/account.svg')
dark: this.appContext.extensionContext.asAbsolutePath('resources/dark/account_inverse.svg'),
light: this.appContext.extensionContext.asAbsolutePath('resources/light/account.svg')
};
return item;
}
@@ -128,10 +148,6 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
}
}
protected get cacheKey(): string {
return 'azureResource.cache.subscriptions';
}
private generateLabel(): string {
let label = `${this.account.displayInfo.displayName} (${this.account.key.accountId})`;
@@ -142,14 +158,14 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
return label;
}
private _subscriptionService: IAzureResourceSubscriptionService = undefined;
private _subscriptionFilterService: IAzureResourceSubscriptionFilterService = undefined;
private _tenantService: IAzureResourceTenantService = undefined;
private _id: string = undefined;
private _label: string = undefined;
private _totalSubscriptionCount = 0;
private _selectedSubscriptionCount = 0;
private static readonly NoSubscriptions = localize('azureResource.tree.accountTreeNode.noSubscriptions', 'No Subscriptions found.');
}
interface AzureResourceSubscriptionsCache {
subscriptions: { [accountId: string]: AzureResourceSubscription[] };
}
private static readonly noSubscriptionsLabel = localize('azure.resource.tree.accountTreeNode.noSubscriptionsLabel', 'No Subscriptions found.');
}

View File

@@ -5,16 +5,16 @@
'use strict';
import * as sqlops from 'sqlops';
import { ServiceClientCredentials } from 'ms-rest';
import { TreeNode } from '../../treeNodes';
import { AppContext } from '../../appContext';
import { AzureResourceServicePool } from '../servicePool';
import { AzureResourceCredentialError } from '../errors';
import { TreeNode } from '../treeNode';
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
import { IAzureResourceCacheService } from '../../azureResource/interfaces';
import { AzureResourceServiceNames } from '../constants';
export abstract class AzureResourceTreeNodeBase extends TreeNode {
public constructor(
public readonly appContext: AppContext,
public readonly treeChangeHandler: IAzureResourceTreeChangeHandler,
parent: TreeNode
) {
@@ -22,17 +22,17 @@ export abstract class AzureResourceTreeNodeBase extends TreeNode {
this.parent = parent;
}
public readonly servicePool = AzureResourceServicePool.getInstance();
}
export abstract class AzureResourceContainerTreeNodeBase extends AzureResourceTreeNodeBase {
public constructor(
public readonly account: sqlops.Account,
appContext: AppContext,
treeChangeHandler: IAzureResourceTreeChangeHandler,
parent: TreeNode
) {
super(treeChangeHandler, parent);
super(appContext, treeChangeHandler, parent);
this._cacheService = this.appContext.getService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService);
}
public clearCache(): void {
@@ -43,29 +43,19 @@ export abstract class AzureResourceContainerTreeNodeBase extends AzureResourceTr
return this._isClearingCache;
}
protected async getCredentials(): Promise<ServiceClientCredentials[]> {
try {
return await this.servicePool.credentialService.getCredentials(this.account, sqlops.AzureResource.ResourceManagement);
} catch (error) {
if (error instanceof AzureResourceCredentialError) {
this.servicePool.contextService.showErrorMessage(error.message);
this.servicePool.contextService.executeCommand('azureresource.signin');
} else {
throw error;
}
}
}
protected setCacheKey(id: string): void {
this._cacheKey = this._cacheService.generateKey(id);
}
protected updateCache<T>(cache: T): void {
this.servicePool.cacheService.update<T>(this.cacheKey, cache);
this._cacheService.update<T>(this._cacheKey, cache);
}
protected getCache<T>(): T {
return this.servicePool.cacheService.get<T>(this.cacheKey);
return this._cacheService.get<T>(this._cacheKey);
}
protected abstract get cacheKey(): string;
protected _isClearingCache = true;
private _cacheService: IAzureResourceCacheService = undefined;
private _cacheKey: string = undefined;
}

View File

@@ -1,103 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import { Account, NodeInfo } from 'sqlops';
import { TreeNode } from '../../treeNodes';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
import { AzureResourceItemType } from '../constants';
import { AzureResourceErrorMessageUtil } from '../utils';
import { AzureResourceDatabaseTreeNode } from './databaseTreeNode';
import { AzureResourceMessageTreeNode } from './messageTreeNode';
import { AzureResourceSubscription, AzureResourceDatabase } from '../models';
import { IAzureResourceTreeChangeHandler } from './treeProvider';
export class AzureResourceDatabaseContainerTreeNode extends AzureResourceContainerTreeNodeBase {
public constructor(
public readonly subscription: AzureResourceSubscription,
account: Account,
treeChangeHandler: IAzureResourceTreeChangeHandler,
parent: TreeNode
) {
super(account, treeChangeHandler, parent);
}
public async getChildren(): Promise<TreeNode[]> {
try {
let databases: AzureResourceDatabase[] = [];
if (this._isClearingCache) {
let credentials = await this.getCredentials();
databases = (await this.servicePool.databaseService.getDatabases(this.subscription, credentials)) || <AzureResourceDatabase[]>[];
let cache = this.getCache<AzureResourceDatabasesCache>();
if (!cache) {
cache = { databases: { } };
}
cache.databases[this.subscription.id] = databases;
this.updateCache(cache);
this._isClearingCache = false;
} else {
const cache = this.getCache<AzureResourceDatabasesCache>();
if (cache) {
databases = cache.databases[this.subscription.id] || <AzureResourceDatabase[]>[];
}
}
if (databases.length === 0) {
return [AzureResourceMessageTreeNode.create(AzureResourceDatabaseContainerTreeNode.NoDatabases, this)];
} else {
return databases.map((database) => new AzureResourceDatabaseTreeNode(database, this.treeChangeHandler, this));
}
} catch (error) {
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), this)];
}
}
public getTreeItem(): TreeItem | Promise<TreeItem> {
let item = new TreeItem(AzureResourceDatabaseContainerTreeNode.Label, TreeItemCollapsibleState.Collapsed);
item.contextValue = AzureResourceItemType.databaseContainer;
item.iconPath = {
dark: this.servicePool.contextService.getAbsolutePath('resources/dark/folder_inverse.svg'),
light: this.servicePool.contextService.getAbsolutePath('resources/light/folder.svg')
};
return item;
}
public getNodeInfo(): NodeInfo {
return {
label: AzureResourceDatabaseContainerTreeNode.Label,
isLeaf: false,
errorMessage: undefined,
metadata: undefined,
nodePath: this.generateNodePath(),
nodeStatus: undefined,
nodeType: AzureResourceItemType.databaseContainer,
nodeSubType: undefined,
iconType: AzureResourceItemType.databaseContainer
};
}
public get nodePathValue(): string {
return 'databaseContainer';
}
protected get cacheKey(): string {
return 'azureResource.cache.databases';
}
private static readonly Label = localize('azureResource.tree.databaseContainerTreeNode.label', 'SQL Databases');
private static readonly NoDatabases = localize('azureResource.tree.databaseContainerTreeNode.noDatabases', 'No SQL Databases found.');
}
interface AzureResourceDatabasesCache {
databases: { [subscriptionId: string]: AzureResourceDatabase[] };
}

View File

@@ -1,103 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import { Account, NodeInfo } from 'sqlops';
import { TreeNode } from '../../treeNodes';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
import { AzureResourceItemType } from '../constants';
import { AzureResourceMessageTreeNode } from './messageTreeNode';
import { AzureResourceErrorMessageUtil } from '../utils';
import { AzureResourceSubscription, AzureResourceDatabaseServer } from '../models';
import { AzureResourceDatabaseServerTreeNode } from './databaseServerTreeNode';
import { IAzureResourceTreeChangeHandler } from './treeProvider';
export class AzureResourceDatabaseServerContainerTreeNode extends AzureResourceContainerTreeNodeBase {
public constructor(
public readonly subscription: AzureResourceSubscription,
account: Account,
treeChangeHandler: IAzureResourceTreeChangeHandler,
parent: TreeNode
) {
super(account, treeChangeHandler, parent);
}
public async getChildren(): Promise<TreeNode[]> {
try {
let databaseServers: AzureResourceDatabaseServer[] = [];
if (this._isClearingCache) {
let credentials = await this.getCredentials();
databaseServers = (await this.servicePool.databaseServerService.getDatabaseServers(this.subscription, credentials)) || <AzureResourceDatabaseServer[]>[];
let cache = this.getCache<AzureResourceDatabaseServersCache>();
if (!cache) {
cache = { databaseServers: { } };
}
cache.databaseServers[this.subscription.id] = databaseServers;
this.updateCache<AzureResourceDatabaseServersCache>(cache);
this._isClearingCache = false;
} else {
const cache = this.getCache<AzureResourceDatabaseServersCache>();
if (cache) {
databaseServers = cache.databaseServers[this.subscription.id] || <AzureResourceDatabaseServer[]>[];
}
}
if (databaseServers.length === 0) {
return [AzureResourceMessageTreeNode.create(AzureResourceDatabaseServerContainerTreeNode.NoDatabaseServers, this)];
} else {
return databaseServers.map((server) => new AzureResourceDatabaseServerTreeNode(server, this.treeChangeHandler, this));
}
} catch (error) {
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), this)];
}
}
public getTreeItem(): TreeItem | Promise<TreeItem> {
let item = new TreeItem(AzureResourceDatabaseServerContainerTreeNode.Label, TreeItemCollapsibleState.Collapsed);
item.contextValue = AzureResourceItemType.databaseServerContainer;
item.iconPath = {
dark: this.servicePool.contextService.getAbsolutePath('resources/dark/folder_inverse.svg'),
light: this.servicePool.contextService.getAbsolutePath('resources/light/folder.svg')
};
return item;
}
public getNodeInfo(): NodeInfo {
return {
label: AzureResourceDatabaseServerContainerTreeNode.Label,
isLeaf: false,
errorMessage: undefined,
metadata: undefined,
nodePath: this.generateNodePath(),
nodeStatus: undefined,
nodeType: AzureResourceItemType.databaseServerContainer,
nodeSubType: undefined,
iconType: AzureResourceItemType.databaseServerContainer
};
}
public get nodePathValue(): string {
return 'databaseServerContainer';
}
protected get cacheKey(): string {
return 'azureResource.cache.databaseServers';
}
private static readonly Label = localize('azureResource.tree.databaseServerContainerTreeNode.label', 'SQL Servers');
private static readonly NoDatabaseServers = localize('azureResource.tree.databaseContainerTreeNode.noDatabaseServers', 'No SQL Servers found.');
}
interface AzureResourceDatabaseServersCache {
databaseServers: { [subscriptionId: string]: AzureResourceDatabaseServer[] };
}

View File

@@ -1,57 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import { NodeInfo } from 'sqlops';
import { TreeNode } from '../../treeNodes';
import { AzureResourceTreeNodeBase } from './baseTreeNodes';
import { AzureResourceItemType } from '../constants';
import { AzureResourceDatabaseServer } from '../models';
import { IAzureResourceTreeChangeHandler } from './treeProvider';
export class AzureResourceDatabaseServerTreeNode extends AzureResourceTreeNodeBase {
public constructor(
public readonly databaseServer: AzureResourceDatabaseServer,
treeChangeHandler: IAzureResourceTreeChangeHandler,
parent: TreeNode
) {
super(treeChangeHandler, parent);
}
public async getChildren(): Promise<TreeNode[]> {
return [];
}
public getTreeItem(): TreeItem | Promise<TreeItem> {
let item = new TreeItem(this.databaseServer.name, TreeItemCollapsibleState.None);
item.contextValue = AzureResourceItemType.databaseServer;
item.iconPath = {
dark: this.servicePool.contextService.getAbsolutePath('resources/dark/sql_server_inverse.svg'),
light: this.servicePool.contextService.getAbsolutePath('resources/light/sql_server.svg')
};
return item;
}
public getNodeInfo(): NodeInfo {
return {
label: this.databaseServer.name,
isLeaf: true,
errorMessage: undefined,
metadata: undefined,
nodePath: this.generateNodePath(),
nodeStatus: undefined,
nodeType: AzureResourceItemType.databaseServer,
nodeSubType: undefined,
iconType: AzureResourceItemType.databaseServer
};
}
public get nodePathValue(): string {
return `databaseServer_${this.databaseServer.name}`;
}
}

View File

@@ -1,61 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import { NodeInfo } from 'sqlops';
import { TreeNode } from '../../treeNodes';
import { AzureResourceTreeNodeBase } from './baseTreeNodes';
import { AzureResourceItemType } from '../constants';
import { AzureResourceDatabase } from '../models';
import { IAzureResourceTreeChangeHandler } from './treeProvider';
export class AzureResourceDatabaseTreeNode extends AzureResourceTreeNodeBase {
public constructor(
public readonly database: AzureResourceDatabase,
treeChangeHandler: IAzureResourceTreeChangeHandler,
parent: TreeNode
) {
super(treeChangeHandler, parent);
this._label = `${this.database.name} (${this.database.serverName})`;
}
public async getChildren(): Promise<TreeNode[]> {
return [];
}
public getTreeItem(): TreeItem | Promise<TreeItem> {
let item = new TreeItem(this._label, TreeItemCollapsibleState.None);
item.contextValue = AzureResourceItemType.database;
item.iconPath = {
dark: this.servicePool.contextService.getAbsolutePath('resources/dark/sql_database_inverse.svg'),
light: this.servicePool.contextService.getAbsolutePath('resources/light/sql_database.svg')
};
return item;
}
public getNodeInfo(): NodeInfo {
return {
label: this._label,
isLeaf: true,
errorMessage: undefined,
metadata: undefined,
nodePath: this.generateNodePath(),
nodeStatus: undefined,
nodeType: AzureResourceItemType.database,
nodeSubType: undefined,
iconType: AzureResourceItemType.database
};
}
public get nodePathValue(): string {
return `database_${this.database.name}`;
}
private _label: string = undefined;
}

View File

@@ -7,38 +7,66 @@
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import { Account, NodeInfo } from 'sqlops';
import { TreeNode } from '../../treeNodes';
import { AppContext } from '../../appContext';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { AzureResourceTreeNodeBase, AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
import { azureResource } from '../azure-resource';
import { TreeNode } from '../treeNode';
import { IAzureResourceNodeWithProviderId } from '../interfaces';
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
import { AzureResourceItemType } from '../constants';
import { AzureResourceDatabaseContainerTreeNode } from './databaseContainerTreeNode';
import { AzureResourceDatabaseServerContainerTreeNode } from './databaseServerContainerTreeNode';
import { AzureResourceSubscription } from '../models';
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
import { AzureResourceMessageTreeNode } from '../messageTreeNode';
import { AzureResourceErrorMessageUtil } from '../utils';
import { AzureResourceService } from '../resourceService';
import { AzureResourceResourceTreeNode } from '../resourceTreeNode';
export class AzureResourceSubscriptionTreeNode extends AzureResourceTreeNodeBase {
export class AzureResourceSubscriptionTreeNode extends AzureResourceContainerTreeNodeBase {
public constructor(
public readonly subscription: AzureResourceSubscription,
account: Account,
public readonly account: Account,
public readonly subscription: azureResource.AzureResourceSubscription,
public readonly tenatId: string,
appContext: AppContext,
treeChangeHandler: IAzureResourceTreeChangeHandler,
parent: TreeNode
) {
super(treeChangeHandler, parent);
super(appContext, treeChangeHandler, parent);
this._children.push(new AzureResourceDatabaseContainerTreeNode(subscription, account, treeChangeHandler, this));
this._children.push(new AzureResourceDatabaseServerContainerTreeNode(subscription, account, treeChangeHandler, this));
this._id = `account_${this.account.key.accountId}.subscription_${this.subscription.id}.tenant_${this.tenatId}`;
this.setCacheKey(`${this._id}.resources`);
}
public async getChildren(): Promise<TreeNode[]> {
return this._children;
try {
const resourceService = AzureResourceService.getInstance();
const children: IAzureResourceNodeWithProviderId[] = [];
for (const resourceProviderId of await resourceService.listResourceProviderIds()) {
children.push(...await resourceService.getRootChildren(resourceProviderId, this.account, this.subscription, this.tenatId));
}
if (children.length === 0) {
return [AzureResourceMessageTreeNode.create(AzureResourceSubscriptionTreeNode.noResourcesLabel, this)];
} else {
return children.map((child) => {
// To make tree node's id unique, otherwise, treeModel.js would complain 'item already registered'
child.resourceNode.treeItem.id = `${this._id}.${child.resourceNode.treeItem.id}`;
return new AzureResourceResourceTreeNode(child, this);
});
}
} catch (error) {
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), this)];
}
}
public getTreeItem(): TreeItem | Promise<TreeItem> {
let item = new TreeItem(this.subscription.name, TreeItemCollapsibleState.Collapsed);
const item = new TreeItem(this.subscription.name, TreeItemCollapsibleState.Collapsed);
item.contextValue = AzureResourceItemType.subscription;
item.iconPath = {
dark: this.servicePool.contextService.getAbsolutePath('resources/dark/subscription_inverse.svg'),
light: this.servicePool.contextService.getAbsolutePath('resources/light/subscription.svg')
dark: this.appContext.extensionContext.asAbsolutePath('resources/dark/subscription_inverse.svg'),
light: this.appContext.extensionContext.asAbsolutePath('resources/light/subscription.svg')
};
return item;
}
@@ -58,8 +86,10 @@ export class AzureResourceSubscriptionTreeNode extends AzureResourceTreeNodeBase
}
public get nodePathValue(): string {
return `subscription_${this.subscription.id}`;
return this._id;
}
private _children: AzureResourceContainerTreeNodeBase[] = [];
private _id: string = undefined;
private static readonly noResourcesLabel = localize('azure.resource.tree.subscriptionTreeNode.noResourcesLabel', 'No Resources found.');
}

View File

@@ -5,7 +5,7 @@
'use strict';
import { TreeNode } from '../../treeNodes';
import { TreeNode } from '../treeNode';
export interface IAzureResourceTreeChangeHandler {
notifyNodeChanged(node: TreeNode): void;

View File

@@ -6,26 +6,25 @@
'use strict';
import { TreeDataProvider, EventEmitter, Event, TreeItem } from 'vscode';
import { DidChangeAccountsParams } from 'sqlops';
import { TreeNode } from '../../treeNodes';
import { setInterval, clearInterval } from 'timers';
import { AppContext } from '../../appContext';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { AzureResourceServicePool } from '../servicePool';
import { TreeNode } from '../treeNode';
import { AzureResourceAccountTreeNode } from './accountTreeNode';
import { AzureResourceAccountNotSignedInTreeNode } from './accountNotSignedInTreeNode';
import { AzureResourceMessageTreeNode } from './messageTreeNode';
import { AzureResourceContainerTreeNodeBase, AzureResourceTreeNodeBase } from './baseTreeNodes';
import { AzureResourceMessageTreeNode } from '../messageTreeNode';
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
import { AzureResourceErrorMessageUtil } from '../utils';
export interface IAzureResourceTreeChangeHandler {
notifyNodeChanged(node: TreeNode): void;
}
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
import { IAzureResourceAccountService } from '../../azureResource/interfaces';
import { AzureResourceServiceNames } from '../constants';
export class AzureResourceTreeProvider implements TreeDataProvider<TreeNode>, IAzureResourceTreeChangeHandler {
public constructor() {
AzureResourceServicePool.getInstance().accountService.onDidChangeAccounts((e: DidChangeAccountsParams) => { this._onDidChangeTreeData.fire(undefined); });
public constructor(
public readonly appContext: AppContext
) {
}
public async getChildren(element?: TreeNode): Promise<TreeNode[]> {
@@ -37,7 +36,7 @@ export class AzureResourceTreeProvider implements TreeDataProvider<TreeNode>, IA
this._loadingTimer = setInterval(async () => {
try {
// Call sqlops.accounts.getAllAccounts() to determine whether the system has been initialized.
await AzureResourceServicePool.getInstance().accountService.getAccounts();
await this.appContext.getService<IAzureResourceAccountService>(AzureResourceServiceNames.accountService).getAccounts();
// System has been initialized
this.isSystemInitialized = true;
@@ -51,16 +50,16 @@ export class AzureResourceTreeProvider implements TreeDataProvider<TreeNode>, IA
// System not initialized yet
this.isSystemInitialized = false;
}
}, AzureResourceTreeProvider.LoadingTimerInterval);
}, AzureResourceTreeProvider.loadingTimerInterval);
return [AzureResourceMessageTreeNode.create(AzureResourceTreeProvider.Loading, undefined)];
return [AzureResourceMessageTreeNode.create(AzureResourceTreeProvider.loadingLabel, undefined)];
}
try {
const accounts = await AzureResourceServicePool.getInstance().accountService.getAccounts();
const accounts = await this.appContext.getService<IAzureResourceAccountService>(AzureResourceServiceNames.accountService).getAccounts();
if (accounts && accounts.length > 0) {
return accounts.map((account) => new AzureResourceAccountTreeNode(account, this));
return accounts.map((account) => new AzureResourceAccountTreeNode(account, this.appContext, this));
} else {
return [new AzureResourceAccountNotSignedInTreeNode()];
}
@@ -96,6 +95,6 @@ export class AzureResourceTreeProvider implements TreeDataProvider<TreeNode>, IA
private _loadingTimer: NodeJS.Timer = undefined;
private _onDidChangeTreeData = new EventEmitter<TreeNode>();
private static readonly Loading = localize('azureResource.tree.treeProvider.loading', 'Loading ...');
private static readonly LoadingTimerInterval = 5000;
private static readonly loadingLabel = localize('azure.resource.tree.treeProvider.loadingLabel', 'Loading ...');
private static readonly loadingTimerInterval = 5000;
}

View File

@@ -11,16 +11,6 @@ import * as vscode from 'vscode';
type TreeNodePredicate = (node: TreeNode) => boolean;
export abstract class TreeNode {
private _parent: TreeNode = undefined;
public get parent(): TreeNode {
return this._parent;
}
public set parent(node: TreeNode) {
this._parent = node;
}
public generateNodePath(): string {
let path = undefined;
if (this.parent) {
@@ -65,13 +55,23 @@ export abstract class TreeNode {
return undefined;
}
public get parent(): TreeNode {
return this._parent;
}
public set parent(node: TreeNode) {
this._parent = node;
}
public abstract getChildren(refreshChildren: boolean): TreeNode[] | Promise<TreeNode[]>;
public abstract getTreeItem(): vscode.TreeItem | Promise<vscode.TreeItem>;
public abstract getNodeInfo(): sqlops.NodeInfo;
/**
* The value to use for this node in the node path
*/
public abstract get nodePathValue(): string;
abstract getChildren(refreshChildren: boolean): TreeNode[] | Promise<TreeNode[]>;
abstract getTreeItem(): vscode.TreeItem | Promise<vscode.TreeItem>;
abstract getNodeInfo(): sqlops.NodeInfo;
private _parent: TreeNode = undefined;
}

View File

@@ -12,10 +12,9 @@ export function getErrorMessage(error: Error | string): string {
return (error instanceof Error) ? error.message : error;
}
export class AzureResourceErrorMessageUtil {
public static getErrorMessage(error: Error | string): string {
return localize('azureResource.error', 'Error: {0}', getErrorMessage(error));
return localize('azure.resource.error', 'Error: {0}', getErrorMessage(error));
}
}

View File

@@ -0,0 +1,53 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import ControllerBase from './controllerBase';
import { DidChangeAccountsParams } from 'sqlops';
import {
IAzureResourceCacheService,
IAzureResourceAccountService,
IAzureResourceSubscriptionService,
IAzureResourceSubscriptionFilterService,
IAzureResourceTenantService } from '../azureResource/interfaces';
import { AzureResourceServiceNames } from '../azureResource/constants';
import { AzureResourceTreeProvider } from '../azureResource/tree/treeProvider';
import { registerAzureResourceCommands } from '../azureResource/commands';
import { AzureResourceAccountService } from '../azureResource/services/accountService';
import { AzureResourceSubscriptionService } from '../azureResource/services/subscriptionService';
import { AzureResourceSubscriptionFilterService } from '../azureResource/services/subscriptionFilterService';
import { AzureResourceCacheService } from '../azureResource/services/cacheService';
import { AzureResourceTenantService } from '../azureResource/services/tenantService';
import { registerAzureResourceDatabaseServerCommands } from '../azureResource/providers/databaseServer/commands';
import { registerAzureResourceDatabaseCommands } from '../azureResource/providers/database/commands';
export default class AzureResourceController extends ControllerBase {
public activate(): Promise<boolean> {
this.appContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, new AzureResourceCacheService(this.extensionContext));
this.appContext.registerService<IAzureResourceAccountService>(AzureResourceServiceNames.accountService, new AzureResourceAccountService(this.apiWrapper));
this.appContext.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, new AzureResourceSubscriptionService());
this.appContext.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, new AzureResourceSubscriptionFilterService(new AzureResourceCacheService(this.extensionContext)));
this.appContext.registerService<IAzureResourceTenantService>(AzureResourceServiceNames.tenantService, new AzureResourceTenantService());
const azureResourceTree = new AzureResourceTreeProvider(this.appContext);
this.extensionContext.subscriptions.push(this.apiWrapper.registerTreeDataProvider('azureResourceExplorer', azureResourceTree));
this.appContext.getService<IAzureResourceAccountService>(AzureResourceServiceNames.accountService).onDidChangeAccounts((e: DidChangeAccountsParams) => { azureResourceTree.notifyNodeChanged(undefined); });
registerAzureResourceCommands(this.appContext, azureResourceTree);
registerAzureResourceDatabaseServerCommands(this.appContext);
registerAzureResourceDatabaseCommands(this.appContext);
return Promise.resolve(true);
}
public deactivate(): void {
}
}

View File

@@ -1,54 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import ControllerBase from './controllerBase';
import { AzureResourceTreeProvider } from '../azureResource/tree/treeProvider';
import { registerAzureResourceCommands } from '../azureResource/commands';
import { AzureResourceServicePool } from '../azureResource/servicePool';
import { AzureResourceCredentialService } from '../azureResource/services/credentialService';
import { AzureResourceAccountService } from '../azureResource/services/accountService';
import { AzureResourceSubscriptionService } from '../azureResource/services/subscriptionService';
import { AzureResourceSubscriptionFilterService } from '../azureResource/services/subscriptionFilterService';
import { AzureResourceDatabaseServerService } from '../azureResource/services/databaseServerService';
import { AzureResourceDatabaseService } from '../azureResource/services/databaseService';
import { AzureResourceCacheService } from '../azureResource/services/cacheService';
import { AzureResourceContextService } from '../azureResource/services/contextService';
/**
* The main controller class that initializes the extension
*/
export default class MainController extends ControllerBase {
// PUBLIC METHODS //////////////////////////////////////////////////////
/**
* Deactivates the extension
*/
public deactivate(): void {
}
public activate(): Promise<boolean> {
this.configureAzureResource();
return Promise.resolve(true);
}
private configureAzureResource(): void {
let servicePool = AzureResourceServicePool.getInstance();
servicePool.cacheService = new AzureResourceCacheService(this.extensionContext);
servicePool.contextService = new AzureResourceContextService(this.extensionContext, this.apiWrapper);
servicePool.accountService = new AzureResourceAccountService(this.apiWrapper);
servicePool.credentialService = new AzureResourceCredentialService(this.apiWrapper);
servicePool.subscriptionService = new AzureResourceSubscriptionService();
servicePool.subscriptionFilterService = new AzureResourceSubscriptionFilterService(new AzureResourceCacheService(this.extensionContext));
servicePool.databaseService = new AzureResourceDatabaseService();
servicePool.databaseServerService = new AzureResourceDatabaseServerService();
let azureResourceTree = new AzureResourceTreeProvider();
this.extensionContext.subscriptions.push(this.apiWrapper.registerTreeDataProvider('azureResourceExplorer', azureResourceTree));
registerAzureResourceCommands(this.apiWrapper, azureResourceTree);
}
}

View File

@@ -6,12 +6,17 @@ import * as path from 'path';
import * as os from 'os';
import * as constants from './constants';
import MainController from './controllers/mainController';
import AzureResourceController from './controllers/azureResourceController';
import { AppContext } from './appContext';
import ControllerBase from './controllers/controllerBase';
import { ApiWrapper } from './apiWrapper';
import { AzureAccountProviderService } from './account-provider/azureAccountProviderService';
import { AzureResourceDatabaseServerProvider } from './azureResource/providers/databaseServer/databaseServerProvider';
import { AzureResourceDatabaseServerService } from './azureResource/providers/databaseServer/databaseServerService';
import { AzureResourceDatabaseProvider } from './azureResource/providers/database/databaseProvider';
import { AzureResourceDatabaseService } from './azureResource/providers/database/databaseService';
let controllers: ControllerBase[] = [];
@@ -35,7 +40,8 @@ export function getDefaultLogLocation() {
// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
export function activate(extensionContext: vscode.ExtensionContext) {
let appContext = new AppContext(extensionContext, new ApiWrapper());
const apiWrapper = new ApiWrapper();
let appContext = new AppContext(extensionContext, apiWrapper);
let activations: Thenable<boolean>[] = [];
// Create the folder for storing the token caches
@@ -56,21 +62,19 @@ export function activate(extensionContext: vscode.ExtensionContext) {
extensionContext.subscriptions.push(accountProviderService);
accountProviderService.activate();
// Start the main controller
let mainController = new MainController(appContext);
controllers.push(mainController);
extensionContext.subscriptions.push(mainController);
activations.push(mainController.activate());
const azureResourceController = new AzureResourceController(appContext);
controllers.push(azureResourceController);
extensionContext.subscriptions.push(azureResourceController);
activations.push(azureResourceController.activate());
return Promise.all(activations)
.then((results: boolean[]) => {
for (let result of results) {
if (!result) {
return false;
}
}
return true;
});
return {
provideResources() {
return [
new AzureResourceDatabaseServerProvider(new AzureResourceDatabaseServerService(), apiWrapper, extensionContext),
new AzureResourceDatabaseProvider(new AzureResourceDatabaseService(), apiWrapper, extensionContext)
];
}
};
}
// this method is called when your extension is deactivated

View File

@@ -9,8 +9,8 @@ import * as should from 'should';
import * as vscode from 'vscode';
import 'mocha';
import { AzureResourceItemType } from '../../../azureResource/constants';
import { AzureResourceMessageTreeNode } from '../../../azureResource/tree/messageTreeNode';
import { AzureResourceItemType } from '../../azureResource/constants';
import { AzureResourceMessageTreeNode } from '../../azureResource/messageTreeNode';
describe('AzureResourceMessageTreeNode.info', function(): void {
it('Should be correct when created.', async function(): Promise<void> {

View File

@@ -0,0 +1,150 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as should from 'should';
import * as TypeMoq from 'typemoq';
import * as sqlops from 'sqlops';
import * as vscode from 'vscode';
import 'mocha';
import { azureResource } from '../../../../azureResource/azure-resource';
import { ApiWrapper } from '../../../../apiWrapper';
import { IAzureResourceDatabaseService } from '../../../../azureResource/providers/database/interfaces';
import { AzureResourceDatabaseTreeDataProvider } from '../../../../azureResource/providers/database/databaseTreeDataProvider';
import { AzureResourceDatabase } from '../../../../azureResource/providers/database/models';
import { AzureResourceItemType } from '../../../../azureResource/constants';
// Mock services
let mockDatabaseService: TypeMoq.IMock<IAzureResourceDatabaseService>;
let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
// Mock test data
const mockAccount: sqlops.Account = {
key: {
accountId: 'mock_account',
providerId: 'mock_provider'
},
displayInfo: {
displayName: 'mock_account@test.com',
accountType: 'Microsoft',
contextualDisplayName: 'test'
},
properties: undefined,
isStale: false
};
const mockSubscription: azureResource.AzureResourceSubscription = {
id: 'mock_subscription',
name: 'mock subscription'
};
const mockTenantId: string = 'mock_tenant';
const mockResourceRootNode: azureResource.IAzureResourceNode = {
account: mockAccount,
subscription: mockSubscription,
tenantId: mockTenantId,
treeItem: {
id: 'mock_resource_root_node',
label: 'mock resource root node',
iconPath: undefined,
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
contextValue: 'mock_resource_root_node'
}
};
const mockTokens = {};
mockTokens[mockTenantId] = {
token: 'mock_token',
tokenType: 'Bearer'
};
const mockDatabases: AzureResourceDatabase[] = [
{
name: 'mock database 1',
serverName: 'mock database server 1',
serverFullName: 'mock database server full name 1',
loginName: 'mock login'
},
{
name: 'mock database 2',
serverName: 'mock database server 2',
serverFullName: 'mock database server full name 2',
loginName: 'mock login'
}
];
describe('AzureResourceDatabaseTreeDataProvider.info', function(): void {
beforeEach(() => {
mockDatabaseService = TypeMoq.Mock.ofType<IAzureResourceDatabaseService>();
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
});
it('Should be correct when created.', async function(): Promise<void> {
const treeDataProvider = new AzureResourceDatabaseTreeDataProvider(mockDatabaseService.object, mockApiWrapper.object, mockExtensionContext.object);
const treeItem = await treeDataProvider.getTreeItem(mockResourceRootNode);
should(treeItem.id).equal(mockResourceRootNode.treeItem.id);
should(treeItem.label).equal(mockResourceRootNode.treeItem.label);
should(treeItem.collapsibleState).equal(mockResourceRootNode.treeItem.collapsibleState);
should(treeItem.contextValue).equal(mockResourceRootNode.treeItem.contextValue);
});
});
describe('AzureResourceDatabaseTreeDataProvider.getChildren', function(): void {
beforeEach(() => {
mockDatabaseService = TypeMoq.Mock.ofType<IAzureResourceDatabaseService>();
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
mockApiWrapper.setup((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockTokens));
mockDatabaseService.setup((o) => o.getDatabases(mockSubscription, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockDatabases));
mockExtensionContext.setup((o) => o.asAbsolutePath(TypeMoq.It.isAnyString())).returns(() => TypeMoq.It.isAnyString());
});
it('Should return container node when element is undefined.', async function(): Promise<void> {
const treeDataProvider = new AzureResourceDatabaseTreeDataProvider(mockDatabaseService.object, mockApiWrapper.object, mockExtensionContext.object);
const children = await treeDataProvider.getChildren();
should(children).Array();
should(children.length).equal(1);
const child = children[0];
should(child.account).undefined();
should(child.subscription).undefined();
should(child.tenantId).undefined();
should(child.treeItem.id).equal('azure.resource.providers.database.treeDataProvider.databaseContainer');
should(child.treeItem.label).equal('SQL Databases');
should(child.treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed);
should(child.treeItem.contextValue).equal('azure.resource.itemType.databaseContainer');
});
it('Should return resource nodes when it is container node.', async function(): Promise<void> {
const treeDataProvider = new AzureResourceDatabaseTreeDataProvider(mockDatabaseService.object, mockApiWrapper.object, mockExtensionContext.object);
const children = await treeDataProvider.getChildren(mockResourceRootNode);
should(children).Array();
should(children.length).equal(mockDatabases.length);
for (let ix = 0; ix < children.length; ix++) {
const child = children[ix];
const database = mockDatabases[ix];
should(child.account).equal(mockAccount);
should(child.subscription).equal(mockSubscription);
should(child.tenantId).equal(mockTenantId);
should(child.treeItem.id).equal(`databaseServer_${database.serverFullName}.database_${database.name}`);
should(child.treeItem.label).equal(`${database.name} (${database.serverName})`);
should(child.treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.None);
should(child.treeItem.contextValue).equal(AzureResourceItemType.database);
}
});
});

View File

@@ -0,0 +1,150 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as should from 'should';
import * as TypeMoq from 'typemoq';
import * as sqlops from 'sqlops';
import * as vscode from 'vscode';
import 'mocha';
import { azureResource } from '../../../../azureResource/azure-resource';
import { ApiWrapper } from '../../../../apiWrapper';
import { IAzureResourceDatabaseServerService } from '../../../../azureResource/providers/databaseServer/interfaces';
import { AzureResourceDatabaseServerTreeDataProvider } from '../../../../azureResource/providers/databaseServer/databaseServerTreeDataProvider';
import { AzureResourceDatabaseServer } from '../../../../azureResource/providers/databaseServer/models';
import { AzureResourceItemType } from '../../../../azureResource/constants';
// Mock services
let mockDatabaseServerService: TypeMoq.IMock<IAzureResourceDatabaseServerService>;
let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
// Mock test data
const mockAccount: sqlops.Account = {
key: {
accountId: 'mock_account',
providerId: 'mock_provider'
},
displayInfo: {
displayName: 'mock_account@test.com',
accountType: 'Microsoft',
contextualDisplayName: 'test'
},
properties: undefined,
isStale: false
};
const mockSubscription: azureResource.AzureResourceSubscription = {
id: 'mock_subscription',
name: 'mock subscription'
};
const mockTenantId: string = 'mock_tenant';
const mockResourceRootNode: azureResource.IAzureResourceNode = {
account: mockAccount,
subscription: mockSubscription,
tenantId: mockTenantId,
treeItem: {
id: 'mock_resource_root_node',
label: 'mock resource root node',
iconPath: undefined,
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
contextValue: 'mock_resource_root_node'
}
};
const mockTokens = {};
mockTokens[mockTenantId] = {
token: 'mock_token',
tokenType: 'Bearer'
};
const mockDatabaseServers: AzureResourceDatabaseServer[] = [
{
name: 'mock database server 1',
fullName: 'mock database server full name 1',
loginName: 'mock login',
defaultDatabaseName: 'master'
},
{
name: 'mock database server 2',
fullName: 'mock database server full name 2',
loginName: 'mock login',
defaultDatabaseName: 'master'
}
];
describe('AzureResourceDatabaseServerTreeDataProvider.info', function(): void {
beforeEach(() => {
mockDatabaseServerService = TypeMoq.Mock.ofType<IAzureResourceDatabaseServerService>();
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
});
it('Should be correct when created.', async function(): Promise<void> {
const treeDataProvider = new AzureResourceDatabaseServerTreeDataProvider(mockDatabaseServerService.object, mockApiWrapper.object, mockExtensionContext.object);
const treeItem = await treeDataProvider.getTreeItem(mockResourceRootNode);
should(treeItem.id).equal(mockResourceRootNode.treeItem.id);
should(treeItem.label).equal(mockResourceRootNode.treeItem.label);
should(treeItem.collapsibleState).equal(mockResourceRootNode.treeItem.collapsibleState);
should(treeItem.contextValue).equal(mockResourceRootNode.treeItem.contextValue);
});
});
describe('AzureResourceDatabaseServerTreeDataProvider.getChildren', function(): void {
beforeEach(() => {
mockDatabaseServerService = TypeMoq.Mock.ofType<IAzureResourceDatabaseServerService>();
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
mockApiWrapper.setup((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockTokens));
mockDatabaseServerService.setup((o) => o.getDatabaseServers(mockSubscription, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockDatabaseServers));
mockExtensionContext.setup((o) => o.asAbsolutePath(TypeMoq.It.isAnyString())).returns(() => TypeMoq.It.isAnyString());
});
it('Should return container node when element is undefined.', async function(): Promise<void> {
const treeDataProvider = new AzureResourceDatabaseServerTreeDataProvider(mockDatabaseServerService.object, mockApiWrapper.object, mockExtensionContext.object);
const children = await treeDataProvider.getChildren();
should(children).Array();
should(children.length).equal(1);
const child = children[0];
should(child.account).undefined();
should(child.subscription).undefined();
should(child.tenantId).undefined();
should(child.treeItem.id).equal('azure.resource.providers.databaseServer.treeDataProvider.databaseServerContainer');
should(child.treeItem.label).equal('SQL Servers');
should(child.treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed);
should(child.treeItem.contextValue).equal('azure.resource.itemType.databaseServerContainer');
});
it('Should return resource nodes when it is container node.', async function(): Promise<void> {
const treeDataProvider = new AzureResourceDatabaseServerTreeDataProvider(mockDatabaseServerService.object, mockApiWrapper.object, mockExtensionContext.object);
const children = await treeDataProvider.getChildren(mockResourceRootNode);
should(children).Array();
should(children.length).equal(mockDatabaseServers.length);
for (let ix = 0; ix < children.length; ix++) {
const child = children[ix];
const databaseServer = mockDatabaseServers[ix];
should(child.account).equal(mockAccount);
should(child.subscription).equal(mockSubscription);
should(child.tenantId).equal(mockTenantId);
should(child.treeItem.id).equal(`databaseServer_${databaseServer.name}`);
should(child.treeItem.label).equal(databaseServer.name);
should(child.treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.None);
should(child.treeItem.contextValue).equal(AzureResourceItemType.databaseServer);
}
});
});

View File

@@ -0,0 +1,180 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as should from 'should';
import * as TypeMoq from 'typemoq';
import * as sqlops from 'sqlops';
import 'mocha';
import { fail } from 'assert';
import { azureResource } from '../../azureResource/azure-resource';
import { AzureResourceService } from '../../azureResource/resourceService';
// Mock test data
const mockAccount: sqlops.Account = {
key: {
accountId: 'mock_account',
providerId: 'mock_provider'
},
displayInfo: {
displayName: 'mock_account@test.com',
accountType: 'Microsoft',
contextualDisplayName: 'test'
},
properties: undefined,
isStale: false
};
const mockSubscription: azureResource.AzureResourceSubscription = {
id: 'mock_subscription',
name: 'mock subscription'
};
const mockTenantId: string = 'mock_tenant';
let mockResourceTreeDataProvider1: TypeMoq.IMock<azureResource.IAzureResourceTreeDataProvider>;
let mockResourceProvider1: TypeMoq.IMock<azureResource.IAzureResourceProvider>;
let mockResourceTreeDataProvider2: TypeMoq.IMock<azureResource.IAzureResourceTreeDataProvider>;
let mockResourceProvider2: TypeMoq.IMock<azureResource.IAzureResourceProvider>;
const resourceService: AzureResourceService = AzureResourceService.getInstance();
describe('AzureResourceService.listResourceProviderIds', function(): void {
beforeEach(() => {
mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
mockResourceTreeDataProvider1.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
mockResourceTreeDataProvider1.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
mockResourceProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1');
mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object);
mockResourceTreeDataProvider2 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
mockResourceTreeDataProvider2.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
mockResourceTreeDataProvider2.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
mockResourceProvider2 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
mockResourceProvider2.setup((o) => o.providerId).returns(() => 'mockResourceProvider2');
mockResourceProvider2.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider2.object);
resourceService.clearResourceProviders();
resourceService.areResourceProvidersLoaded = true;
});
it('Should be correct when registering providers.', async function(): Promise<void> {
resourceService.registerResourceProvider(mockResourceProvider1.object);
let providerIds = await resourceService.listResourceProviderIds();
should(providerIds).Array();
should(providerIds.length).equal(1);
should(providerIds[0]).equal(mockResourceProvider1.object.providerId);
resourceService.registerResourceProvider(mockResourceProvider2.object);
providerIds = await resourceService.listResourceProviderIds();
should(providerIds).Array();
should(providerIds.length).equal(2);
should(providerIds[0]).equal(mockResourceProvider1.object.providerId);
should(providerIds[1]).equal(mockResourceProvider2.object.providerId);
});
});
describe('AzureResourceService.getRootChildren', function(): void {
beforeEach(() => {
mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
mockResourceTreeDataProvider1.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
mockResourceTreeDataProvider1.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
mockResourceProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1');
mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object);
resourceService.clearResourceProviders();
resourceService.registerResourceProvider(mockResourceProvider1.object);
resourceService.areResourceProvidersLoaded = true;
});
it('Should be correct when provider id is correct.', async function(): Promise<void> {
const children = await resourceService.getRootChildren(mockResourceProvider1.object.providerId, mockAccount, mockSubscription, mockTenantId);
should(children).Array();
});
it('Should throw exceptions when provider id is incorrect.', async function(): Promise<void> {
const providerId = 'non_existent_provider_id';
try {
await resourceService.getRootChildren(providerId, mockAccount, mockSubscription, mockTenantId);
} catch (error) {
should(error.message).equal(`Azure resource provider doesn't exist. Id: ${providerId}`);
return;
}
fail();
});
});
describe('AzureResourceService.getChildren', function(): void {
beforeEach(() => {
mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
mockResourceTreeDataProvider1.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
mockResourceTreeDataProvider1.setup((o) => o.getChildren(TypeMoq.It.isAny())).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
mockResourceTreeDataProvider1.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
mockResourceProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1');
mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object);
resourceService.clearResourceProviders();
resourceService.registerResourceProvider(mockResourceProvider1.object);
resourceService.areResourceProvidersLoaded = true;
});
it('Should be correct when provider id is correct.', async function(): Promise<void> {
const children = await resourceService.getChildren(mockResourceProvider1.object.providerId, TypeMoq.It.isAny());
should(children).Array();
});
it('Should throw exceptions when provider id is incorrect.', async function(): Promise<void> {
const providerId = 'non_existent_provider_id';
try {
await resourceService.getRootChildren(providerId, mockAccount, mockSubscription, mockTenantId);
} catch (error) {
should(error.message).equal(`Azure resource provider doesn't exist. Id: ${providerId}`);
return;
}
fail();
});
});
describe('AzureResourceService.getTreeItem', function(): void {
beforeEach(() => {
mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
mockResourceTreeDataProvider1.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
mockResourceTreeDataProvider1.setup((o) => o.getChildren(TypeMoq.It.isAny())).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
mockResourceTreeDataProvider1.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
mockResourceProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1');
mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object);
resourceService.clearResourceProviders();
resourceService.registerResourceProvider(mockResourceProvider1.object);
resourceService.areResourceProvidersLoaded = true;
});
it('Should be correct when provider id is correct.', async function(): Promise<void> {
const treeItem = await resourceService.getTreeItem(mockResourceProvider1.object.providerId, TypeMoq.It.isAny());
should(treeItem).Object();
});
it('Should throw exceptions when provider id is incorrect.', async function(): Promise<void> {
const providerId = 'non_existent_provider_id';
try {
await resourceService.getRootChildren(providerId, mockAccount, mockSubscription, mockTenantId);
} catch (error) {
should(error.message).equal(`Azure resource provider doesn't exist. Id: ${providerId}`);
return;
}
fail();
});
});

View File

@@ -0,0 +1,184 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as should from 'should';
import * as TypeMoq from 'typemoq';
import * as sqlops from 'sqlops';
import * as vscode from 'vscode';
import 'mocha';
import { azureResource } from '../../azureResource/azure-resource';
import { AzureResourceService } from '../../azureResource/resourceService';
import { AzureResourceResourceTreeNode } from '../../azureResource/resourceTreeNode';
const resourceService = AzureResourceService.getInstance();
// Mock test data
const mockAccount: sqlops.Account = {
key: {
accountId: 'mock_account',
providerId: 'mock_provider'
},
displayInfo: {
displayName: 'mock_account@test.com',
accountType: 'Microsoft',
contextualDisplayName: 'test'
},
properties: undefined,
isStale: false
};
const mockSubscription: azureResource.AzureResourceSubscription = {
id: 'mock_subscription',
name: 'mock subscription'
};
const mockTenantId: string = 'mock_tenant';
const mockResourceProviderId: string = 'mock_resource_provider';
const mockResourceRootNode: azureResource.IAzureResourceNode = {
account: mockAccount,
subscription: mockSubscription,
tenantId: mockTenantId,
treeItem: {
id: 'mock_resource_root_node',
label: 'mock resource root node',
iconPath: undefined,
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
contextValue: 'mock_resource_root_node'
}
};
const mockResourceNode1: azureResource.IAzureResourceNode = {
account: mockAccount,
subscription: mockSubscription,
tenantId: mockTenantId,
treeItem: {
id: 'mock_resource_node_1',
label: 'mock resource node 1',
iconPath: undefined,
collapsibleState: vscode.TreeItemCollapsibleState.None,
contextValue: 'mock_resource_node'
}
};
const mockResourceNode2: azureResource.IAzureResourceNode = {
account: mockAccount,
subscription: mockSubscription,
tenantId: mockTenantId,
treeItem: {
id: 'mock_resource_node_2',
label: 'mock resource node 2',
iconPath: undefined,
collapsibleState: vscode.TreeItemCollapsibleState.None,
contextValue: 'mock_resource_node'
}
};
const mockResourceNodes: azureResource.IAzureResourceNode[] = [mockResourceNode1, mockResourceNode2];
let mockResourceTreeDataProvider: TypeMoq.IMock<azureResource.IAzureResourceTreeDataProvider>;
let mockResourceProvider: TypeMoq.IMock<azureResource.IAzureResourceProvider>;
describe('AzureResourceResourceTreeNode.info', function(): void {
beforeEach(() => {
mockResourceTreeDataProvider = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
mockResourceTreeDataProvider.setup((o) => o.getTreeItem(mockResourceRootNode)).returns(() => mockResourceRootNode.treeItem);
mockResourceTreeDataProvider.setup((o) => o.getChildren(mockResourceRootNode)).returns(() => Promise.resolve(mockResourceNodes));
mockResourceProvider = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
mockResourceProvider.setup((o) => o.providerId).returns(() => mockResourceProviderId);
mockResourceProvider.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider.object);
resourceService.clearResourceProviders();
resourceService.registerResourceProvider(mockResourceProvider.object);
resourceService.areResourceProvidersLoaded = true;
});
it('Should be correct when created.', async function(): Promise<void> {
const resourceTreeNode = new AzureResourceResourceTreeNode({
resourceProviderId: mockResourceProviderId,
resourceNode: mockResourceRootNode
}, undefined);
should(resourceTreeNode.nodePathValue).equal(mockResourceRootNode.treeItem.id);
const treeItem = await resourceTreeNode.getTreeItem();
should(treeItem.id).equal(mockResourceRootNode.treeItem.id);
should(treeItem.label).equal(mockResourceRootNode.treeItem.label);
should(treeItem.collapsibleState).equal(mockResourceRootNode.treeItem.collapsibleState);
should(treeItem.contextValue).equal(mockResourceRootNode.treeItem.contextValue);
const nodeInfo = resourceTreeNode.getNodeInfo();
should(nodeInfo.label).equal(mockResourceRootNode.treeItem.label);
should(nodeInfo.isLeaf).equal(mockResourceRootNode.treeItem.collapsibleState === vscode.TreeItemCollapsibleState.None);
should(nodeInfo.nodeType).equal(mockResourceRootNode.treeItem.contextValue);
should(nodeInfo.iconType).equal(mockResourceRootNode.treeItem.contextValue);
});
});
describe('AzureResourceResourceTreeNode.getChildren', function(): void {
beforeEach(() => {
mockResourceTreeDataProvider = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
mockResourceTreeDataProvider.setup((o) => o.getChildren(mockResourceRootNode)).returns(() => Promise.resolve(mockResourceNodes));
mockResourceProvider = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
mockResourceProvider.setup((o) => o.providerId).returns(() => mockResourceProviderId);
mockResourceProvider.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider.object);
resourceService.clearResourceProviders();
resourceService.registerResourceProvider(mockResourceProvider.object);
resourceService.areResourceProvidersLoaded = true;
});
it('Should return resource nodes when it is container node.', async function(): Promise<void> {
const resourceTreeNode = new AzureResourceResourceTreeNode({
resourceProviderId: mockResourceProviderId,
resourceNode: mockResourceRootNode
}, undefined);
const children = await resourceTreeNode.getChildren();
mockResourceTreeDataProvider.verify((o) => o.getChildren(mockResourceRootNode), TypeMoq.Times.once());
should(children).Array();
should(children.length).equal(mockResourceNodes.length);
for (let ix = 0; ix < children.length; ix++) {
const child = children[ix];
should(child).instanceOf(AzureResourceResourceTreeNode);
const childNode = (child as AzureResourceResourceTreeNode).resourceNodeWithProviderId;
should(childNode.resourceProviderId).equal(mockResourceProviderId);
should(childNode.resourceNode.account).equal(mockAccount);
should(childNode.resourceNode.subscription).equal(mockSubscription);
should(childNode.resourceNode.tenantId).equal(mockTenantId);
should(childNode.resourceNode.treeItem.id).equal(mockResourceNodes[ix].treeItem.id);
should(childNode.resourceNode.treeItem.label).equal(mockResourceNodes[ix].treeItem.label);
should(childNode.resourceNode.treeItem.collapsibleState).equal(mockResourceNodes[ix].treeItem.collapsibleState);
should(childNode.resourceNode.treeItem.contextValue).equal(mockResourceNodes[ix].treeItem.contextValue);
}
});
it('Should return empty when it is leaf node.', async function(): Promise<void> {
const resourceTreeNode = new AzureResourceResourceTreeNode({
resourceProviderId: mockResourceProviderId,
resourceNode: mockResourceNode1
}, undefined);
const children = await resourceTreeNode.getChildren();
mockResourceTreeDataProvider.verify((o) => o.getChildren(), TypeMoq.Times.exactly(0));
should(children).Array();
should(children.length).equal(0);
});
});

View File

@@ -26,7 +26,7 @@ describe('AzureResourceAccountNotSignedInTreeNode.info', function(): void {
should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.None);
should(treeItem.command).not.undefined();
should(treeItem.command.title).equal(label);
should(treeItem.command.command).equal('azureresource.signin');
should(treeItem.command.command).equal('azure.resource.signin');
const nodeInfo = treeNode.getNodeInfo();
should(nodeInfo.isLeaf).true();

View File

@@ -10,35 +10,38 @@ import * as TypeMoq from 'typemoq';
import * as sqlops from 'sqlops';
import * as vscode from 'vscode';
import 'mocha';
import { ServiceClientCredentials } from 'ms-rest';
import { TokenCredentials } from 'ms-rest';
import { AppContext } from '../../../appContext';
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
import { azureResource } from '../../../azureResource/azure-resource';
import {
IAzureResourceCacheService,
IAzureResourceContextService,
IAzureResourceCredentialService,
IAzureResourceSubscriptionService,
IAzureResourceSubscriptionFilterService
IAzureResourceSubscriptionFilterService,
IAzureResourceTenantService
} from '../../../azureResource/interfaces';
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
import { AzureResourceAccountTreeNode } from '../../../azureResource/tree/accountTreeNode';
import { AzureResourceSubscription } from '../../../azureResource/models';
import { AzureResourceSubscriptionTreeNode } from '../../../azureResource/tree/subscriptionTreeNode';
import { AzureResourceItemType } from '../../../azureResource/constants';
import { AzureResourceMessageTreeNode } from '../../../azureResource/tree/messageTreeNode';
import { AzureResourceItemType, AzureResourceServiceNames } from '../../../azureResource/constants';
import { AzureResourceMessageTreeNode } from '../../../azureResource/messageTreeNode';
import { ApiWrapper } from '../../../apiWrapper';
import { generateGuid } from '../../../azureResource/utils';
// Mock services
const mockServicePool = AzureResourceServicePool.getInstance();
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
let mockCacheService: TypeMoq.IMock<IAzureResourceCacheService>;
let mockContextService: TypeMoq.IMock<IAzureResourceContextService>;
let mockCredentialService: TypeMoq.IMock<IAzureResourceCredentialService>;
let mockSubscriptionService: TypeMoq.IMock<IAzureResourceSubscriptionService>;
let mockSubscriptionFilterService: TypeMoq.IMock<IAzureResourceSubscriptionFilterService>;
let mockTenantService: TypeMoq.IMock<IAzureResourceTenantService>;
let mockAppContext: AppContext;
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
// Mock test data
const mockTenantId = 'mock_tenant_id';
const mockAccount: sqlops.Account = {
key: {
accountId: 'mock_account',
@@ -49,51 +52,68 @@ const mockAccount: sqlops.Account = {
accountType: 'Microsoft',
contextualDisplayName: 'test'
},
properties: undefined,
properties: {
tenants: [
{
id: mockTenantId
}
]
},
isStale: false
};
const mockCredential = TypeMoq.Mock.ofType<ServiceClientCredentials>().object;
const mockCredentials = [mockCredential];
const mockSubscription1: AzureResourceSubscription = {
const mockSubscription1: azureResource.AzureResourceSubscription = {
id: 'mock_subscription_1',
name: 'mock subscription 1'
};
const mockSubscription2: AzureResourceSubscription = {
const mockSubscription2: azureResource.AzureResourceSubscription = {
id: 'mock_subscription_2',
name: 'mock subscription 2'
};
const mockSubscriptions = [mockSubscription1, mockSubscription2];
const mockFilteredSubscriptions = [mockSubscription1];
let mockSubscriptionCache: { subscriptions: { [accountId: string]: AzureResourceSubscription[]} };
const mockTokens = {};
mockTokens[mockTenantId] = {
token: 'mock_token',
tokenType: 'Bearer'
};
const mockCredential = new TokenCredentials(mockTokens[mockTenantId].token, mockTokens[mockTenantId].tokenType);
let mockSubscriptionCache: azureResource.AzureResourceSubscription[] = [];
describe('AzureResourceAccountTreeNode.info', function(): void {
beforeEach(() => {
mockContextService = TypeMoq.Mock.ofType<IAzureResourceContextService>();
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
mockCredentialService = TypeMoq.Mock.ofType<IAzureResourceCredentialService>();
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
mockTenantService = TypeMoq.Mock.ofType<IAzureResourceTenantService>();
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
mockSubscriptionCache = { subscriptions: {} };
mockSubscriptionCache = [];
mockServicePool.contextService = mockContextService.object;
mockServicePool.cacheService = mockCacheService.object;
mockServicePool.credentialService = mockCredentialService.object;
mockServicePool.subscriptionService = mockSubscriptionService.object;
mockServicePool.subscriptionFilterService = mockSubscriptionFilterService.object;
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object);
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
mockAppContext.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionService.object);
mockAppContext.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
mockAppContext.registerService<IAzureResourceTenantService>(AzureResourceServiceNames.tenantService, mockTenantService.object);
mockCredentialService.setup((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockCredentials));
mockApiWrapper.setup((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockTokens));
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
mockCacheService.setup((o) => o.get(TypeMoq.It.isAnyString())).returns(() => mockSubscriptionCache);
mockCacheService.setup((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => mockSubscriptionCache.subscriptions[mockAccount.key.accountId] = mockSubscriptions);
mockCacheService.setup((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => mockSubscriptionCache = mockSubscriptions);
mockTenantService.setup((o) => o.getTenantId(TypeMoq.It.isAny())).returns(() => Promise.resolve(mockTenantId));
});
it('Should be correct when created.', async function(): Promise<void> {
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
const accountTreeNodeId = `account_${mockAccount.key.accountId}`;
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockAccount.key.accountId})`;
@@ -114,14 +134,17 @@ describe('AzureResourceAccountTreeNode.info', function(): void {
});
it('Should be correct when there are subscriptions listed.', async function(): Promise<void> {
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(undefined));
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockAccount.key.accountId}) (${mockSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
await accountTreeNode.getChildren();
const subscriptionNodes = await accountTreeNode.getChildren();
should(subscriptionNodes).Array();
should(subscriptionNodes.length).equal(mockSubscriptions.length);
const treeItem = await accountTreeNode.getTreeItem();
should(treeItem.label).equal(accountTreeNodeLabel);
@@ -131,14 +154,17 @@ describe('AzureResourceAccountTreeNode.info', function(): void {
});
it('Should be correct when there are subscriptions filtered.', async function(): Promise<void> {
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(mockFilteredSubscriptions));
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockAccount.key.accountId}) (${mockFilteredSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
await accountTreeNode.getChildren();
const subscriptionNodes = await accountTreeNode.getChildren();
should(subscriptionNodes).Array();
should(subscriptionNodes.length).equal(mockFilteredSubscriptions.length);
const treeItem = await accountTreeNode.getTreeItem();
should(treeItem.label).equal(accountTreeNodeLabel);
@@ -150,36 +176,41 @@ describe('AzureResourceAccountTreeNode.info', function(): void {
describe('AzureResourceAccountTreeNode.getChildren', function(): void {
beforeEach(() => {
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
mockCredentialService = TypeMoq.Mock.ofType<IAzureResourceCredentialService>();
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
mockTenantService = TypeMoq.Mock.ofType<IAzureResourceTenantService>();
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
mockSubscriptionCache = { subscriptions: {} };
mockSubscriptionCache = [];
mockServicePool.cacheService = mockCacheService.object;
mockServicePool.credentialService = mockCredentialService.object;
mockServicePool.subscriptionService = mockSubscriptionService.object;
mockServicePool.subscriptionFilterService = mockSubscriptionFilterService.object;
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object);
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
mockAppContext.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionService.object);
mockAppContext.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
mockAppContext.registerService<IAzureResourceTenantService>(AzureResourceServiceNames.tenantService, mockTenantService.object);
mockCredentialService.setup((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockCredentials));
mockApiWrapper.setup((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockTokens));
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
mockCacheService.setup((o) => o.get(TypeMoq.It.isAnyString())).returns(() => mockSubscriptionCache);
mockCacheService.setup((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => mockSubscriptionCache.subscriptions[mockAccount.key.accountId] = mockSubscriptions);
mockCacheService.setup((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => mockSubscriptionCache = mockSubscriptions);
mockTenantService.setup((o) => o.getTenantId(TypeMoq.It.isAny())).returns(() => Promise.resolve(mockTenantId));
});
it('Should load subscriptions from scratch and update cache when it is clearing cache.', async function(): Promise<void> {
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(undefined));
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve([]));
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
const children = await accountTreeNode.getChildren();
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredentials), TypeMoq.Times.once());
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.once());
mockApiWrapper.verify((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredential), TypeMoq.Times.once());
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(0));
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
mockSubscriptionFilterService.verify((o) => o.getSelectedSubscriptions(mockAccount), TypeMoq.Times.once());
@@ -192,43 +223,42 @@ describe('AzureResourceAccountTreeNode.getChildren', function(): void {
should(children).Array();
should(children.length).equal(mockSubscriptions.length);
should(Object.keys(mockSubscriptionCache.subscriptions)).deepEqual([mockAccount.key.accountId]);
should(mockSubscriptionCache.subscriptions[mockAccount.key.accountId]).deepEqual(mockSubscriptions);
should(mockSubscriptionCache).deepEqual(mockSubscriptions);
for (let ix = 0; ix < mockSubscriptions.length; ix++) {
const child = children[ix];
const subscription = mockSubscriptions[ix];
should(child).instanceof(AzureResourceSubscriptionTreeNode);
should(child.nodePathValue).equal(`subscription_${subscription.id}`);
should(child.nodePathValue).equal(`account_${mockAccount.key.accountId}.subscription_${subscription.id}.tenant_${mockTenantId}`);
}
});
it('Should load subscriptions from cache when it is not clearing cache.', async function(): Promise<void> {
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(undefined));
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
await accountTreeNode.getChildren();
const children = await accountTreeNode.getChildren();
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.exactly(1));
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredentials), TypeMoq.Times.exactly(1));
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(2));
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.exactly(1));
mockApiWrapper.verify((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredential), TypeMoq.Times.once());
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.once());
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
should(children.length).equal(mockSubscriptionCache.subscriptions[mockAccount.key.accountId].length);
should(children.length).equal(mockSubscriptionCache.length);
for (let ix = 0; ix < mockSubscriptionCache.subscriptions[mockAccount.key.accountId].length; ix++) {
should(children[ix].nodePathValue).equal(`subscription_${mockSubscriptionCache.subscriptions[mockAccount.key.accountId][ix].id}`);
for (let ix = 0; ix < mockSubscriptionCache.length; ix++) {
should(children[ix].nodePathValue).equal(`account_${mockAccount.key.accountId}.subscription_${mockSubscriptionCache[ix].id}.tenant_${mockTenantId}`);
}
});
it('Should handle when there is no subscriptions.', async function(): Promise<void> {
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => Promise.resolve(undefined));
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(undefined));
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
const children = await accountTreeNode.getChildren();
@@ -242,10 +272,10 @@ describe('AzureResourceAccountTreeNode.getChildren', function(): void {
});
it('Should honor subscription filtering.', async function(): Promise<void> {
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(mockFilteredSubscriptions));
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
const children = await accountTreeNode.getChildren();
@@ -255,23 +285,25 @@ describe('AzureResourceAccountTreeNode.getChildren', function(): void {
should(children.length).equal(mockFilteredSubscriptions.length);
for (let ix = 0; ix < mockFilteredSubscriptions.length; ix++) {
should(children[ix].nodePathValue).equal(`subscription_${mockFilteredSubscriptions[ix].id}`);
should(children[ix].nodePathValue).equal(`account_${mockAccount.key.accountId}.subscription_${mockFilteredSubscriptions[ix].id}.tenant_${mockTenantId}`);
}
});
it('Should handle errors.', async function(): Promise<void> {
const mockError = 'Test error';
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => { throw new Error(mockError); });
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(mockSubscriptions));
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
const mockError = 'Test error';
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => { throw new Error(mockError); });
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
const children = await accountTreeNode.getChildren();
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredentials), TypeMoq.Times.once());
mockApiWrapper.verify((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredential), TypeMoq.Times.once());
mockSubscriptionFilterService.verify((o) => o.getSelectedSubscriptions(mockAccount), TypeMoq.Times.once());
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.never());
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.never());
mockSubscriptionFilterService.verify((o) => o.getSelectedSubscriptions(mockAccount), TypeMoq.Times.never());
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
should(children).Array();
should(children.length).equal(1);
@@ -283,12 +315,33 @@ describe('AzureResourceAccountTreeNode.getChildren', function(): void {
describe('AzureResourceAccountTreeNode.clearCache', function() : void {
beforeEach(() => {
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
mockTenantService = TypeMoq.Mock.ofType<IAzureResourceTenantService>();
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
mockSubscriptionCache = [];
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object);
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
mockAppContext.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionService.object);
mockAppContext.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
mockAppContext.registerService<IAzureResourceTenantService>(AzureResourceServiceNames.tenantService, mockTenantService.object);
mockApiWrapper.setup((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockTokens));
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
mockCacheService.setup((o) => o.get(TypeMoq.It.isAnyString())).returns(() => mockSubscriptionCache);
mockCacheService.setup((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => mockSubscriptionCache = mockSubscriptions);
mockTenantService.setup((o) => o.getTenantId(TypeMoq.It.isAny())).returns(() => Promise.resolve(mockTenantId));
});
it('Should clear cache.', async function(): Promise<void> {
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
accountTreeNode.clearCache();
should(accountTreeNode.isClearingCache).true();
});
});
});

View File

@@ -1,219 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as should from 'should';
import * as TypeMoq from 'typemoq';
import * as sqlops from 'sqlops';
import * as vscode from 'vscode';
import 'mocha';
import { ServiceClientCredentials } from 'ms-rest';
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
import {
IAzureResourceCacheService,
IAzureResourceContextService,
IAzureResourceCredentialService,
IAzureResourceDatabaseService
} from '../../../azureResource/interfaces';
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
import { AzureResourceSubscription, AzureResourceDatabase } from '../../../azureResource/models';
import { AzureResourceItemType } from '../../../azureResource/constants';
import { AzureResourceMessageTreeNode } from '../../../azureResource/tree/messageTreeNode';
import { AzureResourceDatabaseContainerTreeNode } from '../../../azureResource/tree/databaseContainerTreeNode';
import { AzureResourceDatabaseTreeNode } from '../../../azureResource/tree/databaseTreeNode';
// Mock services
const mockServicePool = AzureResourceServicePool.getInstance();
let mockCacheService: TypeMoq.IMock<IAzureResourceCacheService>;
let mockContextService: TypeMoq.IMock<IAzureResourceContextService>;
let mockCredentialService: TypeMoq.IMock<IAzureResourceCredentialService>;
let mockDatabaseService: TypeMoq.IMock<IAzureResourceDatabaseService>;
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
// Mock test data
const mockAccount: sqlops.Account = {
key: {
accountId: 'mock_account',
providerId: 'mock_provider'
},
displayInfo: {
displayName: 'mock_account@test.com',
accountType: 'Microsoft',
contextualDisplayName: 'test'
},
properties: undefined,
isStale: false
};
const mockCredential = TypeMoq.Mock.ofType<ServiceClientCredentials>().object;
const mockCredentials = [mockCredential];
const mockSubscription: AzureResourceSubscription = {
id: 'mock_subscription',
name: 'mock subscription'
};
const mockDatabase1: AzureResourceDatabase = {
name: 'mock database 1',
serverName: 'mock server 1',
serverFullName: 'mock server 1',
loginName: 'mock user 1'
};
const mockDatabase2: AzureResourceDatabase = {
name: 'mock database 2',
serverName: 'mock server 2',
serverFullName: 'mock server 2',
loginName: 'mock user 2'
};
const mockDatabases = [mockDatabase1, mockDatabase2];
let mockDatabaseContainerCache: { databases: { [subscriptionId: string]: AzureResourceDatabase[] } };
describe('AzureResourceDatabaseContainerTreeNode.info', function(): void {
beforeEach(() => {
mockContextService = TypeMoq.Mock.ofType<IAzureResourceContextService>();
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
mockServicePool.contextService = mockContextService.object;
});
it('Should be correct when created.', async function(): Promise<void> {
const databaseContainerTreeNode = new AzureResourceDatabaseContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
const databaseContainerTreeNodeLabel = 'SQL Databases';
should(databaseContainerTreeNode.nodePathValue).equal('databaseContainer');
const treeItem = await databaseContainerTreeNode.getTreeItem();
should(treeItem.label).equal(databaseContainerTreeNodeLabel);
should(treeItem.contextValue).equal(AzureResourceItemType.databaseContainer);
should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed);
const nodeInfo = databaseContainerTreeNode.getNodeInfo();
should(nodeInfo.isLeaf).false();
should(nodeInfo.label).equal(databaseContainerTreeNodeLabel);
should(nodeInfo.nodeType).equal(AzureResourceItemType.databaseContainer);
should(nodeInfo.iconType).equal(AzureResourceItemType.databaseContainer);
});
});
describe('AzureResourceDatabaseContainerTreeNode.getChildren', function(): void {
beforeEach(() => {
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
mockCredentialService = TypeMoq.Mock.ofType<IAzureResourceCredentialService>();
mockDatabaseService = TypeMoq.Mock.ofType<IAzureResourceDatabaseService>();
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
mockDatabaseContainerCache = { databases: {} };
mockServicePool.cacheService = mockCacheService.object;
mockServicePool.credentialService = mockCredentialService.object;
mockServicePool.databaseService = mockDatabaseService.object;
mockCredentialService.setup((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockCredentials));
mockCacheService.setup((o) => o.get(TypeMoq.It.isAnyString())).returns(() => mockDatabaseContainerCache);
mockCacheService.setup((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => mockDatabaseContainerCache.databases[mockSubscription.id] = mockDatabases);
});
it('Should load databases from scratch and update cache when it is clearing cache.', async function(): Promise<void> {
mockDatabaseService.setup((o) => o.getDatabases(mockSubscription, mockCredentials)).returns(() => Promise.resolve(mockDatabases));
const databaseContainerTreeNode = new AzureResourceDatabaseContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
const children = await databaseContainerTreeNode.getChildren();
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
mockDatabaseService.verify((o) => o.getDatabases(mockSubscription, mockCredentials), TypeMoq.Times.once());
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.once());
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
should(databaseContainerTreeNode.isClearingCache).false();
should(children).Array();
should(children.length).equal(mockDatabases.length);
should(Object.keys(mockDatabaseContainerCache.databases)).deepEqual([mockSubscription.id]);
should(mockDatabaseContainerCache.databases[mockSubscription.id]).deepEqual(mockDatabases);
for (let ix = 0; ix < mockDatabases.length; ix++) {
const child = children[ix];
const database = mockDatabases[ix];
should(child).instanceof(AzureResourceDatabaseTreeNode);
should(child.nodePathValue).equal(`database_${database.name}`);
}
});
it('Should load databases from cache when it is not clearing cache.', async function(): Promise<void> {
mockDatabaseService.setup((o) => o.getDatabases(mockSubscription, mockCredentials)).returns(() => Promise.resolve(mockDatabases));
const databaseContainerTreeNode = new AzureResourceDatabaseContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
await databaseContainerTreeNode.getChildren();
const children = await databaseContainerTreeNode.getChildren();
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.exactly(1));
mockDatabaseService.verify((o) => o.getDatabases(mockSubscription, mockCredentials), TypeMoq.Times.exactly(1));
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(2));
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.exactly(1));
should(children.length).equal(mockDatabaseContainerCache.databases[mockSubscription.id].length);
for (let ix = 0; ix < mockDatabaseContainerCache.databases[mockSubscription.id].length; ix++) {
should(children[ix].nodePathValue).equal(`database_${mockDatabaseContainerCache.databases[mockSubscription.id][ix].name}`);
}
});
it('Should handle when there is no databases.', async function(): Promise<void> {
mockDatabaseService.setup((o) => o.getDatabases(mockSubscription, mockCredentials)).returns(() => Promise.resolve(undefined));
const databaseContainerTreeNode = new AzureResourceDatabaseContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
const children = await databaseContainerTreeNode.getChildren();
should(children).Array();
should(children.length).equal(1);
should(children[0]).instanceof(AzureResourceMessageTreeNode);
should(children[0].nodePathValue).startWith('message_');
should(children[0].getNodeInfo().label).equal('No SQL Databases found.');
});
it('Should handle errors.', async function(): Promise<void> {
const mockError = 'Test error';
mockDatabaseService.setup((o) => o.getDatabases(mockSubscription, mockCredentials)).returns(() => { throw new Error(mockError); });
const databaseContainerTreeNode = new AzureResourceDatabaseContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
const children = await databaseContainerTreeNode.getChildren();
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
mockDatabaseService.verify((o) => o.getDatabases(mockSubscription, mockCredentials), TypeMoq.Times.once());
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.never());
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.never());
should(children).Array();
should(children.length).equal(1);
should(children[0]).instanceof(AzureResourceMessageTreeNode);
should(children[0].nodePathValue).startWith('message_');
should(children[0].getNodeInfo().label).equal(`Error: ${mockError}`);
});
});
describe('AzureResourceDatabaseContainerTreeNode.clearCache', function() : void {
beforeEach(() => {
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
});
it('Should clear cache.', async function(): Promise<void> {
const databaseContainerTreeNode = new AzureResourceDatabaseContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
databaseContainerTreeNode.clearCache();
should(databaseContainerTreeNode.isClearingCache).true();
});
});

View File

@@ -1,219 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as should from 'should';
import * as TypeMoq from 'typemoq';
import * as sqlops from 'sqlops';
import * as vscode from 'vscode';
import 'mocha';
import { ServiceClientCredentials } from 'ms-rest';
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
import {
IAzureResourceCacheService,
IAzureResourceContextService,
IAzureResourceCredentialService,
IAzureResourceDatabaseServerService
} from '../../../azureResource/interfaces';
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
import { AzureResourceSubscription, AzureResourceDatabaseServer } from '../../../azureResource/models';
import { AzureResourceItemType } from '../../../azureResource/constants';
import { AzureResourceMessageTreeNode } from '../../../azureResource/tree/messageTreeNode';
import { AzureResourceDatabaseServerContainerTreeNode } from '../../../azureResource/tree/databaseServerContainerTreeNode';
import { AzureResourceDatabaseServerTreeNode } from '../../../azureResource/tree/databaseServerTreeNode';
// Mock services
const mockServicePool = AzureResourceServicePool.getInstance();
let mockCacheService: TypeMoq.IMock<IAzureResourceCacheService>;
let mockContextService: TypeMoq.IMock<IAzureResourceContextService>;
let mockCredentialService: TypeMoq.IMock<IAzureResourceCredentialService>;
let mockDatabaseServerService: TypeMoq.IMock<IAzureResourceDatabaseServerService>;
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
// Mock test data
const mockAccount: sqlops.Account = {
key: {
accountId: 'mock_account',
providerId: 'mock_provider'
},
displayInfo: {
displayName: 'mock_account@test.com',
accountType: 'Microsoft',
contextualDisplayName: 'test'
},
properties: undefined,
isStale: false
};
const mockCredential = TypeMoq.Mock.ofType<ServiceClientCredentials>().object;
const mockCredentials = [mockCredential];
const mockSubscription: AzureResourceSubscription = {
id: 'mock_subscription',
name: 'mock subscription'
};
const mockDatabaseServer1: AzureResourceDatabaseServer = {
name: 'mock server 1',
fullName: 'mock server 1',
loginName: 'mock user 1',
defaultDatabaseName: 'master'
};
const mockDatabaseServer2: AzureResourceDatabaseServer = {
name: 'mock server 2',
fullName: 'mock server 2',
loginName: 'mock user 2',
defaultDatabaseName: 'master'
};
const mockDatabaseServers = [mockDatabaseServer1, mockDatabaseServer2];
let mockDatabaseServerContainerCache: { databaseServers: { [subscriptionId: string]: AzureResourceDatabaseServer[] } };
describe('AzureResourceDatabaseServerContainerTreeNode.info', function(): void {
beforeEach(() => {
mockContextService = TypeMoq.Mock.ofType<IAzureResourceContextService>();
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
mockServicePool.contextService = mockContextService.object;
});
it('Should be correct when created.', async function(): Promise<void> {
const databaseServerContainerTreeNode = new AzureResourceDatabaseServerContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
const databaseServerContainerTreeNodeLabel = 'SQL Servers';
should(databaseServerContainerTreeNode.nodePathValue).equal('databaseServerContainer');
const treeItem = await databaseServerContainerTreeNode.getTreeItem();
should(treeItem.label).equal(databaseServerContainerTreeNodeLabel);
should(treeItem.contextValue).equal(AzureResourceItemType.databaseServerContainer);
should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed);
const nodeInfo = databaseServerContainerTreeNode.getNodeInfo();
should(nodeInfo.isLeaf).false();
should(nodeInfo.label).equal(databaseServerContainerTreeNodeLabel);
should(nodeInfo.nodeType).equal(AzureResourceItemType.databaseServerContainer);
should(nodeInfo.iconType).equal(AzureResourceItemType.databaseServerContainer);
});
});
describe('AzureResourceDatabaseServerContainerTreeNode.getChildren', function(): void {
beforeEach(() => {
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
mockCredentialService = TypeMoq.Mock.ofType<IAzureResourceCredentialService>();
mockDatabaseServerService = TypeMoq.Mock.ofType<IAzureResourceDatabaseServerService>();
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
mockDatabaseServerContainerCache = { databaseServers: {} };
mockServicePool.cacheService = mockCacheService.object;
mockServicePool.credentialService = mockCredentialService.object;
mockServicePool.databaseServerService = mockDatabaseServerService.object;
mockCredentialService.setup((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockCredentials));
mockCacheService.setup((o) => o.get(TypeMoq.It.isAnyString())).returns(() => mockDatabaseServerContainerCache);
mockCacheService.setup((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => mockDatabaseServerContainerCache.databaseServers[mockSubscription.id] = mockDatabaseServers);
});
it('Should load database servers from scratch and update cache when it is clearing cache.', async function(): Promise<void> {
mockDatabaseServerService.setup((o) => o.getDatabaseServers(mockSubscription, mockCredentials)).returns(() => Promise.resolve(mockDatabaseServers));
const databaseServerContainerTreeNode = new AzureResourceDatabaseServerContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
const children = await databaseServerContainerTreeNode.getChildren();
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
mockDatabaseServerService.verify((o) => o.getDatabaseServers(mockSubscription, mockCredentials), TypeMoq.Times.once());
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.once());
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
should(databaseServerContainerTreeNode.isClearingCache).false();
should(children).Array();
should(children.length).equal(mockDatabaseServers.length);
should(Object.keys(mockDatabaseServerContainerCache.databaseServers)).deepEqual([mockSubscription.id]);
should(mockDatabaseServerContainerCache.databaseServers[mockSubscription.id]).deepEqual(mockDatabaseServers);
for (let ix = 0; ix < mockDatabaseServers.length; ix++) {
const child = children[ix];
const databaseServer = mockDatabaseServers[ix];
should(child).instanceof(AzureResourceDatabaseServerTreeNode);
should(child.nodePathValue).equal(`databaseServer_${databaseServer.name}`);
}
});
it('Should load database servers from cache when it is not clearing cache.', async function(): Promise<void> {
mockDatabaseServerService.setup((o) => o.getDatabaseServers(mockSubscription, mockCredentials)).returns(() => Promise.resolve(mockDatabaseServers));
const databaseServerContainerTreeNode = new AzureResourceDatabaseServerContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
await databaseServerContainerTreeNode.getChildren();
const children = await databaseServerContainerTreeNode.getChildren();
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.exactly(1));
mockDatabaseServerService.verify((o) => o.getDatabaseServers(mockSubscription, mockCredentials), TypeMoq.Times.exactly(1));
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(2));
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.exactly(1));
should(children.length).equal(mockDatabaseServerContainerCache.databaseServers[mockSubscription.id].length);
for (let ix = 0; ix < mockDatabaseServerContainerCache.databaseServers[mockSubscription.id].length; ix++) {
should(children[ix].nodePathValue).equal(`databaseServer_${mockDatabaseServerContainerCache.databaseServers[mockSubscription.id][ix].name}`);
}
});
it('Should handle when there is no database servers.', async function(): Promise<void> {
mockDatabaseServerService.setup((o) => o.getDatabaseServers(mockSubscription, mockCredentials)).returns(() => Promise.resolve(undefined));
const databaseContainerTreeNode = new AzureResourceDatabaseServerContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
const children = await databaseContainerTreeNode.getChildren();
should(children).Array();
should(children.length).equal(1);
should(children[0]).instanceof(AzureResourceMessageTreeNode);
should(children[0].nodePathValue).startWith('message_');
should(children[0].getNodeInfo().label).equal('No SQL Servers found.');
});
it('Should handle errors.', async function(): Promise<void> {
const mockError = 'Test error';
mockDatabaseServerService.setup((o) => o.getDatabaseServers(mockSubscription, mockCredentials)).returns(() => { throw new Error(mockError); });
const databaseServerContainerTreeNode = new AzureResourceDatabaseServerContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
const children = await databaseServerContainerTreeNode.getChildren();
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
mockDatabaseServerService.verify((o) => o.getDatabaseServers(mockSubscription, mockCredentials), TypeMoq.Times.once());
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.never());
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.never());
should(children).Array();
should(children.length).equal(1);
should(children[0]).instanceof(AzureResourceMessageTreeNode);
should(children[0].nodePathValue).startWith('message_');
should(children[0].getNodeInfo().label).equal(`Error: ${mockError}`);
});
});
describe('AzureResourceDatabaseServerContainerTreeNode.clearCache', function() : void {
beforeEach(() => {
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
});
it('Should clear cache.', async function(): Promise<void> {
const databaseServerContainerTreeNode = new AzureResourceDatabaseServerContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
databaseServerContainerTreeNode.clearCache();
should(databaseServerContainerTreeNode.isClearingCache).true();
});
});

View File

@@ -1,62 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as should from 'should';
import * as TypeMoq from 'typemoq';
import * as vscode from 'vscode';
import 'mocha';
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
import { IAzureResourceContextService } from '../../../azureResource/interfaces';
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
import { AzureResourceDatabaseServer } from '../../../azureResource/models';
import { AzureResourceItemType } from '../../../azureResource/constants';
import { AzureResourceDatabaseServerTreeNode } from '../../../azureResource/tree/databaseServerTreeNode';
// Mock services
const mockServicePool = AzureResourceServicePool.getInstance();
let mockContextService: TypeMoq.IMock<IAzureResourceContextService>;
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
// Mock test data
const mockDatabaseServer: AzureResourceDatabaseServer = {
name: 'mock database 1',
fullName: 'mock server 1',
loginName: 'mock user 1',
defaultDatabaseName: 'master'
};
describe('AzureResourceDatabaseServerTreeNode.info', function(): void {
beforeEach(() => {
mockContextService = TypeMoq.Mock.ofType<IAzureResourceContextService>();
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
mockServicePool.contextService = mockContextService.object;
});
it('Should be correct when created.', async function(): Promise<void> {
const databaseServerTreeNode = new AzureResourceDatabaseServerTreeNode(mockDatabaseServer, mockTreeChangeHandler.object, undefined);
const databaseServerTreeNodeLabel = mockDatabaseServer.name;
should(databaseServerTreeNode.nodePathValue).equal(`databaseServer_${mockDatabaseServer.name}`);
const treeItem = await databaseServerTreeNode.getTreeItem();
should(treeItem.label).equal(databaseServerTreeNodeLabel);
should(treeItem.contextValue).equal(AzureResourceItemType.databaseServer);
should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.None);
const nodeInfo = databaseServerTreeNode.getNodeInfo();
should(nodeInfo.isLeaf).true();
should(nodeInfo.label).equal(databaseServerTreeNodeLabel);
should(nodeInfo.nodeType).equal(AzureResourceItemType.databaseServer);
should(nodeInfo.iconType).equal(AzureResourceItemType.databaseServer);
});
});

View File

@@ -1,62 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as should from 'should';
import * as TypeMoq from 'typemoq';
import * as vscode from 'vscode';
import 'mocha';
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
import { IAzureResourceContextService } from '../../../azureResource/interfaces';
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
import { AzureResourceDatabase } from '../../../azureResource/models';
import { AzureResourceItemType } from '../../../azureResource/constants';
import { AzureResourceDatabaseTreeNode } from '../../../azureResource/tree/databaseTreeNode';
// Mock services
const mockServicePool = AzureResourceServicePool.getInstance();
let mockContextService: TypeMoq.IMock<IAzureResourceContextService>;
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
// Mock test data
const mockDatabase: AzureResourceDatabase = {
name: 'mock database 1',
serverName: 'mock server 1',
serverFullName: 'mock server 1',
loginName: 'mock user 1'
};
describe('AzureResourceDatabaseTreeNode.info', function(): void {
beforeEach(() => {
mockContextService = TypeMoq.Mock.ofType<IAzureResourceContextService>();
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
mockServicePool.contextService = mockContextService.object;
});
it('Should be correct.', async function(): Promise<void> {
const databaseTreeNode = new AzureResourceDatabaseTreeNode(mockDatabase, mockTreeChangeHandler.object, undefined);
const databaseTreeNodeLabel = `${mockDatabase.name} (${mockDatabase.serverName})`;
should(databaseTreeNode.nodePathValue).equal(`database_${mockDatabase.name}`);
const treeItem = await databaseTreeNode.getTreeItem();
should(treeItem.label).equal(databaseTreeNodeLabel);
should(treeItem.contextValue).equal(AzureResourceItemType.database);
should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.None);
const nodeInfo = databaseTreeNode.getNodeInfo();
should(nodeInfo.isLeaf).true();
should(nodeInfo.label).equal(databaseTreeNodeLabel);
should(nodeInfo.nodeType).equal(AzureResourceItemType.database);
should(nodeInfo.iconType).equal(AzureResourceItemType.database);
});
});

View File

@@ -10,20 +10,24 @@ import * as TypeMoq from 'typemoq';
import * as sqlops from 'sqlops';
import * as vscode from 'vscode';
import 'mocha';
import { AppContext } from '../../../appContext';
import { ApiWrapper } from '../../../apiWrapper';
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
import { IAzureResourceContextService } from '../../../azureResource/interfaces';
import { azureResource } from '../../../azureResource/azure-resource';
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
import { AzureResourceSubscription } from '../../../azureResource/models';
import { AzureResourceSubscriptionTreeNode } from '../../../azureResource/tree/subscriptionTreeNode';
import { AzureResourceDatabaseContainerTreeNode } from '../../../azureResource/tree/databaseContainerTreeNode';
import { AzureResourceDatabaseServerContainerTreeNode } from '../../../azureResource/tree/databaseServerContainerTreeNode';
import { AzureResourceItemType } from '../../../azureResource/constants';
import { AzureResourceItemType, AzureResourceServiceNames } from '../../../azureResource/constants';
import { AzureResourceService } from '../../../azureResource/resourceService';
import { AzureResourceResourceTreeNode } from '../../../azureResource/resourceTreeNode';
import { IAzureResourceCacheService } from '../../../azureResource/interfaces';
import { generateGuid } from '../../../azureResource/utils';
// Mock services
const mockServicePool = AzureResourceServicePool.getInstance();
let mockAppContext: AppContext;
let mockContextService: TypeMoq.IMock<IAzureResourceContextService>;
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
let mockCacheService: TypeMoq.IMock<IAzureResourceCacheService>;
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
@@ -42,24 +46,60 @@ const mockAccount: sqlops.Account = {
isStale: false
};
const mockSubscription: AzureResourceSubscription = {
const mockSubscription: azureResource.AzureResourceSubscription = {
id: 'mock_subscription',
name: 'mock subscription'
};
const mockTenantId: string = 'mock_tenant';
let mockResourceTreeDataProvider1: TypeMoq.IMock<azureResource.IAzureResourceTreeDataProvider>;
let mockResourceProvider1: TypeMoq.IMock<azureResource.IAzureResourceProvider>;
let mockResourceTreeDataProvider2: TypeMoq.IMock<azureResource.IAzureResourceTreeDataProvider>;
let mockResourceProvider2: TypeMoq.IMock<azureResource.IAzureResourceProvider>;
const resourceService: AzureResourceService = AzureResourceService.getInstance();
describe('AzureResourceSubscriptionTreeNode.info', function(): void {
beforeEach(() => {
mockContextService = TypeMoq.Mock.ofType<IAzureResourceContextService>();
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object);
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
mockServicePool.contextService = mockContextService.object;
mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
mockResourceTreeDataProvider1.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
mockResourceTreeDataProvider1.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
mockResourceProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1');
mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object);
mockResourceTreeDataProvider2 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
mockResourceTreeDataProvider2.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
mockResourceTreeDataProvider2.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
mockResourceProvider2 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
mockResourceProvider2.setup((o) => o.providerId).returns(() => 'mockResourceProvider2');
mockResourceProvider2.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider2.object);
resourceService.clearResourceProviders();
resourceService.registerResourceProvider(mockResourceProvider1.object);
resourceService.registerResourceProvider(mockResourceProvider2.object);
resourceService.areResourceProvidersLoaded = true;
});
it('Should be correct when created.', async function(): Promise<void> {
const subscriptionTreeNode = new AzureResourceSubscriptionTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
const subscriptionTreeNode = new AzureResourceSubscriptionTreeNode(mockAccount, mockSubscription, mockTenantId, mockAppContext, mockTreeChangeHandler.object, undefined);
should(subscriptionTreeNode.nodePathValue).equal(`subscription_${mockSubscription.id}`);
should(subscriptionTreeNode.nodePathValue).equal(`account_${mockAccount.key.accountId}.subscription_${mockSubscription.id}.tenant_${mockTenantId}`);
const treeItem = await subscriptionTreeNode.getTreeItem();
should(treeItem.label).equal(mockSubscription.name);
@@ -76,16 +116,52 @@ describe('AzureResourceSubscriptionTreeNode.info', function(): void {
describe('AzureResourceSubscriptionTreeNode.getChildren', function(): void {
beforeEach(() => {
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object);
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
mockResourceTreeDataProvider1.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
mockResourceTreeDataProvider1.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
mockResourceProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1');
mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object);
mockResourceTreeDataProvider2 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
mockResourceTreeDataProvider2.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
mockResourceTreeDataProvider2.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
mockResourceProvider2 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
mockResourceProvider2.setup((o) => o.providerId).returns(() => 'mockResourceProvider2');
mockResourceProvider2.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider2.object);
resourceService.clearResourceProviders();
resourceService.registerResourceProvider(mockResourceProvider1.object);
resourceService.registerResourceProvider(mockResourceProvider2.object);
resourceService.areResourceProvidersLoaded = true;
});
it('Should load database containers.', async function(): Promise<void> {
const subscriptionTreeNode = new AzureResourceSubscriptionTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
it('Should return resource containers.', async function(): Promise<void> {
const subscriptionTreeNode = new AzureResourceSubscriptionTreeNode(mockAccount, mockSubscription, mockTenantId, mockAppContext, mockTreeChangeHandler.object, undefined);
const children = await subscriptionTreeNode.getChildren();
mockResourceTreeDataProvider1.verify((o) => o.getChildren(), TypeMoq.Times.once());
mockResourceTreeDataProvider2.verify((o) => o.getChildren(), TypeMoq.Times.once());
const expectedChildren = await resourceService.listResourceProviderIds();
should(children).Array();
should(children.length).equal(2);
should(children[0]).instanceof(AzureResourceDatabaseContainerTreeNode);
should(children[1]).instanceof(AzureResourceDatabaseServerContainerTreeNode);
should(children.length).equal(expectedChildren.length);
for (const child of children) {
should(child).instanceOf(AzureResourceResourceTreeNode);
}
});
});

View File

@@ -5,21 +5,28 @@
'use strict';
import * as vscode from 'vscode';
import * as should from 'should';
import * as TypeMoq from 'typemoq';
import * as sqlops from 'sqlops';
import 'mocha';
import { AppContext } from '../../../appContext';
import { ApiWrapper } from '../../../apiWrapper';
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
import { IAzureResourceAccountService } from '../../../azureResource/interfaces';
import { IAzureResourceCacheService, IAzureResourceAccountService } from '../../../azureResource/interfaces';
import { AzureResourceTreeProvider } from '../../../azureResource/tree/treeProvider';
import { AzureResourceAccountTreeNode } from '../../../azureResource/tree/accountTreeNode';
import { AzureResourceAccountNotSignedInTreeNode } from '../../../azureResource/tree/accountNotSignedInTreeNode';
import { AzureResourceMessageTreeNode } from '../../../azureResource/tree/messageTreeNode';
import { AzureResourceMessageTreeNode } from '../../../azureResource/messageTreeNode';
import { AzureResourceServiceNames } from '../../../azureResource/constants';
import { generateGuid } from '../../../azureResource/utils';
// Mock services
const mockServicePool = AzureResourceServicePool.getInstance();
let mockAppContext: AppContext;
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
let mockCacheService: TypeMoq.IMock<IAzureResourceCacheService>;
let mockAccountService: TypeMoq.IMock<IAzureResourceAccountService>;
// Mock test data
@@ -53,15 +60,23 @@ const mockAccounts = [mockAccount1, mockAccount2];
describe('AzureResourceTreeProvider.getChildren', function(): void {
beforeEach(() => {
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
mockAccountService = TypeMoq.Mock.ofType<IAzureResourceAccountService>();
mockServicePool.accountService = mockAccountService.object;
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object);
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
mockAppContext.registerService<IAzureResourceAccountService>(AzureResourceServiceNames.accountService, mockAccountService.object);
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
});
it('Should load accounts.', async function(): Promise<void> {
mockAccountService.setup((o) => o.getAccounts()).returns(() => Promise.resolve(mockAccounts));
const treeProvider = new AzureResourceTreeProvider();
const treeProvider = new AzureResourceTreeProvider(mockAppContext);
treeProvider.isSystemInitialized = true;
const children = await treeProvider.getChildren(undefined);
@@ -83,7 +98,7 @@ describe('AzureResourceTreeProvider.getChildren', function(): void {
it('Should handle when there is no accounts.', async function(): Promise<void> {
mockAccountService.setup((o) => o.getAccounts()).returns(() => Promise.resolve(undefined));
const treeProvider = new AzureResourceTreeProvider();
const treeProvider = new AzureResourceTreeProvider(mockAppContext);
treeProvider.isSystemInitialized = true;
const children = await treeProvider.getChildren(undefined);
@@ -97,7 +112,7 @@ describe('AzureResourceTreeProvider.getChildren', function(): void {
const mockAccountError = 'Test account error';
mockAccountService.setup((o) => o.getAccounts()).returns(() => { throw new Error(mockAccountError); });
const treeProvider = new AzureResourceTreeProvider();
const treeProvider = new AzureResourceTreeProvider(mockAppContext);
treeProvider.isSystemInitialized = true;
const children = await treeProvider.getChildren(undefined);

File diff suppressed because it is too large Load Diff

View File

@@ -10,16 +10,16 @@ set VSCODEUSERDATADIR=%TMP%\vscodeuserfolder-%RANDOM%-%TIME:~6,5%
:: call .\scripts\code.bat %~dp0\..\extensions\vscode-api-tests\testWorkspace --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out --disableExtensions --user-data-dir=%VSCODEUSERDATADIR%
call .\scripts\code.bat %~dp0\..\extensions\vscode-colorize-tests\test --extensionDevelopmentPath=%~dp0\..\extensions\vscode-colorize-tests --extensionTestsPath=%~dp0\..\extensions\vscode-colorize-tests\out --disableExtensions --user-data-dir=%VSCODEUSERDATADIR%
call .\scripts\code.bat %~dp0\..\extensions\markdown-language-features\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\markdown-language-features --extensionTestsPath=%~dp0\..\extensions\markdown-language-features\out\test --disableExtensions --user-data-dir=%VSCODEUSERDATADIR%
call .\scripts\code.bat %~dp0\..\extensions\azure\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\azure --extensionTestsPath=%~dp0\..\extensions\azure\out\test --disableExtensions --user-data-dir=%VSCODEUSERDATADIR%
call .\scripts\code.bat %~dp0\..\extensions\azurecore\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\azurecore --extensionTestsPath=%~dp0\..\extensions\azurecore\out\test --disableExtensions --user-data-dir=%VSCODEUSERDATADIR%
if %errorlevel% neq 0 exit /b %errorlevel%
call .\scripts\code.bat $%~dp0\..\extensions\emmet\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\emmet --extensionTestsPath=%~dp0\..\extensions\emmet\out\test --disableExtensions --user-data-dir=%VSCODEUSERDATADIR% .
if %errorlevel% neq 0 exit /b %errorlevel%
:: call .\scripts\code.bat $%~dp0\..\extensions\emmet\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\emmet --extensionTestsPath=%~dp0\..\extensions\emmet\out\test --disableExtensions --user-data-dir=%VSCODEUSERDATADIR% .
:: if %errorlevel% neq 0 exit /b %errorlevel%
:: Integration & performance tests in AMD
call .\scripts\test.bat --runGlob **\*.integrationTest.js %*
# Tests in commonJS (HTML, CSS, JSON language server tests...)
:: Tests in commonJS (HTML, CSS, JSON language server tests...)
call .\scripts\node-electron.bat .\node_modules\mocha\bin\_mocha .\extensions\*\server\out\test\**\*.test.js
if %errorlevel% neq 0 exit /b %errorlevel%

View File

@@ -17,7 +17,7 @@ cd $ROOT
# ./scripts/code.sh $ROOT/extensions/vscode-api-tests/testWorkspace --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out --disableExtensions --user-data-dir=$VSCODEUSERDATADIR --skip-getting-started
./scripts/code.sh $ROOT/extensions/vscode-colorize-tests/test --extensionDevelopmentPath=$ROOT/extensions/vscode-colorize-tests --extensionTestsPath=$ROOT/extensions/vscode-colorize-tests/out --disableExtensions --user-data-dir=$VSCODEUSERDATADIR --skip-getting-started
./scripts/code.sh $ROOT/extensions/markdown-language-features/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/markdown-language-features --extensionTestsPath=$ROOT/extensions/markdown-language-features/out/test --disableExtensions --user-data-dir=$VSCODEUSERDATADIR --skip-getting-started
./scripts/code.sh $ROOT/extensions/azure/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/azure --extensionTestsPath=$ROOT/extensions/azure/out/test --disableExtensions --user-data-dir=$VSCODEUSERDATADIR --skip-getting-started
./scripts/code.sh $ROOT/extensions/azurecore/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/azurecore --extensionTestsPath=$ROOT/extensions/azurecore/out/test --disableExtensions --user-data-dir=$VSCODEUSERDATADIR --skip-getting-started
mkdir $ROOT/extensions/emmet/test-fixtures
./scripts/code.sh $ROOT/extensions/emmet/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test --disableExtensions --user-data-dir=$VSCODEUSERDATADIR --skip-getting-started .