Add Table Menu for SQL Bindings promptForObjectName (#19358)

* first wip for table menu prompt

* fix up some table prompts

* use simpleExecuteRequest

* fix table prompt scenario for sql binding also

* fix/add more testing and address comments

* add brackets to selected database

* add manually entered option
This commit is contained in:
Vasu Bhog
2022-05-18 14:51:45 -07:00
committed by GitHub
parent 1210bceded
commit 8967ec36f4
10 changed files with 271 additions and 83 deletions

View File

@@ -7,7 +7,8 @@ import * as vscode from 'vscode';
import * as path from 'path';
import * as utils from './utils';
import * as constants from './constants';
import { BindingType } from 'sql-bindings';
import * as azureFunctionsContracts from '../contracts/azureFunctions/azureFunctionsContracts';
import { BindingType, IConnectionStringInfo } from 'sql-bindings';
import { ConnectionDetails, IConnectionInfo } from 'vscode-mssql';
// https://github.com/microsoft/vscode-azurefunctions/blob/main/src/vscode-azurefunctions.api.d.ts
import { AzureFunctionsExtensionApi } from '../../../types/vscode-azurefunctions.api';
@@ -313,23 +314,54 @@ export async function promptForBindingType(funcName?: string): Promise<BindingTy
/**
* Prompts the user to enter object name for the SQL query
* @param bindingType Type of SQL Binding
* @param connectionInfo (optional) connection info from the selected connection profile
* if left undefined we prompt to manually enter the object name
* @returns the object name from user's input or menu choice
*/
export async function promptForObjectName(bindingType: BindingType): Promise<string | undefined> {
return vscode.window.showInputBox({
prompt: bindingType === BindingType.input ? constants.sqlTableOrViewToQuery : constants.sqlTableToUpsert,
placeHolder: constants.placeHolderObject,
validateInput: input => input ? undefined : constants.nameMustNotBeEmpty,
ignoreFocusOut: true
});
export async function promptForObjectName(bindingType: BindingType, connectionInfo?: IConnectionInfo): Promise<string | undefined> {
// show the connection string methods (user input and connection profile options)
let connectionURI: string | undefined;
let selectedDatabase: string | undefined;
while (true) {
if (!connectionInfo) {
// prompt is shown when user selects an existing connection string setting
// or manually enters a connection string
return promptToManuallyEnterObjectName(bindingType);
}
// TODO create path to solve for selectView (first need to support views as well)
// Prompt user to select a table based on connection profile and selected database}
// get connectionURI and selectedDatabase to be used for listing tables query request
connectionURI = await getConnectionURI(connectionInfo);
if (!connectionURI) {
// User cancelled or mssql connection error
// we will then prompt user to choose a connection profile again
continue;
}
selectedDatabase = await promptSelectDatabase(connectionURI);
if (!selectedDatabase) {
// User cancelled
// we will then prompt user to choose a connection profile again
continue;
}
connectionInfo.database = selectedDatabase;
let selectedObjectName = await promptSelectTable(connectionURI, bindingType, selectedDatabase);
return selectedObjectName;
}
}
/**
* Prompts the user to enter connection setting and updates it from AF project
* @param projectUri Azure Function project uri
* @param connectionInfo connection info from the user to update the connection string
* @param connectionInfo (optional) connection info from the user to update the connection string,
* if left undefined we prompt the user for the connection info
* @returns connection string setting name to be used for the createFunction API
*/
export async function promptAndUpdateConnectionStringSetting(projectUri: vscode.Uri | undefined, connectionInfo?: IConnectionInfo): Promise<string | undefined> {
export async function promptAndUpdateConnectionStringSetting(projectUri: vscode.Uri | undefined, connectionInfo?: IConnectionInfo): Promise<IConnectionStringInfo | undefined> {
let connectionStringSettingName: string | undefined;
const vscodeMssqlApi = await utils.getVscodeMssqlApi();
@@ -459,6 +491,11 @@ export async function promptAndUpdateConnectionStringSetting(projectUri: vscode.
validateInput: input => input ? undefined : constants.valueMustNotBeEmpty
}
) ?? '';
if (!connectionString) {
// User cancelled
// we can prompt for connection string methods again
continue;
}
} else {
// Let user choose from existing connections to create connection string from
connectionInfo = await vscodeMssqlApi.promptForConnection(true);
@@ -509,7 +546,7 @@ export async function promptAndUpdateConnectionStringSetting(projectUri: vscode.
ignoreFocusOut: true
});
}
return connectionStringSettingName;
return { connectionStringSettingName: connectionStringSettingName!, connectionInfo: connectionInfo };
}
/**
@@ -579,10 +616,9 @@ export async function promptConnectionStringPasswordAndUpdateConnectionString(co
}
}
export async function promptSelectDatabase(connectionInfo: IConnectionInfo): Promise<string | undefined> {
export async function promptSelectDatabase(connectionURI: string): Promise<string | undefined> {
const vscodeMssqlApi = await utils.getVscodeMssqlApi();
let connectionURI = await vscodeMssqlApi.connect(connectionInfo);
let listDatabases = await vscodeMssqlApi.listDatabases(connectionURI);
const selectedDatabase = (await vscode.window.showQuickPick(listDatabases, {
canPickMany: false,
@@ -596,3 +632,77 @@ export async function promptSelectDatabase(connectionInfo: IConnectionInfo): Pro
}
return selectedDatabase;
}
export async function getConnectionURI(connectionInfo: IConnectionInfo): Promise<string | undefined> {
const vscodeMssqlApi = await utils.getVscodeMssqlApi();
let connectionURI: string = '';
try {
await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: constants.connectionProgressTitle,
cancellable: false
}, async (_progress, _token) => {
// show progress bar while connecting to the users selected connection profile
connectionURI = await vscodeMssqlApi.connect(connectionInfo!);
}
);
} catch (e) {
// mssql connection error will be shown to the user
return undefined;
}
return connectionURI;
}
export async function promptSelectTable(connectionURI: string, bindingType: BindingType, selectedDatabase: string): Promise<string | undefined> {
const vscodeMssqlApi = await utils.getVscodeMssqlApi();
const userObjectName = bindingType === BindingType.input ? constants.enterObjectName : constants.enterObjectNameToUpsert;
// Create query to get list of tables from database selected
let tableQuery = tablesQuery(selectedDatabase);
const params = { ownerUri: connectionURI, queryString: tableQuery };
// send SimpleExecuteRequest query to STS to get list of schema and tables based on the connection profile of the user
let queryResult: azureFunctionsContracts.SimpleExecuteResult = await vscodeMssqlApi.sendRequest(azureFunctionsContracts.SimpleExecuteRequest.type, params);
// Get schema and table names from query result rows
const tableNames = queryResult.rows.map(r => r[0].displayValue);
// add manual entry option to table names list for user to choose from as well (with pencil icon)
let manuallyEnterObjectName = '$(pencil) ' + userObjectName;
tableNames.unshift(manuallyEnterObjectName);
// prompt user to select table from list of tables options
while (true) {
let selectedObject = await vscode.window.showQuickPick(tableNames, {
canPickMany: false,
title: constants.selectTable,
ignoreFocusOut: true
});
if (selectedObject === manuallyEnterObjectName) {
let selectedObject = promptToManuallyEnterObjectName(bindingType);
if (!selectedObject) {
// user cancelled so we will show the tables prompt again
continue;
}
}
return selectedObject;
}
}
export function tablesQuery(selectedDatabase: string): string {
let quotedDatabase = '[' + utils.escapeClosingBrackets(selectedDatabase) + ']';
return `SELECT CONCAT(QUOTENAME(table_schema),'.',QUOTENAME(table_name)) from ${quotedDatabase}.INFORMATION_SCHEMA.TABLES where TABLE_TYPE = 'BASE TABLE'`;
}
export async function promptToManuallyEnterObjectName(bindingType: BindingType): Promise<string | undefined> {
// user manually enters table or view to query or upsert into
let selectedObject = await vscode.window.showInputBox({
prompt: bindingType === BindingType.input ? constants.sqlTableOrViewToQuery : constants.sqlTableToUpsert,
placeHolder: constants.placeHolderObject,
validateInput: input => input ? undefined : constants.nameMustNotBeEmpty,
ignoreFocusOut: true
});
return selectedObject;
}

View File

@@ -75,6 +75,9 @@ export const userPasswordLater = localize('userPasswordLater', 'In order to user
export const openFile = localize('openFile', "Open File");
export const closeButton = localize('closeButton', "Close");
export const connectionProgressTitle = localize('connectionProgressTitle', "Testing SQL Server connection...");
export const enterObjectName = localize('enterObjectName', 'Enter SQL table or view to query');
export const enterObjectNameToUpsert = localize('enterObjectNameToUpsert', 'Enter SQL table to upsert into');
export const selectTable = localize('selectTable', 'Select table to use');
export const selectConnectionError = (err?: any): string => err ? localize('selectConnectionError', "Failed to set connection string app setting: {0}", utils.getErrorMessage(err)) : localize('unableToSetConnectionString', "Failed to set connection string app setting");
export function selectBindingType(funcName?: string): string { return funcName ? localize('selectBindingTypeToSpecifiedFunction', "Select type of binding for the function '{0}'", funcName) : localize('selectBindingType', "Select type of binding"); }
export function settingAlreadyExists(settingName: string): string { return localize('SettingAlreadyExists', 'Local app setting \'{0}\' already exists. Overwrite?', settingName); }

View File

@@ -173,4 +173,3 @@ export function getErrorType(error: any): string | undefined {
return 'UnknownError';
}
}

View File

@@ -19,3 +19,69 @@ export namespace AddSqlBindingRequest {
export namespace GetAzureFunctionsRequest {
export const type = new RequestType<GetAzureFunctionsParams, GetAzureFunctionsResult, void, void>('azureFunctions/getAzureFunctions');
}
// ------------------------------- < Execute String > ------------------------------------
// source: https://github.com/microsoft/azuredatastudio/blob/main/src/sql/azdata.d.ts#L1021
export interface SimpleExecuteParams {
queryString: string;
ownerUri: string;
}
/**
* Simple Query Execute Result will return rowCount, columnInfo, and rows from STS request
* source: https://github.com/microsoft/azuredatastudio/blob/main/src/sql/azdata.d.ts#L1026
* rowCount is the number of rows returned with resultset
* columnInfo is the details about the columns that are povided as solutions
* rows is a 2D array of the cell values from the resultset
*/
export interface SimpleExecuteResult {
rowCount: number;
columnInfo: IDbColumn[];
rows: DbCellValue[][];
}
// source: https://github.com/microsoft/sqlops-dataprotocolclient/blob/main/src/protocol.ts#L437
export namespace SimpleExecuteRequest {
export const type = new RequestType<SimpleExecuteParams, SimpleExecuteResult, void, void>('query/simpleexecute');
}
// source: https://github.com/microsoft/azuredatastudio/blob/main/src/sql/azdata.d.ts#L907
export interface IDbColumn {
allowDBNull?: boolean | undefined;
baseCatalogName: string;
baseColumnName: string;
baseSchemaName: string;
baseServerName: string;
baseTableName: string;
columnName: string;
columnOrdinal?: number | undefined;
columnSize?: number | undefined;
isAliased?: boolean | undefined;
isAutoIncrement?: boolean | undefined;
isExpression?: boolean | undefined;
isHidden?: boolean | undefined;
isIdentity?: boolean | undefined;
isKey?: boolean | undefined;
isBytes?: boolean | undefined;
isChars?: boolean | undefined;
isSqlVariant?: boolean | undefined;
isUdt?: boolean | undefined;
dataType: string;
isXml?: boolean | undefined;
isJson?: boolean | undefined;
isLong?: boolean | undefined;
isReadOnly?: boolean | undefined;
isUnique?: boolean | undefined;
numericPrecision?: number | undefined;
numericScale?: number | undefined;
udtAssemblyQualifiedName: string;
dataTypeName: string;
}
// source: https://github.com/microsoft/azuredatastudio/blob/main/src/sql/azdata.d.ts#L1066
export interface DbCellValue {
displayValue: string;
isNull: boolean;
invariantCultureDisplayValue: string;
}

