mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
Add more SsmsMin interactions (#5267)
* Add support for more SsmsMin property dialogs and the Generate Script Wizard. Also fixed bug with ExtHostObjectExplorerNode getParent function * Localize package.json entries * Fix localization tokens * Address PR comments * Fix regex and getParent
This commit is contained in:
@@ -1,61 +1,75 @@
|
|||||||
{
|
{
|
||||||
"name": "admin-tool-ext-win",
|
"name": "admin-tool-ext-win",
|
||||||
"displayName": "Database Admin Tool Extensions for Windows",
|
"displayName": "%adminToolExtWin.displayName%",
|
||||||
"description": "Adds additional Windows-specific functionality to Azure Data Studio",
|
"description": "%adminToolExtWin.description%",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"publisher": "Microsoft",
|
"publisher": "Microsoft",
|
||||||
"preview": true,
|
"preview": true,
|
||||||
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt",
|
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt",
|
||||||
"icon": "images/sqlserver.png",
|
"icon": "images/sqlserver.png",
|
||||||
"aiKey": "AIF-5574968e-856d-40d2-af67-c89a14e76412",
|
"aiKey": "AIF-5574968e-856d-40d2-af67-c89a14e76412",
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "^1.30.1",
|
"vscode": "^1.30.1",
|
||||||
"sqlops": "*"
|
"sqlops": "*"
|
||||||
},
|
},
|
||||||
"activationEvents": [
|
"activationEvents": [
|
||||||
"*"
|
"*"
|
||||||
],
|
],
|
||||||
"main": "./out/main",
|
"main": "./out/main",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/Microsoft/azuredatastudio.git"
|
"url": "https://github.com/Microsoft/azuredatastudio.git"
|
||||||
},
|
},
|
||||||
"extensionDependencies": [
|
"extensionDependencies": [
|
||||||
"Microsoft.mssql"
|
"Microsoft.mssql"
|
||||||
],
|
],
|
||||||
"contributes": {
|
"contributes": {
|
||||||
"commands": [
|
"commands": [
|
||||||
{
|
{
|
||||||
"command": "adminToolExtWin.launchSsmsServerPropertiesDialog",
|
"command": "adminToolExtWin.launchSsmsMinPropertiesDialog",
|
||||||
"title": "%adminToolExtWin.launchSsmsServerPropertiesDialog%",
|
"title": "%adminToolExtWin.propertiesMenuItem%",
|
||||||
"category": "AdminToolExtWin"
|
"category": "AdminToolExtWin"
|
||||||
}
|
},
|
||||||
],
|
{
|
||||||
"menus": {
|
"command": "adminToolExtWin.launchSsmsMinGswDialog",
|
||||||
"commandPalette": [
|
"title": "%adminToolExtWin.launchGswMenuItem%",
|
||||||
{
|
"category": "AdminToolExtWin"
|
||||||
"command": "adminToolExtWin.launchSsmsServerPropertiesDialog",
|
}
|
||||||
"when": "false"
|
],
|
||||||
}
|
"menus": {
|
||||||
],
|
"commandPalette": [
|
||||||
"objectExplorer/item/context": [
|
{
|
||||||
{
|
"command": "adminToolExtWin.launchSsmsMinPropertiesDialog",
|
||||||
"command": "adminToolExtWin.launchSsmsServerPropertiesDialog",
|
"when": "false"
|
||||||
"when": "isWindows && connectionProvider == MSSQL && nodeType && nodeType == Server",
|
},
|
||||||
"group": "AdminToolExtWin"
|
{
|
||||||
}
|
"command": "adminToolExtWin.launchSsmsMinGswDialog",
|
||||||
]
|
"when": "false"
|
||||||
},
|
}
|
||||||
"outputChannels": [
|
],
|
||||||
"admin-tool-ext-win"
|
"objectExplorer/item/context": [
|
||||||
]
|
{
|
||||||
},
|
"command": "adminToolExtWin.launchSsmsMinGswDialog",
|
||||||
"dependencies": {
|
"when": "isWindows && connectionProvider == MSSQL && nodeType && nodeType == Database",
|
||||||
"service-downloader": "github:anthonydresser/service-downloader#0.1.5",
|
"group": "z-AdminToolExt@1"
|
||||||
"vscode-extension-telemetry": "^0.0.15",
|
},
|
||||||
"vscode-nls": "^3.2.1"
|
{
|
||||||
},
|
"command": "adminToolExtWin.launchSsmsMinPropertiesDialog",
|
||||||
"devDependencies": {
|
"when": "isWindows && connectionProvider == MSSQL && nodeType && nodeType =~ /^(Server|Database|Table|Column|Index|Statistic|View|ServerLevelLogin|ServerLevelServerRole|ServerLevelCredential|ServerLevelServerAudit|ServerLevelServerAuditSpecification|StoredProcedure|ScalarValuedFunction|TableValuedFunction|AggregateFunction|Synonym|Assembly|UserDefinedDataType|UserDefinedType|UserDefinedTableType|Sequence|User|DatabaseRole|ApplicationRole|Schema|SecurityPolicy|ServerLevelLinkedServer)$/",
|
||||||
"vscode": "1.0.1"
|
"group": "z-AdminToolExt@2"
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"outputChannels": [
|
||||||
|
"admin-tool-ext-win"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"service-downloader": "github:anthonydresser/service-downloader#0.1.5",
|
||||||
|
"vscode-extension-telemetry": "^0.0.15",
|
||||||
|
"vscode-nls": "^3.2.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"vscode": "1.0.1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
{
|
{
|
||||||
"adminToolExtWin.launchSsmsServerPropertiesDialog": "Properties"
|
"adminToolExtWin.displayName": "Database Administration Tool Extensions for Windows",
|
||||||
|
"adminToolExtWin.description": "Adds additional Windows-specific functionality to Azure Data Studio",
|
||||||
|
"adminToolExtWin.propertiesMenuItem": "Properties",
|
||||||
|
"adminToolExtWin.launchGswMenuItem": "Generate Scripts..."
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,7 @@ import * as azdata from 'azdata';
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { IConfig, ServerProvider } from 'service-downloader';
|
import { IConfig, ServerProvider } from 'service-downloader';
|
||||||
import { Telemetry } from './telemetry';
|
import { Telemetry } from './telemetry';
|
||||||
import * as utils from './utils';
|
import { doubleEscapeSingleQuotes, backEscapeDoubleQuotes, getConfiguration } from './utils';
|
||||||
import { ChildProcess, exec } from 'child_process';
|
import { ChildProcess, exec } from 'child_process';
|
||||||
|
|
||||||
const baseConfig = require('./config.json');
|
const baseConfig = require('./config.json');
|
||||||
@@ -17,6 +17,43 @@ const localize = nls.loadMessageBundle();
|
|||||||
let exePath: string;
|
let exePath: string;
|
||||||
let runningProcesses: Map<number, ChildProcess> = new Map<number, ChildProcess>();
|
let runningProcesses: Map<number, ChildProcess> = new Map<number, ChildProcess>();
|
||||||
|
|
||||||
|
|
||||||
|
interface SmoMapping {
|
||||||
|
action: string;
|
||||||
|
urnName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeTypeToUrnNameMapping: { [oeNodeType: string]: SmoMapping } = {
|
||||||
|
'Database': { action: 'sqla:Properties@Microsoft.SqlServer.Management.Smo.Database', urnName: 'Database' },
|
||||||
|
'Server': { action: 'sqla:Properties@Microsoft.SqlServer.Management.Smo.Server', urnName: 'Server' },
|
||||||
|
'ServerLevelServerAudit': { action: 'sqla:AuditProperties', urnName: 'Audit' },
|
||||||
|
'ServerLevelCredential': { action: 'sqla:Properties@Microsoft.SqlServer.Management.Smo.Credential', urnName: 'Credential' },
|
||||||
|
'ServerLevelServerRole': { action: 'sqla:ManageServerRole', urnName: 'Role' },
|
||||||
|
'ServerLevelServerAuditSpecification': { action: 'sqla:ServerAuditSpecificationProperties', urnName: 'ServerAuditSpecification' },
|
||||||
|
'ServerLevelLinkedServer': { action: 'sqla:Properties@Microsoft.SqlServer.Management.Smo.LinkedServer', urnName: 'LinkedServer' },
|
||||||
|
'Table': { action: 'sqla:Properties@Microsoft.SqlServer.Management.Smo.Table', urnName: 'Table' },
|
||||||
|
'View': { action: 'sqla:Properties@Microsoft.SqlServer.Management.Smo.View', urnName: 'View' },
|
||||||
|
'Column': { action: 'sqla:Properties@Microsoft.SqlServer.Management.Smo.Column', urnName: 'Column' },
|
||||||
|
'Index': { action: 'sqla:IndexProperties', urnName: 'Index' },
|
||||||
|
'Statistic': { action: 'sqla:Properties@Microsoft.SqlServer.Management.Smo.Statistic', urnName: 'Statistic' },
|
||||||
|
'StoredProcedure': { action: 'sqla:Properties@Microsoft.SqlServer.Management.Smo.StoredProcedure', urnName: 'StoredProcedure' },
|
||||||
|
'ScalarValuedFunction': { action: 'sqla:Properties@Microsoft.SqlServer.Management.Smo.UserDefinedFunction', urnName: 'UserDefinedFunction' },
|
||||||
|
'TableValuedFunction': { action: 'sqla:Properties@Microsoft.SqlServer.Management.Smo.UserDefinedFunction', urnName: 'UserDefinedFunction' },
|
||||||
|
'AggregateFunction': { action: 'sqla:Properties@Microsoft.SqlServer.Management.Smo.UserDefinedFunction', urnName: 'UserDefinedFunction' },
|
||||||
|
'Synonym': { action: 'sqla:Properties@Microsoft.SqlServer.Management.Smo.Synonym', urnName: 'Synonym' },
|
||||||
|
'Assembly': { action: 'sqla:Properties@Microsoft.SqlServer.Management.Smo.SqlAssembly', urnName: 'SqlAssembly' },
|
||||||
|
'UserDefinedDataType': { action: 'sqla:Properties@Microsoft.SqlServer.Management.Smo.UserDefinedDataType', urnName: 'UserDefinedDataType' },
|
||||||
|
'UserDefinedType': { action: 'sqla:Properties@Microsoft.SqlServer.Management.Smo.UserDefinedType', urnName: 'UserDefinedType' },
|
||||||
|
'UserDefinedTableType': { action: 'sqla:Properties@Microsoft.SqlServer.Management.Smo.UserDefinedTableType', urnName: 'UserDefinedTableType' },
|
||||||
|
'Sequence': { action: 'sqla:SequenceProperties', urnName: 'Sequence' },
|
||||||
|
'User': { action: 'sqla:Properties@Microsoft.SqlServer.Management.Smo.User', urnName: 'User' },
|
||||||
|
'DatabaseRole': { action: 'sqla:Properties@Microsoft.SqlServer.Management.Smo.DatabaseRole', urnName: 'Role' },
|
||||||
|
'ApplicationRole': { action: 'sqla:Properties@Microsoft.SqlServer.Management.Smo.ApplicationRole', urnName: 'ApplicationRole' },
|
||||||
|
'Schema': { action: 'sqla:Properties@Microsoft.SqlServer.Management.Smo.Schema', urnName: 'Schema' },
|
||||||
|
'SecurityPolicy': { action: 'sqla:Properties@Microsoft.SqlServer.Management.Smo.SecurityPolicy', urnName: 'SecurityPolicy' },
|
||||||
|
'ServerLevelLogin': { action: 'sqla:Properties@Microsoft.SqlServer.Management.Smo.Login', urnName: 'Login' },
|
||||||
|
};
|
||||||
|
|
||||||
// Params to pass to SsmsMin.exe, only an action and server are required - the rest are optional based on the
|
// Params to pass to SsmsMin.exe, only an action and server are required - the rest are optional based on the
|
||||||
// action used. Exported for use in testing.
|
// action used. Exported for use in testing.
|
||||||
export interface LaunchSsmsDialogParams {
|
export interface LaunchSsmsDialogParams {
|
||||||
@@ -36,8 +73,8 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
|
|||||||
|
|
||||||
let config: IConfig = JSON.parse(JSON.stringify(baseConfig));
|
let config: IConfig = JSON.parse(JSON.stringify(baseConfig));
|
||||||
config.installDirectory = path.join(context.extensionPath, config.installDirectory);
|
config.installDirectory = path.join(context.extensionPath, config.installDirectory);
|
||||||
config.proxy = utils.getConfiguration('http').get('proxy');
|
config.proxy = getConfiguration('http').get('proxy');
|
||||||
config.strictSSL = utils.getConfiguration('http').get('proxyStrictSSL') || true;
|
config.strictSSL = getConfiguration('http').get('proxyStrictSSL') || true;
|
||||||
|
|
||||||
const serverdownloader = new ServerProvider(config);
|
const serverdownloader = new ServerProvider(config);
|
||||||
const installationStart = Date.now();
|
const installationStart = Date.now();
|
||||||
@@ -52,9 +89,9 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
|
|||||||
} else {
|
} else {
|
||||||
throw new Error('Could not find SsmsMin.exe after downloading');
|
throw new Error('Could not find SsmsMin.exe after downloading');
|
||||||
}
|
}
|
||||||
// Add the command now that we have the exePath to run the tool with
|
|
||||||
context.subscriptions.push(
|
// Register the commands now that we have the exePath to run the tool with
|
||||||
vscode.commands.registerCommand('adminToolExtWin.launchSsmsServerPropertiesDialog', handleLaunchSsmsServerPropertiesDialogCommand));
|
registerCommands(context);
|
||||||
|
|
||||||
Telemetry.sendTelemetryEvent('startup/ExtensionStarted', {
|
Telemetry.sendTelemetryEvent('startup/ExtensionStarted', {
|
||||||
installationTime: String(installationComplete - installationStart),
|
installationTime: String(installationComplete - installationStart),
|
||||||
@@ -75,16 +112,52 @@ export async function deactivate(): Promise<void> {
|
|||||||
runningProcesses.forEach(p => exec('taskkill /pid ' + p.pid + ' /T /F'));
|
runningProcesses.forEach(p => exec('taskkill /pid ' + p.pid + ' /T /F'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers extension commands with command subsystem
|
||||||
|
* @param context The context used to register the commands
|
||||||
|
*/
|
||||||
|
function registerCommands(context: vscode.ExtensionContext): void {
|
||||||
|
context.subscriptions.push(
|
||||||
|
vscode.commands.registerCommand('adminToolExtWin.launchSsmsMinPropertiesDialog', handleLaunchSsmsMinPropertiesDialogCommand));
|
||||||
|
context.subscriptions.push(
|
||||||
|
vscode.commands.registerCommand('adminToolExtWin.launchSsmsMinGswDialog', handleLaunchSsmsMinGswDialogCommand));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler for command to launch SSMS Server Properties dialog
|
* Handler for command to launch SSMS Server Properties dialog
|
||||||
* @param connectionId The connection context from the command
|
* @param connectionId The connection context from the command
|
||||||
*/
|
*/
|
||||||
function handleLaunchSsmsServerPropertiesDialogCommand(connectionContext?: azdata.ObjectExplorerContext) {
|
async function handleLaunchSsmsMinPropertiesDialogCommand(connectionContext?: azdata.ObjectExplorerContext): Promise<void> {
|
||||||
if (connectionContext && connectionContext.connectionProfile) {
|
if (!connectionContext) {
|
||||||
launchSsmsDialog(
|
console.error('No ConnectionContext provided for handleLaunchSsmsMinPropertiesDialogCommand');
|
||||||
/*action*/'sqla:Properties@Microsoft.SqlServer.Management.Smo.Server',
|
return;
|
||||||
/*connectionProfile*/connectionContext.connectionProfile);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let nodeType: string;
|
||||||
|
if (connectionContext.isConnectionNode) {
|
||||||
|
nodeType = 'Server';
|
||||||
|
}
|
||||||
|
else if (connectionContext.nodeInfo) {
|
||||||
|
nodeType = connectionContext.nodeInfo.nodeType;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.error(`Could not determine NodeType for handleLaunchSsmsMinPropertiesDialogCommand with context ${connectionContext}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
launchSsmsDialog(
|
||||||
|
nodeTypeToUrnNameMapping[nodeType].action,
|
||||||
|
connectionContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for command to launch SSMS "Generate Script Wizard" dialog
|
||||||
|
* @param connectionId The connection context from the command
|
||||||
|
*/
|
||||||
|
async function handleLaunchSsmsMinGswDialogCommand(connectionContext?: azdata.ObjectExplorerContext): Promise<void> {
|
||||||
|
launchSsmsDialog(
|
||||||
|
'GenerateScripts',
|
||||||
|
connectionContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -93,29 +166,49 @@ function handleLaunchSsmsServerPropertiesDialogCommand(connectionContext?: azdat
|
|||||||
* @param params The params used to construct the command
|
* @param params The params used to construct the command
|
||||||
* @param urn The URN to pass to SsmsMin
|
* @param urn The URN to pass to SsmsMin
|
||||||
*/
|
*/
|
||||||
function launchSsmsDialog(action: string, connectionProfile: azdata.IConnectionProfile, urn?: string) {
|
async function launchSsmsDialog(action: string, connectionContext: azdata.ObjectExplorerContext): Promise<void> {
|
||||||
if (!exePath) {
|
if (!exePath) {
|
||||||
vscode.window.showErrorMessage(localize('adminToolExtWin.noExeError', 'Unable to find SsmsMin.exe.'));
|
vscode.window.showErrorMessage(localize('adminToolExtWin.noExeError', 'Unable to find SsmsMin.exe.'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!connectionContext.connectionProfile) {
|
||||||
|
console.error(`No ConnectionProfile provided from connectionContext : ${connectionContext}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let oeNode: azdata.objectexplorer.ObjectExplorerNode;
|
||||||
|
// Server node is a Connection node and so doesn't have the NodeInfo
|
||||||
|
if (connectionContext.isConnectionNode) {
|
||||||
|
oeNode = undefined;
|
||||||
|
}
|
||||||
|
else if (connectionContext.nodeInfo && connectionContext.nodeInfo.nodeType && connectionContext.connectionProfile) {
|
||||||
|
oeNode = await azdata.objectexplorer.getNode(connectionContext.connectionProfile.id, connectionContext.nodeInfo.nodePath);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.error(`Could not determine Object Explorer node from connectionContext : ${connectionContext}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let urn: string = await buildUrn(connectionContext.connectionProfile.serverName, oeNode);
|
||||||
|
|
||||||
Telemetry.sendTelemetryEvent('LaunchSsmsDialog', { 'action': action });
|
Telemetry.sendTelemetryEvent('LaunchSsmsDialog', { 'action': action });
|
||||||
|
|
||||||
let params: LaunchSsmsDialogParams = {
|
let params: LaunchSsmsDialogParams = {
|
||||||
action: action,
|
action: action,
|
||||||
server: connectionProfile.serverName,
|
server: connectionContext.connectionProfile.serverName,
|
||||||
database: connectionProfile.databaseName,
|
database: connectionContext.connectionProfile.databaseName,
|
||||||
password: connectionProfile.password,
|
password: connectionContext.connectionProfile.password,
|
||||||
user: connectionProfile.userName,
|
user: connectionContext.connectionProfile.userName,
|
||||||
useAad: connectionProfile.authenticationType === 'AzureMFA',
|
useAad: connectionContext.connectionProfile.authenticationType === 'AzureMFA',
|
||||||
urn: urn
|
urn: urn
|
||||||
};
|
};
|
||||||
let args = buildSsmsMinCommandArgs(params);
|
let args = buildSsmsMinCommandArgs(params);
|
||||||
|
|
||||||
// This will be an async call since we pass in the callback
|
// This will be an async call since we pass in the callback
|
||||||
let proc: ChildProcess = exec(
|
let proc: ChildProcess = exec(
|
||||||
/*command*/`"${exePath}" ${args}`,
|
/*command*/ `"${exePath}" ${args}`,
|
||||||
/*options*/undefined,
|
/*options*/ undefined,
|
||||||
(execException, stdout, stderr) => {
|
(execException, stdout, stderr) => {
|
||||||
// Process has exited so remove from map of running processes
|
// Process has exited so remove from map of running processes
|
||||||
runningProcesses.delete(proc.pid);
|
runningProcesses.delete(proc.pid);
|
||||||
@@ -145,10 +238,34 @@ function launchSsmsDialog(action: string, connectionProfile: azdata.IConnectionP
|
|||||||
* @param params The params used to build up the command parameter string
|
* @param params The params used to build up the command parameter string
|
||||||
*/
|
*/
|
||||||
export function buildSsmsMinCommandArgs(params: LaunchSsmsDialogParams): string {
|
export function buildSsmsMinCommandArgs(params: LaunchSsmsDialogParams): string {
|
||||||
return `${params.action ? '-a "' + params.action.replace(/"/g, '\\"') + '"' : ''}\
|
return `${params.action ? '-a "' + backEscapeDoubleQuotes(params.action) + '"' : ''}\
|
||||||
${params.server ? ' -S "' + params.server.replace(/"/g, '\\"') + '"' : ''}\
|
${params.server ? ' -S "' + backEscapeDoubleQuotes(params.server) + '"' : ''}\
|
||||||
${params.database ? ' -D "' + params.database.replace(/"/g, '\\"') + '"' : ''}\
|
${params.database ? ' -D "' + backEscapeDoubleQuotes(params.database) + '"' : ''}\
|
||||||
${params.useAad !== true && params.user ? ' -U "' + params.user.replace(/"/g, '\\"') + '"' : ''}\
|
${params.useAad !== true && params.user ? ' -U "' + backEscapeDoubleQuotes(params.user) + '"' : ''}\
|
||||||
${params.useAad === true ? ' -G' : ''}\
|
${params.useAad === true ? ' -G' : ''}\
|
||||||
${params.urn ? ' -u "' + params.urn.replace(/"/g, '\\"') + '"' : ''}`;
|
${params.urn ? ' -u "' + backEscapeDoubleQuotes(params.urn) + '"' : ''}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the URN string for a given ObjectExplorerNode in the form understood by SsmsMin
|
||||||
|
* @param serverName The name of the Server to use for the Server segment
|
||||||
|
* @param node The node to get the URN of
|
||||||
|
*/
|
||||||
|
export async function buildUrn(serverName: string, node: azdata.objectexplorer.ObjectExplorerNode): Promise<string> {
|
||||||
|
let urnNodes: string[] = [];
|
||||||
|
while (node) {
|
||||||
|
// Server is special since it's a connection node - always add it as the root
|
||||||
|
if (node.nodeType === 'Server') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (node.metadata && node.nodeType !== 'Folder') {
|
||||||
|
// SFC URN expects Name and Schema to be separate properties
|
||||||
|
let urnSegment = node.metadata.schema && node.metadata.schema !== '' ?
|
||||||
|
`${nodeTypeToUrnNameMapping[node.nodeType].urnName}[@Name='${doubleEscapeSingleQuotes(node.metadata.name)}' and @Schema='${doubleEscapeSingleQuotes(node.metadata.schema)}']` :
|
||||||
|
`${nodeTypeToUrnNameMapping[node.nodeType].urnName}[@Name='${doubleEscapeSingleQuotes(node.metadata.name)}']`;
|
||||||
|
urnNodes = [urnSegment].concat(urnNodes);
|
||||||
|
}
|
||||||
|
node = await node.getParent();
|
||||||
|
}
|
||||||
|
return [`Server[@Name='${doubleEscapeSingleQuotes(serverName)}']`].concat(urnNodes).join('/');
|
||||||
}
|
}
|
||||||
68
extensions/admin-tool-ext-win/src/test/stubs.ts
Normal file
68
extensions/admin-tool-ext-win/src/test/stubs.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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 azdata from 'azdata';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper stub class for mocking ExtHostObjectExplorerNode
|
||||||
|
*/
|
||||||
|
export class ExtHostObjectExplorerNodeStub implements azdata.objectexplorer.ObjectExplorerNode {
|
||||||
|
// Stub properties
|
||||||
|
private parent: azdata.objectexplorer.ObjectExplorerNode;
|
||||||
|
|
||||||
|
// Base properties
|
||||||
|
public connectionId: string;
|
||||||
|
public nodePath: string;
|
||||||
|
public nodeType: string;
|
||||||
|
public nodeSubType: string;
|
||||||
|
public nodeStatus: string;
|
||||||
|
public label: string;
|
||||||
|
public isLeaf: boolean;
|
||||||
|
public metadata: azdata.ObjectMetadata;
|
||||||
|
public errorMessage: string;
|
||||||
|
|
||||||
|
constructor(nodeName: string, nodeSchema: string, nodeType, parent: azdata.objectexplorer.ObjectExplorerNode) {
|
||||||
|
this.parent = parent;
|
||||||
|
this.nodeType = nodeType;
|
||||||
|
this.metadata = { metadataType: undefined, metadataTypeName: undefined, name: nodeName, schema: nodeSchema, urn: undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
isExpanded(): Thenable<boolean> {
|
||||||
|
throw new Error('Method not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
setExpandedState(expandedState: vscode.TreeItemCollapsibleState): Thenable<void> {
|
||||||
|
throw new Error('Method not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelected(selected: boolean, clearOtherSelections: boolean = undefined): Thenable<void> {
|
||||||
|
throw new Error('Method not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
getChildren(): Thenable<azdata.objectexplorer.ObjectExplorerNode[]> {
|
||||||
|
throw new Error('Method not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
getParent(): Thenable<azdata.objectexplorer.ObjectExplorerNode> {
|
||||||
|
return Promise.resolve(this.parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh(): Thenable<void> {
|
||||||
|
throw new Error('Method not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param nodeName Helperfunction to create a node that is a child of this one
|
||||||
|
* @param nodeSchema The schema to give the child node
|
||||||
|
* @param nodeType The type of node this should be
|
||||||
|
*/
|
||||||
|
createChild(nodeName: string, nodeSchema: string, nodeType: string): ExtHostObjectExplorerNodeStub {
|
||||||
|
return new ExtHostObjectExplorerNodeStub(nodeName, nodeSchema, nodeType, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,11 +8,13 @@
|
|||||||
import * as should from 'should';
|
import * as should from 'should';
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
|
|
||||||
import * as extensionMain from '../main';
|
import { buildSsmsMinCommandArgs, buildUrn, LaunchSsmsDialogParams } from '../main';
|
||||||
|
import { doubleEscapeSingleQuotes, backEscapeDoubleQuotes } from '../utils';
|
||||||
|
import { ExtHostObjectExplorerNodeStub } from './stubs';
|
||||||
|
|
||||||
describe('buildSsmsMinCommandArgs Method Tests', () => {
|
describe('buildSsmsMinCommandArgs Method Tests', () => {
|
||||||
it('Should be built correctly with all params and UseAAD as false', function (): void {
|
it('Should be built correctly with all params and UseAAD as false', function (): void {
|
||||||
let params: extensionMain.LaunchSsmsDialogParams = {
|
const params: LaunchSsmsDialogParams = {
|
||||||
action: 'myAction',
|
action: 'myAction',
|
||||||
server: 'myServer',
|
server: 'myServer',
|
||||||
database: 'myDatabase',
|
database: 'myDatabase',
|
||||||
@@ -21,12 +23,12 @@ describe('buildSsmsMinCommandArgs Method Tests', () => {
|
|||||||
useAad: false,
|
useAad: false,
|
||||||
urn: 'Server\\Database\\Table'
|
urn: 'Server\\Database\\Table'
|
||||||
};
|
};
|
||||||
let args = extensionMain.buildSsmsMinCommandArgs(params);
|
const args = buildSsmsMinCommandArgs(params);
|
||||||
should(args).equal('-a "myAction" -S "myServer" -D "myDatabase" -U "user" -u "Server\\Database\\Table"');
|
should(args).equal('-a "myAction" -S "myServer" -D "myDatabase" -U "user" -u "Server\\Database\\Table"');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should be built correctly with all params and UseAAD as true', function (): void {
|
it('Should be built correctly with all params and UseAAD as true', function (): void {
|
||||||
let params: extensionMain.LaunchSsmsDialogParams = {
|
const params: LaunchSsmsDialogParams = {
|
||||||
action: 'myAction',
|
action: 'myAction',
|
||||||
server: 'myServer',
|
server: 'myServer',
|
||||||
database: 'myDatabase',
|
database: 'myDatabase',
|
||||||
@@ -35,13 +37,13 @@ describe('buildSsmsMinCommandArgs Method Tests', () => {
|
|||||||
useAad: true,
|
useAad: true,
|
||||||
urn: 'Server\\Database\\Table'
|
urn: 'Server\\Database\\Table'
|
||||||
};
|
};
|
||||||
let args = extensionMain.buildSsmsMinCommandArgs(params);
|
const args = buildSsmsMinCommandArgs(params);
|
||||||
// User is omitted since UseAAD is true
|
// User is omitted since UseAAD is true
|
||||||
should(args).equal('-a "myAction" -S "myServer" -D "myDatabase" -G -u "Server\\Database\\Table"');
|
should(args).equal('-a "myAction" -S "myServer" -D "myDatabase" -G -u "Server\\Database\\Table"');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should be built correctly and names escaped correctly', function (): void {
|
it('Should be built correctly and names escaped correctly', function (): void {
|
||||||
let params: extensionMain.LaunchSsmsDialogParams = {
|
const params: LaunchSsmsDialogParams = {
|
||||||
action: 'myAction\'"/\\[]tricky',
|
action: 'myAction\'"/\\[]tricky',
|
||||||
server: 'myServer\'"/\\[]tricky',
|
server: 'myServer\'"/\\[]tricky',
|
||||||
database: 'myDatabase\'"/\\[]tricky',
|
database: 'myDatabase\'"/\\[]tricky',
|
||||||
@@ -50,18 +52,87 @@ describe('buildSsmsMinCommandArgs Method Tests', () => {
|
|||||||
useAad: true,
|
useAad: true,
|
||||||
urn: 'Server\\Database[\'myDatabase\'\'"/\\[]tricky\']\\Table["myTable\'""/\\[]tricky"]'
|
urn: 'Server\\Database[\'myDatabase\'\'"/\\[]tricky\']\\Table["myTable\'""/\\[]tricky"]'
|
||||||
};
|
};
|
||||||
let args = extensionMain.buildSsmsMinCommandArgs(params);
|
const args = buildSsmsMinCommandArgs(params);
|
||||||
// User is omitted since UseAAD is true
|
// User is omitted since UseAAD is true
|
||||||
should(args).equal('-a "myAction\'\\"/\\[]tricky" -S "myServer\'\\"/\\[]tricky" -D "myDatabase\'\\"/\\[]tricky" -G -u "Server\\Database[\'myDatabase\'\'\\"/\\[]tricky\']\\Table[\\"myTable\'\\"\\"/\\[]tricky\\"]"');
|
should(args).equal('-a "myAction\'\\"/\\[]tricky" -S "myServer\'\\"/\\[]tricky" -D "myDatabase\'\\"/\\[]tricky" -G -u "Server\\Database[\'myDatabase\'\'\\"/\\[]tricky\']\\Table[\\"myTable\'\\"\\"/\\[]tricky\\"]"');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should be built correctly with only action and server', function (): void {
|
it('Should be built correctly with only action and server', function (): void {
|
||||||
|
|
||||||
let params: extensionMain.LaunchSsmsDialogParams = {
|
const params: LaunchSsmsDialogParams = {
|
||||||
action: 'myAction',
|
action: 'myAction',
|
||||||
server: 'myServer'
|
server: 'myServer'
|
||||||
};
|
};
|
||||||
let args = extensionMain.buildSsmsMinCommandArgs(params);
|
const args = buildSsmsMinCommandArgs(params);
|
||||||
should(args).equal('-a "myAction" -S "myServer"');
|
should(args).equal('-a "myAction" -S "myServer"');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const serverName = 'My\'Server';
|
||||||
|
const escapedServerName = doubleEscapeSingleQuotes(serverName);
|
||||||
|
const dbName = 'My\'Db';
|
||||||
|
const escapedDbName = doubleEscapeSingleQuotes(dbName);
|
||||||
|
const dbSchema = 'db\'sch';
|
||||||
|
const escapedDbSchema = doubleEscapeSingleQuotes(dbSchema);
|
||||||
|
const tableName = 'My\'Table';
|
||||||
|
const escapedTableName = doubleEscapeSingleQuotes(tableName);
|
||||||
|
const tableSchema = 'tbl\'sch';
|
||||||
|
const escapedTableSchema = doubleEscapeSingleQuotes(tableSchema);
|
||||||
|
|
||||||
|
describe('buildUrn Method Tests', () => {
|
||||||
|
it('Urn should be correct with just server', async function (): Promise<void> {
|
||||||
|
should(await buildUrn(serverName, undefined)).equal(`Server[@Name=\'${escapedServerName}\']`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Urn should be correct with Server and only Databases folder', async function (): Promise<void> {
|
||||||
|
const leafNode: ExtHostObjectExplorerNodeStub =
|
||||||
|
new ExtHostObjectExplorerNodeStub('Databases', undefined, 'Folder', undefined);
|
||||||
|
should(await buildUrn(serverName, leafNode)).equal(`Server[@Name='${escapedServerName}']`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Urn should be correct with Server and Database node', async function (): Promise<void> {
|
||||||
|
const leafNode: ExtHostObjectExplorerNodeStub =
|
||||||
|
new ExtHostObjectExplorerNodeStub('Databases', undefined, 'Folder', undefined)
|
||||||
|
.createChild(dbName, dbSchema, 'Database');
|
||||||
|
should(await buildUrn(serverName, leafNode)).equal(
|
||||||
|
`Server[@Name='${escapedServerName}']/Database[@Name='${escapedDbName}' and @Schema='${escapedDbSchema}']`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Urn should be correct with Multiple levels of Nodes', async function (): Promise<void> {
|
||||||
|
const rootNode: ExtHostObjectExplorerNodeStub =
|
||||||
|
new ExtHostObjectExplorerNodeStub('Databases', undefined, 'Folder', undefined)
|
||||||
|
.createChild(dbName, dbSchema, 'Database')
|
||||||
|
.createChild('Tables', undefined, 'Folder')
|
||||||
|
.createChild(tableName, tableSchema, 'Table');
|
||||||
|
should(await buildUrn(serverName, rootNode)).equal(
|
||||||
|
`Server[@Name='${escapedServerName}']/Database[@Name='${escapedDbName}' and @Schema='${escapedDbSchema}']/Table[@Name='${escapedTableName}' and @Schema='${escapedTableSchema}']`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('doubleEscapeSingleQuotes Method Tests', () => {
|
||||||
|
it('Should return original string if no single quotes', function (): void {
|
||||||
|
const testString: string = 'MyTestString';
|
||||||
|
const ret = doubleEscapeSingleQuotes(testString);
|
||||||
|
should(ret).equal(testString);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should return escaped original string if it contains single quotes', function (): void {
|
||||||
|
const testString: string = 'MyTestString\'\'WithQuotes';
|
||||||
|
const ret = doubleEscapeSingleQuotes(testString);
|
||||||
|
should(ret).equal('MyTestString\'\'\'\'WithQuotes');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('backEscapeDoubleQuotes Method Tests', () => {
|
||||||
|
it('Should return original string if no double quotes', function (): void {
|
||||||
|
const testString: string = 'MyTestString';
|
||||||
|
const ret = backEscapeDoubleQuotes(testString);
|
||||||
|
should(ret).equal(testString);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should return escaped original string if it contains double quotes', function (): void {
|
||||||
|
const testString: string = 'MyTestString\"\"WithQuotes';
|
||||||
|
const ret = backEscapeDoubleQuotes(testString);
|
||||||
|
should(ret).equal('MyTestString\\"\\"WithQuotes');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -40,3 +40,21 @@ export function getConfiguration(extensionName?: string, resource?: vscode.Uri |
|
|||||||
}
|
}
|
||||||
return vscode.workspace.getConfiguration(extensionName, resource as vscode.Uri);
|
return vscode.workspace.getConfiguration(extensionName, resource as vscode.Uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes all single-quotes (') by prefixing them with another single quote ('')
|
||||||
|
* ' => ''
|
||||||
|
* @param value The string to escape
|
||||||
|
*/
|
||||||
|
export function doubleEscapeSingleQuotes(value: string): string {
|
||||||
|
return value.replace(/'/g, '\'\'');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escape all double-quotes (") by prefixing them with a \
|
||||||
|
* " => \"
|
||||||
|
* @param value The string to escape
|
||||||
|
*/
|
||||||
|
export function backEscapeDoubleQuotes(value: string): string {
|
||||||
|
return value.replace(/"/g, '\\"');
|
||||||
|
}
|
||||||
@@ -8,7 +8,6 @@ import { IMainContext } from 'vs/workbench/api/common/extHost.protocol';
|
|||||||
import { ExtHostObjectExplorerShape, SqlMainContext, MainThreadObjectExplorerShape } from 'sql/workbench/api/node/sqlExtHost.protocol';
|
import { ExtHostObjectExplorerShape, SqlMainContext, MainThreadObjectExplorerShape } from 'sql/workbench/api/node/sqlExtHost.protocol';
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { entries } from 'sql/base/common/objects';
|
|
||||||
|
|
||||||
export class ExtHostObjectExplorer implements ExtHostObjectExplorerShape {
|
export class ExtHostObjectExplorer implements ExtHostObjectExplorerShape {
|
||||||
|
|
||||||
@@ -41,7 +40,7 @@ export class ExtHostObjectExplorer implements ExtHostObjectExplorerShape {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExtHostObjectExplorerNode implements azdata.objectexplorer.ObjectExplorerNode {
|
export class ExtHostObjectExplorerNode implements azdata.objectexplorer.ObjectExplorerNode {
|
||||||
public connectionId: string;
|
public connectionId: string;
|
||||||
public nodePath: string;
|
public nodePath: string;
|
||||||
public nodeType: string;
|
public nodeType: string;
|
||||||
@@ -74,11 +73,21 @@ class ExtHostObjectExplorerNode implements azdata.objectexplorer.ObjectExplorerN
|
|||||||
}
|
}
|
||||||
|
|
||||||
getParent(): Thenable<azdata.objectexplorer.ObjectExplorerNode> {
|
getParent(): Thenable<azdata.objectexplorer.ObjectExplorerNode> {
|
||||||
let parentPathEndIndex = this.nodePath.lastIndexOf('/');
|
// Object nodes have a name like <schema>.<name> in the nodePath - we can't use label because
|
||||||
if (parentPathEndIndex === -1) {
|
// that may have additional display information appended to it. Items without metadata are nodes
|
||||||
return Promise.resolve(undefined);
|
// such as folders that don't correspond to actual objects and so just use the label
|
||||||
|
let nodePathName = this.metadata ?
|
||||||
|
`${this.metadata.schema ? this.metadata.schema + '.' : ''}${this.metadata.name}` :
|
||||||
|
this.label;
|
||||||
|
|
||||||
|
// -1 to remove the / as well
|
||||||
|
let parentPathEndIndex: number = this.nodePath.lastIndexOf(nodePathName) - 1;
|
||||||
|
if (parentPathEndIndex < 0) {
|
||||||
|
// At root node
|
||||||
|
Promise.resolve(undefined);
|
||||||
}
|
}
|
||||||
return this._proxy.$getNode(this.connectionId, this.nodePath.slice(0, parentPathEndIndex)).then(nodeInfo => nodeInfo ? new ExtHostObjectExplorerNode(nodeInfo, this.connectionId, this._proxy) : undefined);
|
return this._proxy.$getNode(this.connectionId, this.nodePath.slice(0, parentPathEndIndex)).then(
|
||||||
|
nodeInfo => nodeInfo ? new ExtHostObjectExplorerNode(nodeInfo, this.connectionId, this._proxy) : undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh(): Thenable<void> {
|
refresh(): Thenable<void> {
|
||||||
|
|||||||
111
src/sqltest/workbench/api/extHostObjectExplorer.test.ts
Normal file
111
src/sqltest/workbench/api/extHostObjectExplorer.test.ts
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as azdata from 'azdata';
|
||||||
|
import * as assert from 'assert';
|
||||||
|
import * as TypeMoq from 'typemoq';
|
||||||
|
|
||||||
|
import { MainThreadObjectExplorerShape } from 'sql/workbench/api/node/sqlExtHost.protocol';
|
||||||
|
import { ExtHostObjectExplorerNode } from 'sql/workbench/api/node/extHostObjectExplorer';
|
||||||
|
|
||||||
|
const nodes: { [nodeName: string]: azdata.NodeInfo } =
|
||||||
|
{
|
||||||
|
'Server1': {
|
||||||
|
nodePath: 'MyServer',
|
||||||
|
nodeStatus: '',
|
||||||
|
nodeSubType: '',
|
||||||
|
nodeType: 'Server',
|
||||||
|
isLeaf: false,
|
||||||
|
label: 'MyServer',
|
||||||
|
metadata: undefined,
|
||||||
|
errorMessage: ''
|
||||||
|
},
|
||||||
|
'DatabasesFolder': {
|
||||||
|
nodePath: 'MyServer/Databases',
|
||||||
|
nodeStatus: '',
|
||||||
|
nodeSubType: '',
|
||||||
|
nodeType: 'Folder',
|
||||||
|
isLeaf: false,
|
||||||
|
label: 'Databases',
|
||||||
|
metadata: undefined,
|
||||||
|
errorMessage: ''
|
||||||
|
},
|
||||||
|
'Database1': {
|
||||||
|
nodePath: 'MyServer/Databases/MyDatabase',
|
||||||
|
nodeStatus: '',
|
||||||
|
nodeSubType: '',
|
||||||
|
nodeType: 'Database',
|
||||||
|
isLeaf: false,
|
||||||
|
label: 'MyDatabase',
|
||||||
|
metadata: undefined,
|
||||||
|
errorMessage: ''
|
||||||
|
},
|
||||||
|
'Database2': {
|
||||||
|
nodePath: 'MyServer/Databases/My/TrickyDatabase',
|
||||||
|
nodeStatus: '',
|
||||||
|
nodeSubType: '',
|
||||||
|
nodeType: 'Database',
|
||||||
|
isLeaf: false,
|
||||||
|
label: 'My/TrickyDatabase',
|
||||||
|
metadata: undefined,
|
||||||
|
errorMessage: ''
|
||||||
|
},
|
||||||
|
'TablesFolder': {
|
||||||
|
nodePath: 'MyServer/Databases/My/TrickyDatabase/Tables',
|
||||||
|
nodeStatus: '',
|
||||||
|
nodeSubType: '',
|
||||||
|
nodeType: 'Folder',
|
||||||
|
isLeaf: false,
|
||||||
|
label: 'Tables',
|
||||||
|
metadata: undefined,
|
||||||
|
errorMessage: ''
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
suite('ExtHostObjectExplorer Tests', () => {
|
||||||
|
let mockProxy: TypeMoq.Mock<MainThreadObjectExplorerShape>;
|
||||||
|
suiteSetup(() => {
|
||||||
|
mockProxy = TypeMoq.Mock.ofInstance(<MainThreadObjectExplorerShape>{
|
||||||
|
$getNode: (connectionId: string, nodePath?: string): Thenable<azdata.NodeInfo> => undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
mockProxy.setup(p =>
|
||||||
|
p.$getNode(TypeMoq.It.isAny(), TypeMoq.It.isAny()))
|
||||||
|
.returns((connectionId, nodePath) => {
|
||||||
|
return Promise.resolve<azdata.NodeInfo>(nodes[Object.keys(nodes).find(key =>
|
||||||
|
nodes[key].nodePath === nodePath)]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
suite('ExtHostObjectExplorerNode', () => {
|
||||||
|
let extHostObjectExplorerNode: ExtHostObjectExplorerNode;
|
||||||
|
suite('getParent', () => {
|
||||||
|
test('Should return undefined if no parent', async () => {
|
||||||
|
extHostObjectExplorerNode = new ExtHostObjectExplorerNode(nodes['Server1'], 'connectionId', mockProxy.object);
|
||||||
|
assert.equal(await extHostObjectExplorerNode.getParent(), undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return root with direct descendent of root', async () => {
|
||||||
|
extHostObjectExplorerNode = new ExtHostObjectExplorerNode(nodes['DatabasesFolder'], 'connectionId', mockProxy.object);
|
||||||
|
assert.equal((await extHostObjectExplorerNode.getParent()).nodePath, nodes['Server1'].nodePath);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return correct parent with further descendent of root', async () => {
|
||||||
|
extHostObjectExplorerNode = new ExtHostObjectExplorerNode(nodes['Database1'], 'connectionId', mockProxy.object);
|
||||||
|
assert.equal((await extHostObjectExplorerNode.getParent()).nodePath, nodes['DatabasesFolder'].nodePath);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return correct parent with node having / in its name', async () => {
|
||||||
|
extHostObjectExplorerNode = new ExtHostObjectExplorerNode(nodes['Database2'], 'connectionId', mockProxy.object);
|
||||||
|
assert.equal((await extHostObjectExplorerNode.getParent()).nodePath, nodes['DatabasesFolder'].nodePath);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return correct parent with parent node having / in its name', async () => {
|
||||||
|
extHostObjectExplorerNode = new ExtHostObjectExplorerNode(nodes['TablesFolder'], 'connectionId', mockProxy.object);
|
||||||
|
assert.equal((await extHostObjectExplorerNode.getParent()).nodePath, nodes['Database2'].nodePath);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user