View File

@@ -77,17 +77,7 @@ export async function launchAddSqlBindingQuickpick(uri: vscode.Uri | undefined):
TelemetryReporter.createActionEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.getBindingType)
.withAdditionalProperties(propertyBag).send();
// 3. ask for object name for the binding
quickPickStep = 'getObjectName';
const objectName = await azureFunctionsUtils.promptForObjectName(selectedBinding);
if (!objectName) {
return;
}
TelemetryReporter.createActionEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.getObjectName)
.withAdditionalProperties(propertyBag).send();
// 4. ask for connection string setting name
// 3. ask for connection string setting name
let projectUri: vscode.Uri | undefined;
try {
projectUri = await azureFunctionsUtils.getAFProjectContainingFile(uri);
@@ -96,17 +86,26 @@ export async function launchAddSqlBindingQuickpick(uri: vscode.Uri | undefined):
}
quickPickStep = 'updateConnectionString';
let connectionStringSettingName = await azureFunctionsUtils.promptAndUpdateConnectionStringSetting(projectUri);
if (!connectionStringSettingName) {
let connectionStringInfo = await azureFunctionsUtils.promptAndUpdateConnectionStringSetting(projectUri);
if (!connectionStringInfo) {
return;
}
TelemetryReporter.createActionEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.updateConnectionString)
.withAdditionalProperties(propertyBag).send();
// 4. ask for object name for the binding
quickPickStep = 'getObjectName';
const objectName = await azureFunctionsUtils.promptForObjectName(selectedBinding, connectionStringInfo.connectionInfo);
if (!objectName) {
return;
}
TelemetryReporter.createActionEvent(TelemetryViews.SqlBindingsQuickPick, TelemetryActions.getObjectName)
.withAdditionalProperties(propertyBag).send();
// 5. insert binding
try {
quickPickStep = 'insertBinding';
const result = await addSqlBinding(selectedBinding, uri.fsPath, azureFunctionName, objectName, connectionStringSettingName);
const result = await addSqlBinding(selectedBinding, uri.fsPath, azureFunctionName, objectName, connectionStringInfo.connectionStringSettingName!);
if (!result.success) {
void vscode.window.showErrorMessage(result.errorMessage);

View File

@@ -3,8 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { ITreeNodeInfo } from 'vscode-mssql';
import { IExtension, BindingType, GetAzureFunctionsResult, ResultStatus } from 'sql-bindings';
import { IConnectionInfo, ITreeNodeInfo } from 'vscode-mssql';
import { IExtension, BindingType, GetAzureFunctionsResult, ResultStatus, IConnectionStringInfo } from 'sql-bindings';
import { addSqlBinding, createAzureFunction, getAzureFunctions } from './services/azureFunctionsService';
import { launchAddSqlBindingQuickpick } from './dialogs/addSqlBindingQuickpick';
import { promptForBindingType, promptAndUpdateConnectionStringSetting, promptForObjectName } from './common/azureFunctionsUtils';
@@ -23,11 +23,11 @@ export async function activate(context: vscode.ExtensionContext): Promise<IExten
promptForBindingType: async (funcName?: string): Promise<BindingType | undefined> => {
return promptForBindingType(funcName);
},
promptForObjectName: async (bindingType: BindingType): Promise<string | undefined> => {
return promptForObjectName(bindingType);
promptForObjectName: async (bindingType: BindingType, connectionInfo?: IConnectionInfo): Promise<string | undefined> => {
return promptForObjectName(bindingType, connectionInfo);
},
promptAndUpdateConnectionStringSetting: async (projectUri: vscode.Uri | undefined): Promise<string | undefined> => {
return promptAndUpdateConnectionStringSetting(projectUri);
promptAndUpdateConnectionStringSetting: async (projectUri: vscode.Uri | undefined, connectionInfo?: IConnectionInfo): Promise<IConnectionStringInfo | undefined> => {
return promptAndUpdateConnectionStringSetting(projectUri, connectionInfo);
},
getAzureFunctions: async (filePath: string): Promise<GetAzureFunctionsResult> => {
return getAzureFunctions(filePath);

View File

@@ -11,7 +11,7 @@ import * as azureFunctionsUtils from '../common/azureFunctionsUtils';
import * as constants from '../common/constants';
import * as azureFunctionsContracts from '../contracts/azureFunctions/azureFunctionsContracts';
import { CreateAzureFunctionStep, TelemetryActions, TelemetryReporter, TelemetryViews, ExitReason } from '../common/telemetry';
import { AddSqlBindingParams, BindingType, GetAzureFunctionsParams, GetAzureFunctionsResult, ResultStatus } from 'sql-bindings';
import { AddSqlBindingParams, BindingType, GetAzureFunctionsParams, GetAzureFunctionsResult, IConnectionStringInfo, ResultStatus } from 'sql-bindings';
import { IConnectionInfo, ITreeNodeInfo } from 'vscode-mssql';
import { createAddConnectionStringStep } from '../createNewProject/addConnectionStringStep';
@@ -83,6 +83,7 @@ export async function createAzureFunction(node?: ITreeNodeInfo): Promise<void> {
return;
}
projectFolder = projectFolders[0].fsPath;
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, telemetryStep)
.withAdditionalProperties(propertyBag).send();
break;
@@ -125,45 +126,13 @@ export async function createAzureFunction(node?: ITreeNodeInfo): Promise<void> {
}
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, telemetryStep)
.withAdditionalProperties(propertyBag).withConnectionInfo(connectionInfo).send();
telemetryStep = CreateAzureFunctionStep.getConnectionProfile;
let connectionURI: string = '';
try {
await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: constants.connectionProgressTitle,
cancellable: false
}, async (_progress, _token) => {
// list databases based on connection profile selected
connectionURI = await vscodeMssqlApi.connect(connectionInfo!);
}
);
} catch (e) {
// mssql connection error will be shown to the user
// we will then prompt user to choose a connection profile again
continue;
}
// list databases based on connection profile selected
telemetryStep = CreateAzureFunctionStep.getDatabase;
let listDatabases = await vscodeMssqlApi.listDatabases(connectionURI);
const selectedDatabase = (await vscode.window.showQuickPick(listDatabases, {
canPickMany: false,
title: constants.selectDatabase,
ignoreFocusOut: true
}));
if (!selectedDatabase) {
// User cancelled
// we will then prompt user to choose a connection profile again
continue;
}
connectionInfo.database = selectedDatabase;
telemetryStep = CreateAzureFunctionStep.getObjectName;
// prompt user for object name to create function from
objectName = await azureFunctionsUtils.promptForObjectName(selectedBinding);
objectName = await azureFunctionsUtils.promptForObjectName(selectedBinding, connectionInfo);
if (!objectName) {
// user cancelled
return;
continue;
}
break;
}
@@ -213,16 +182,20 @@ export async function createAzureFunction(node?: ITreeNodeInfo): Promise<void> {
let templateId: string = selectedBindingType === BindingType.input ? constants.inputTemplateID : constants.outputTemplateID;
// prompt for Connection String Setting Name
let connectionStringSettingName: string | undefined = constants.sqlConnectionStringSetting;
let connectionStringInfo: IConnectionStringInfo | undefined = { connectionStringSettingName: constants.sqlConnectionStringSetting, connectionInfo: connectionInfo };
if (!isCreateNewProject && projectFile) {
telemetryStep = CreateAzureFunctionStep.getConnectionStringSettingName;
connectionStringSettingName = await azureFunctionsUtils.promptAndUpdateConnectionStringSetting(vscode.Uri.parse(projectFile), connectionInfo);
connectionStringInfo = await azureFunctionsUtils.promptAndUpdateConnectionStringSetting(vscode.Uri.parse(projectFile), connectionInfo);
if (!connectionStringInfo) {
// User cancelled connection string setting name prompt or connection string method prompt
return;
}
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, telemetryStep)
.withAdditionalProperties(propertyBag)
.withConnectionInfo(connectionInfo).send();
}
// addtional execution step that will be used by vscode-azurefunctions to execute only when creating a new azure function project
let connectionStringExecuteStep = createAddConnectionStringStep(projectFolder, connectionInfo, connectionStringSettingName);
let connectionStringExecuteStep = createAddConnectionStringStep(projectFolder, connectionInfo, connectionStringInfo.connectionStringSettingName);
// create C# Azure Function with SQL Binding
telemetryStep = 'createFunctionAPI';
@@ -233,7 +206,7 @@ export async function createAzureFunction(node?: ITreeNodeInfo): Promise<void> {
templateId: templateId,
functionName: functionName,
functionSettings: {
connectionStringSetting: connectionStringSettingName,
connectionStringSetting: connectionStringInfo.connectionStringSettingName,
...(selectedBindingType === BindingType.input && { object: objectName }),
...(selectedBindingType === BindingType.output && { table: objectName })
},

View File

@@ -7,6 +7,7 @@
declare module 'sql-bindings' {
import * as vscode from 'vscode';
import { IConnectionInfo } from 'vscode-mssql';
export const enum extension {
name = 'Microsoft.sql-bindings',
@@ -36,14 +37,20 @@ declare module 'sql-bindings' {
/**
* Prompts the user to enter object name for the SQL query
* @param bindingType Type of SQL Binding
* @param connectionInfo (optional) connection info from the selected connection profile
* if left undefined we prompt to manually enter the object name
* @returns the object name from user's input or menu choice
*/
promptForObjectName(bindingType: BindingType): Promise<string | undefined>;
promptForObjectName(bindingType: BindingType, connectionInfo?: IConnectionInfo): Promise<string | undefined>;
/**
* Prompts the user to enter connection setting and updates it from AF project
* @param projectUri Azure Function project uri
*/
promptAndUpdateConnectionStringSetting(projectUri: vscode.Uri | undefined): Promise<string | undefined>;
* @param connectionInfo (optional) connection info from the user to update the connection string,
* if left undefined we prompt the user for the connection info
* @returns connection string setting name to be used for the createFunction API
*/
promptAndUpdateConnectionStringSetting(projectUri: vscode.Uri | undefined, connectionInfo?: IConnectionInfo): Promise<IConnectionStringInfo | undefined>;
/**
* Gets the names of the Azure Functions in the file
@@ -119,4 +126,12 @@ declare module 'sql-bindings' {
azureFunctions: string[];
}
/**
* Result from promptAndUpdateConnectionStringSetting
*/
export interface IConnectionStringInfo {
connectionStringSettingName: string;
connectionInfo: IConnectionInfo | undefined;
}
}

View File

@@ -7,14 +7,17 @@ import * as should from 'should';
import * as sinon from 'sinon';
import * as vscode from 'vscode';
import * as TypeMoq from 'typemoq';
import * as fs from 'fs';
import * as utils from '../../common/utils';
import * as constants from '../../common/constants';
import * as azureFunctionUtils from '../../common/azureFunctionsUtils';
import * as azureFunctionService from '../../services/azureFunctionsService';
import * as azureFunctionsContracts from '../../contracts/azureFunctions/azureFunctionsContracts';
import { createTestUtils, TestUtils, createTestCredentials } from '../testUtils';
import { launchAddSqlBindingQuickpick } from '../../dialogs/addSqlBindingQuickpick';
import { BindingType } from 'sql-bindings';
import { IConnectionInfo } from 'vscode-mssql';
let testUtils: TestUtils;
const fileUri = vscode.Uri.file('testUri');
@@ -46,6 +49,17 @@ describe('Add SQL Binding quick pick', () => {
it('Should show error if adding SQL binding was not successful', async function (): Promise<void> {
sinon.stub(utils, 'getVscodeMssqlApi').resolves(testUtils.vscodeMssqlIExtension.object);
let connectionCreds: IConnectionInfo = createTestCredentials();// Mocks promptForConnection
let connectionDetails = { options: connectionCreds };
// set test vscode-mssql API calls
testUtils.vscodeMssqlIExtension.setup(x => x.promptForConnection(true)).returns(() => Promise.resolve(connectionCreds));
testUtils.vscodeMssqlIExtension.setup(x => x.getConnectionString(connectionDetails, true, false)).returns(() => Promise.resolve('testConnectionString'));
testUtils.vscodeMssqlIExtension.setup(x => x.connect(connectionCreds)).returns(() => Promise.resolve('testConnectionURI'));
testUtils.vscodeMssqlIExtension.setup(x => x.listDatabases('testConnectionURI')).returns(() => Promise.resolve(['testDb']));
const params = { ownerUri: 'testConnectionURI', queryString: azureFunctionUtils.tablesQuery('testDb') };
testUtils.vscodeMssqlIExtension.setup(x => x.sendRequest(azureFunctionsContracts.SimpleExecuteRequest.type, params))
.returns(() => Promise.resolve({ rowCount: 1, columnInfo: [], rows: [['[schema].[testTable]']] }));
sinon.stub(azureFunctionService, 'getAzureFunctions').withArgs(fileUri.fsPath).returns(
Promise.resolve({
success: true,
@@ -53,7 +67,7 @@ describe('Add SQL Binding quick pick', () => {
azureFunctions: ['af1', 'af2']
}));
//failure since no AFs are found in the project
sinon.stub(azureFunctionUtils, 'getAFProjectContainingFile').resolves(undefined);
const errormsg = 'Error inserting binding';
sinon.stub(azureFunctionService, 'addSqlBinding').withArgs(
sinon.match.any, sinon.match.any, sinon.match.any,
@@ -65,13 +79,22 @@ describe('Add SQL Binding quick pick', () => {
const spy = sinon.spy(vscode.window, 'showErrorMessage');
// select Azure function
let quickpickStub = sinon.stub(vscode.window, 'showQuickPick').onFirstCall().resolves({ label: 'af1' });
let quickpickStub = sinon.stub(vscode.window, 'showQuickPick').returns(Promise.resolve('af1') as any);
// select input or output binding
quickpickStub.onSecondCall().resolves(<any>{ label: constants.input, type: BindingType.input });
// give object name
let inputBoxStub = sinon.stub(vscode.window, 'showInputBox').onFirstCall().resolves('dbo.table1');
sinon.stub(azureFunctionUtils, 'getAFProjectContainingFile').resolves(vscode.Uri.file('testUri'));
// select connection profile - create new
quickpickStub.onThirdCall().resolves(<any>{ label: constants.createNewLocalAppSettingWithIcon });
// give connection string setting name
inputBoxStub.onSecondCall().resolves('sqlConnectionString');
sinon.stub(vscode.window, 'showInputBox').onFirstCall().resolves('sqlConnectionString');
quickpickStub.onCall(3).returns(Promise.resolve(constants.connectionProfile) as any);
quickpickStub.onCall(4).returns(Promise.resolve(constants.yesString) as any);
// setLocalAppSetting fails if we dont set writeFile stub
sinon.stub(fs.promises, 'writeFile');
sinon.stub(azureFunctionUtils, 'setLocalAppSetting').withArgs(sinon.match.any, 'sqlConnectionString', 'testConnectionString').returns(Promise.resolve(true));
sinon.stub(utils, 'executeCommand').resolves('downloaded nuget package');
quickpickStub.onCall(5).returns(Promise.resolve('testDb') as any);
quickpickStub.onCall(6).returns(Promise.resolve('[schema].[testTable]') as any);
await launchAddSqlBindingQuickpick(vscode.Uri.file('testUri'));

View File

@@ -79,7 +79,7 @@ export function createTestCredentials(): vscodeMssql.IConnectionInfo {
accountId: 'test-account-id',
tenantId: 'test-tenant-id',
port: 1234,
authenticationType: 'test',
authenticationType: 'SqlLogin',
azureAccountToken: '',
expiresOn: 0,
encrypt: false,