From c5093dbb5a587bb4c047f855e87e1bff314fd702 Mon Sep 17 00:00:00 2001 From: Vasu Bhog Date: Mon, 20 Jun 2022 15:48:38 -0700 Subject: [PATCH] Add Azure Function Service Error/Cancel tests (#19758) * cleanup tests * exit/error scenario tests * remove + from manuallyEnterObjectName * address comments * add resolve for writeFile --- .../src/common/azureFunctionsUtils.ts | 2 +- .../sql-bindings/src/common/constants.ts | 1 + .../src/services/azureFunctionsService.ts | 2 +- .../src/test/addConnectionStringStep.test.ts | 2 +- .../test/common/azureFunctionsUtils.test.ts | 4 +- .../dialog/addSqlBindingQuickpick.test.ts | 4 +- .../service/azureFunctionsService.test.ts | 173 ++++++++++++++---- extensions/sql-bindings/src/test/testUtils.ts | 2 +- 8 files changed, 151 insertions(+), 39 deletions(-) diff --git a/extensions/sql-bindings/src/common/azureFunctionsUtils.ts b/extensions/sql-bindings/src/common/azureFunctionsUtils.ts index bbbc803919..865123c1b2 100644 --- a/extensions/sql-bindings/src/common/azureFunctionsUtils.ts +++ b/extensions/sql-bindings/src/common/azureFunctionsUtils.ts @@ -680,7 +680,7 @@ export async function promptSelectTable(connectionURI: string, bindingType: Bind // 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; + let manuallyEnterObjectName = constants.manuallyEnterObjectName(userObjectName); tableNames.unshift(manuallyEnterObjectName); // prompt user to select table from list of tables options while (true) { diff --git a/extensions/sql-bindings/src/common/constants.ts b/extensions/sql-bindings/src/common/constants.ts index 3c0f5b6cef..64f5292383 100644 --- a/extensions/sql-bindings/src/common/constants.ts +++ b/extensions/sql-bindings/src/common/constants.ts @@ -84,3 +84,4 @@ export function settingAlreadyExists(settingName: string): string { return local export function failedToParse(filename: string, error: any): string { return localize('failedToParse', 'Failed to parse "{0}": {1}.', filename, utils.getErrorMessage(error)); } export function addSqlBinding(functionName: string): string { return localize('addSqlBinding', 'Adding SQL Binding to function "{0}"...'), functionName; } export function errorNewAzureFunction(error: any): string { return localize('errorNewAzureFunction', 'Error creating new Azure Function: {0}', utils.getErrorMessage(error)); } +export function manuallyEnterObjectName(userObjectName: string): string { return `$(pencil) ${userObjectName}`; } diff --git a/extensions/sql-bindings/src/services/azureFunctionsService.ts b/extensions/sql-bindings/src/services/azureFunctionsService.ts index 1e713c8402..758459f0ad 100644 --- a/extensions/sql-bindings/src/services/azureFunctionsService.ts +++ b/extensions/sql-bindings/src/services/azureFunctionsService.ts @@ -113,13 +113,13 @@ export async function createAzureFunction(node?: ITreeNodeInfo): Promise { // Get connection string parameters and construct object name from prompt or connectionInfo given let objectName: string | undefined; - const vscodeMssqlApi = await utils.getVscodeMssqlApi(); if (!node) { // if user selects command in command palette we prompt user for information telemetryStep = CreateAzureFunctionStep.launchFromCommandPalette; // prompt user for connection profile to get connection info while (true) { try { + const vscodeMssqlApi = await utils.getVscodeMssqlApi(); connectionInfo = await vscodeMssqlApi.promptForConnection(true); } catch (e) { // user cancelled while creating connection profile diff --git a/extensions/sql-bindings/src/test/addConnectionStringStep.test.ts b/extensions/sql-bindings/src/test/addConnectionStringStep.test.ts index 0402502a1e..15877a0f68 100644 --- a/extensions/sql-bindings/src/test/addConnectionStringStep.test.ts +++ b/extensions/sql-bindings/src/test/addConnectionStringStep.test.ts @@ -41,7 +41,7 @@ describe('Add Connection String Execute Step', () => { // Include Password Prompt - Yes to include password let quickPickStub = sinon.stub(vscode.window, 'showQuickPick').onFirstCall().resolves((constants.yesString) as any); // setup stub for setting local app setting with connection string - sinon.stub(fs.promises, 'writeFile'); + sinon.stub(fs.promises, 'writeFile').resolves(); sinon.stub(azureFunctionUtils, 'setLocalAppSetting').withArgs(sinon.match.any, sinon.match.any, sinon.match.any).resolves(true); // call execute step on the AzureWizardExecuteStep diff --git a/extensions/sql-bindings/src/test/common/azureFunctionsUtils.test.ts b/extensions/sql-bindings/src/test/common/azureFunctionsUtils.test.ts index e461d703d8..105d29588e 100644 --- a/extensions/sql-bindings/src/test/common/azureFunctionsUtils.test.ts +++ b/extensions/sql-bindings/src/test/common/azureFunctionsUtils.test.ts @@ -44,7 +44,7 @@ describe('AzureFunctionUtils', function (): void { "Values": {"test1": "test1", "test2": "test2", "test3":"test3"}}` ); - let writeFileStub = sinon.stub(fs.promises, 'writeFile'); + let writeFileStub = sinon.stub(fs.promises, 'writeFile').resolves(); await azureFunctionsUtils.setLocalAppSetting(path.dirname(localSettingsPath), 'test4', 'test4'); should(writeFileStub.calledWithExactly(localSettingsPath, `{\n "IsEncrypted": false,\n "Values": {\n "test1": "test1",\n "test2": "test2",\n "test3": "test3",\n "test4": "test4"\n }\n}`)).equals(true, 'writeFile should be called with the correct arguments'); }); @@ -77,7 +77,7 @@ describe('AzureFunctionUtils', function (): void { ); const connectionString = 'testConnectionString'; - let writeFileStub = sinon.stub(fs.promises, 'writeFile'); + let writeFileStub = sinon.stub(fs.promises, 'writeFile').resolves(); await azureFunctionsUtils.addConnectionStringToConfig(connectionString, rootFolderPath); should(writeFileStub.calledWithExactly(localSettingsPath, `{\n "IsEncrypted": false,\n "Values": {\n "test1": "test1",\n "test2": "test2",\n "test3": "test3",\n "SqlConnectionString": "testConnectionString"\n }\n}`)).equals(true, 'writeFile should be called with the correct arguments'); }); diff --git a/extensions/sql-bindings/src/test/dialog/addSqlBindingQuickpick.test.ts b/extensions/sql-bindings/src/test/dialog/addSqlBindingQuickpick.test.ts index 7b241776b8..798ce2f527 100644 --- a/extensions/sql-bindings/src/test/dialog/addSqlBindingQuickpick.test.ts +++ b/extensions/sql-bindings/src/test/dialog/addSqlBindingQuickpick.test.ts @@ -71,7 +71,7 @@ describe('Add SQL Binding quick pick', () => { quickpickStub.onCall(3).resolves((constants.connectionProfile) as any); quickpickStub.onCall(4).resolves((constants.yesString) as any); // setLocalAppSetting fails if we dont set writeFile stub - sinon.stub(fs.promises, 'writeFile'); + sinon.stub(fs.promises, 'writeFile').resolves(); sinon.stub(azureFunctionUtils, 'setLocalAppSetting').withArgs(sinon.match.any, 'sqlConnectionString', 'testConnectionString1').resolves((true)); sinon.stub(utils, 'executeCommand').resolves('downloaded nuget package'); quickpickStub.onCall(5).resolves(('testDb') as any); @@ -124,7 +124,7 @@ describe('Add SQL Binding quick pick', () => { quickpickStub.onCall(3).resolves((constants.connectionProfile) as any); quickpickStub.onCall(4).resolves((constants.yesString) as any); // setLocalAppSetting fails if we dont set writeFile stub - sinon.stub(fs.promises, 'writeFile'); + sinon.stub(fs.promises, 'writeFile').resolves(); sinon.stub(azureFunctionUtils, 'setLocalAppSetting').withArgs(sinon.match.any, 'sqlConnectionString', 'testConnectionString2').resolves((true)); sinon.stub(utils, 'executeCommand').resolves('downloaded nuget package'); quickpickStub.onCall(5).resolves(('testDb') as any); diff --git a/extensions/sql-bindings/src/test/service/azureFunctionsService.test.ts b/extensions/sql-bindings/src/test/service/azureFunctionsService.test.ts index 7620a58b8a..e26ffdf9d7 100644 --- a/extensions/sql-bindings/src/test/service/azureFunctionsService.test.ts +++ b/extensions/sql-bindings/src/test/service/azureFunctionsService.test.ts @@ -23,15 +23,10 @@ const rootFolderPath = 'test'; const projectFilePath: string = path.join(rootFolderPath, 'test.csproj'); let testUtils: TestUtils; describe('AzureFunctionsService', () => { + beforeEach(function (): void { + testUtils = createTestUtils(); + }); describe('Create Azure Function with SQL Binding', () => { - beforeEach(function (): void { - testUtils = createTestUtils(); - }); - - afterEach(function (): void { - sinon.restore(); - }); - it('Should show info message to install azure functions extension if not installed', async function (): Promise { const infoStub = sinon.stub(vscode.window, 'showInformationMessage').resolves(undefined); await azureFunctionService.createAzureFunction(); @@ -46,8 +41,7 @@ describe('AzureFunctionsService', () => { sinon.stub(azureFunctionUtils, 'getAzureFunctionProject').resolves(projectFilePath); //set azure function project to have one project sinon.stub(utils, 'getVscodeMssqlApi').resolves(testUtils.vscodeMssqlIExtension.object); - let connectionInfo: IConnectionInfo = createTestCredentials();// Mocks promptForConnection - connectionInfo.database = 'testDb'; + let connectionInfo: IConnectionInfo = createTestCredentials();// create test connectionInfo let connectionDetails = { options: connectionInfo }; testUtils.vscodeMssqlIExtension.setup(x => x.getConnectionString(connectionDetails, true, false)).returns(() => Promise.resolve('testConnectionString')); @@ -55,43 +49,45 @@ describe('AzureFunctionsService', () => { const showErrorMessageSpy = sinon.spy(vscode.window, 'showErrorMessage'); // select input or output binding - let quickpickStub = sinon.stub(vscode.window, 'showQuickPick').resolves({ label: constants.input, type: BindingType.input }); + let quickPickStub = sinon.stub(vscode.window, 'showQuickPick').resolves({ label: constants.input, type: BindingType.input }); // no table used for connection info so prompt user to get connection info testUtils.vscodeMssqlIExtension.setup(x => x.promptForConnection(true)).returns(() => Promise.resolve(connectionInfo)); testUtils.vscodeMssqlIExtension.setup(x => x.connect(connectionInfo)).returns(() => Promise.resolve('testConnectionURI')); testUtils.vscodeMssqlIExtension.setup(x => x.listDatabases('testConnectionURI')).returns(() => Promise.resolve(['testDb'])); // select the testDB from list of databases based on connection info - quickpickStub.onSecondCall().resolves(('testDb') as any); + quickPickStub.onSecondCall().resolves(('testDb') as any); // get tables from selected database 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]']] })); + .returns(() => Promise.resolve({ rowCount: 1, columnInfo: [], rows: [[{ displayValue: '[schema].[testTable]' }]] })); // select the schema.testTable from list of tables based on connection info and database - quickpickStub.onThirdCall().resolves(('[schema].[testTable]') as any); + quickPickStub.onThirdCall().resolves(('[schema].[testTable]') as any); // set azure function name let inputStub = sinon.stub(vscode.window, 'showInputBox').resolves('testFunctionName'); // promptAndUpdateConnectionStringSetting - quickpickStub.onCall(3).resolves({ label: constants.createNewLocalAppSettingWithIcon }); + quickPickStub.onCall(3).resolves({ label: constants.createNewLocalAppSettingWithIcon }); inputStub.onSecondCall().resolves('SqlConnectionString'); // promptConnectionStringPasswordAndUpdateConnectionString - tested in AzureFunctionUtils.test.ts - quickpickStub.onCall(4).resolves((constants.yesString) as any); + quickPickStub.onCall(4).resolves((constants.yesString) as any); testUtils.vscodeMssqlIExtension.setup(x => x.getConnectionString(connectionDetails, true, false)).returns(() => Promise.resolve('testConnectionString')); // setLocalAppSetting with connection string setting name and connection string // fails if we dont set writeFile stub - sinon.stub(fs.promises, 'writeFile'); + sinon.stub(fs.promises, 'writeFile').resolves(); sinon.stub(azureFunctionUtils, 'setLocalAppSetting').withArgs(sinon.match.any, 'SqlConnectionString', 'testConnectionString').resolves((true)); sinon.stub(utils, 'executeCommand').resolves('downloaded nuget package'); const testWatcher = TypeMoq.Mock.ofType().object; sinon.stub(azureFunctionUtils, 'waitForNewFunctionFile').withArgs(sinon.match.any).returns({ filePromise: Promise.resolve('TestFileCreated'), watcherDisposable: testWatcher }); + + should(connectionInfo.database).equal('my_db', 'ConnectionInfo database should not be changed'); await azureFunctionService.createAzureFunction(); should(showErrorMessageSpy.notCalled).be.true('showErrorMessage should not have been called'); // set the connection info to be the one the user selects from list of databases quickpick - should(connectionInfo.database).equal('testDb', 'connectionInfo.database should be testDb after user selects testDb'); + should(connectionInfo.database).equal('testDb', 'connectionInfo.database should be testDb after user selects testDb'); }); it('Should create azure function project using command via the sql server table OE', async function (): Promise { @@ -100,14 +96,14 @@ describe('AzureFunctionsService', () => { sinon.stub(azureFunctionUtils, 'getAzureFunctionProject').resolves(projectFilePath); //set azure function project to have one project sinon.stub(utils, 'getVscodeMssqlApi').resolves(testUtils.vscodeMssqlIExtension.object); - let connectionInfo: IConnectionInfo = createTestCredentials();// Mocks promptForConnection + let connectionInfo: IConnectionInfo = createTestCredentials();// create test connectionInfo let connectionDetails = { options: connectionInfo }; testUtils.vscodeMssqlIExtension.setup(x => x.getConnectionString(connectionDetails, true, false)).returns(() => Promise.resolve('testConnectionString')); const showErrorMessageSpy = sinon.spy(vscode.window, 'showErrorMessage'); // select input or output binding - let quickpickStub = sinon.stub(vscode.window, 'showQuickPick').resolves({ label: constants.input, type: BindingType.input }); + let quickPickStub = sinon.stub(vscode.window, 'showQuickPick').resolves({ label: constants.input, type: BindingType.input }); // table node used when creating azure function project let tableTestNode = createTestTableNode(connectionInfo); @@ -115,25 +111,26 @@ describe('AzureFunctionsService', () => { let inputStub = sinon.stub(vscode.window, 'showInputBox').resolves('testFunctionName'); // promptAndUpdateConnectionStringSetting - quickpickStub.onSecondCall().resolves({ label: constants.createNewLocalAppSettingWithIcon }); + quickPickStub.onSecondCall().resolves({ label: constants.createNewLocalAppSettingWithIcon }); inputStub.onSecondCall().resolves('SqlConnectionString'); // promptConnectionStringPasswordAndUpdateConnectionString - tested in AzureFunctionUtils.test.ts - quickpickStub.onThirdCall().resolves((constants.yesString) as any); + quickPickStub.onThirdCall().resolves((constants.yesString) as any); testUtils.vscodeMssqlIExtension.setup(x => x.getConnectionString(connectionDetails, true, false)).returns(() => Promise.resolve('testConnectionString')); // setLocalAppSetting with connection string setting name and connection string // fails if we dont set writeFile stub - sinon.stub(fs.promises, 'writeFile'); + sinon.stub(fs.promises, 'writeFile').resolves(); sinon.stub(azureFunctionUtils, 'setLocalAppSetting').withArgs(sinon.match.any, 'SqlConnectionString', 'testConnectionString').resolves((true)); sinon.stub(utils, 'executeCommand').resolves('downloaded nuget package'); const testWatcher = TypeMoq.Mock.ofType().object; sinon.stub(azureFunctionUtils, 'waitForNewFunctionFile').withArgs(sinon.match.any).returns({ filePromise: Promise.resolve('TestFileCreated'), watcherDisposable: testWatcher }); + should(connectionInfo.database).equal('my_db', 'ConnectionInfo database should not be changed'); await azureFunctionService.createAzureFunction(tableTestNode); should(showErrorMessageSpy.notCalled).be.true('showErrorMessage should not have been called'); // set the connection info to be the one used from the test table node from OE - should(connectionInfo.database).equal('testDb','connectionInfo.database should be testDb after user selects testDb'); + should(connectionInfo.database).equal('testDb', 'connectionInfo.database should be testDb after user selects testDb'); }); it('Should open link to learn more about SQL bindings when no azure function project found in folder or workspace', async function (): Promise { @@ -141,7 +138,7 @@ describe('AzureFunctionsService', () => { sinon.stub(azureFunctionUtils, 'getAzureFunctionsExtensionApi').resolves(testUtils.azureFunctionsExtensionApi.object); // set azure functions extension api sinon.stub(utils, 'getVscodeMssqlApi').resolves(testUtils.vscodeMssqlIExtension.object); - let connectionInfo: IConnectionInfo = createTestCredentials();// Mocks promptForConnection + let connectionInfo: IConnectionInfo = createTestCredentials();// create test connectionInfo let connectionDetails = { options: connectionInfo }; testUtils.vscodeMssqlIExtension.setup(x => x.getConnectionString(connectionDetails, true, false)).returns(() => Promise.resolve('testConnectionString')); @@ -158,7 +155,7 @@ describe('AzureFunctionsService', () => { sinon.stub(azureFunctionUtils, 'getAzureFunctionsExtensionApi').resolves(testUtils.azureFunctionsExtensionApi.object); // set azure functions extension api sinon.stub(utils, 'getVscodeMssqlApi').resolves(testUtils.vscodeMssqlIExtension.object); - let connectionInfo: IConnectionInfo = createTestCredentials();// Mocks promptForConnection + let connectionInfo: IConnectionInfo = createTestCredentials();// create test connectionInfo let connectionDetails = { options: connectionInfo }; testUtils.vscodeMssqlIExtension.setup(x => x.getConnectionString(connectionDetails, true, false)).returns(() => Promise.resolve('testConnectionString')); @@ -166,12 +163,12 @@ describe('AzureFunctionsService', () => { const showErrorStub = sinon.stub(vscode.window, 'showErrorMessage').resolves((constants.createProject) as any); // user chooses to browse for folder - let quickpickStub = sinon.stub(vscode.window, 'showQuickPick').resolves((constants.browseEllipsisWithIcon) as any); + let quickPickStub = sinon.stub(vscode.window, 'showQuickPick').resolves((constants.browseEllipsisWithIcon) as any); // stub out folder to be chosen (showOpenDialog) sinon.stub(vscode.window, 'showOpenDialog').withArgs(sinon.match.any).resolves([vscode.Uri.file(projectFilePath)]); // select input or output binding - quickpickStub.onSecondCall().resolves({ label: constants.input, type: BindingType.input }); + quickPickStub.onSecondCall().resolves({ label: constants.input, type: BindingType.input }); // table node used when creating azure function project let tableTestNode = createTestTableNode(connectionInfo); @@ -179,23 +176,137 @@ describe('AzureFunctionsService', () => { let inputStub = sinon.stub(vscode.window, 'showInputBox').resolves('testFunctionName'); // promptAndUpdateConnectionStringSetting - quickpickStub.onThirdCall().resolves({ label: constants.createNewLocalAppSettingWithIcon }); + quickPickStub.onThirdCall().resolves({ label: constants.createNewLocalAppSettingWithIcon }); inputStub.onSecondCall().resolves('SqlConnectionString'); // promptConnectionStringPasswordAndUpdateConnectionString - tested in AzureFunctionUtils.test.ts - quickpickStub.onCall(3).resolves((constants.yesString) as any); + quickPickStub.onCall(3).resolves((constants.yesString) as any); testUtils.vscodeMssqlIExtension.setup(x => x.getConnectionString(connectionDetails, true, false)).returns(() => Promise.resolve('testConnectionString')); // setLocalAppSetting with connection string setting name and connection string // fails if we dont set writeFile stub - sinon.stub(fs.promises, 'writeFile'); + sinon.stub(fs.promises, 'writeFile').resolves(); sinon.stub(azureFunctionUtils, 'setLocalAppSetting').withArgs(sinon.match.any, 'SqlConnectionString', 'testConnectionString').resolves((true)); sinon.stub(utils, 'executeCommand').resolves('downloaded nuget package'); const testWatcher = TypeMoq.Mock.ofType().object; sinon.stub(azureFunctionUtils, 'waitForNewFunctionFile').withArgs(sinon.match.any).returns({ filePromise: Promise.resolve('TestFileCreated'), watcherDisposable: testWatcher }); + should(connectionInfo.database).equal('my_db', 'ConnectionInfo database should not be changed'); await azureFunctionService.createAzureFunction(tableTestNode); should(showErrorStub.calledOnce).be.true('showErrorMessage should have been called'); }); }); + + describe('Cancel/Error scenarios for Azure Function with SQL Binding ', function (): void { + let quickPickStub: sinon.SinonStub; + beforeEach(function (): void { + sinon.stub(azureFunctionUtils, 'getAzureFunctionsExtensionApi').resolves(testUtils.azureFunctionsExtensionApi.object); // set azure functions extension api + sinon.stub(azureFunctionUtils, 'getAzureFunctionProject').resolves(projectFilePath); //set azure function project to have one project + sinon.stub(utils, 'getVscodeMssqlApi').resolves(testUtils.vscodeMssqlIExtension.object); + + // select input or output binding + quickPickStub = sinon.stub(vscode.window, 'showQuickPick').resolves({ label: constants.input, type: BindingType.input }); + }); + it('Should prompt connection profile when user cancels selecting database', async function (): Promise { + // This test will have an azure function project already in the project and the azure functions extension installed (stubbed) + let connectionInfo: IConnectionInfo = createTestCredentials();// create test connectionInfo + + // promptForConnection is selected first time for user and then set undefined in order to exit out of the createFunction + let promptForConnectionStub = sinon.stub(testUtils.vscodeMssqlIExtension.object, 'promptForConnection').withArgs(true).onFirstCall().resolves(connectionInfo); + promptForConnectionStub.onSecondCall().resolves(undefined); + // required calls to get databases list for setting up promptForDatabase + testUtils.vscodeMssqlIExtension.setup(x => x.connect(connectionInfo)).returns(() => Promise.resolve('testConnectionURI')); + testUtils.vscodeMssqlIExtension.setup(x => x.listDatabases('testConnectionURI')).returns(() => Promise.resolve(['testDb'])); + // cancel out of promptForDatabase - select database to use + quickPickStub.onSecondCall().resolves(undefined); + + await azureFunctionService.createAzureFunction(); + + // promptForConnection should be prompted twice since the user cancels the quickpick to select database + should(promptForConnectionStub.callCount).equal(2, 'promptForConnection should have been called 2 times only'); + }); + + it('Should prompt connection profile when user cancels selecting table', async function (): Promise { + // This test will re-prompt the user to choose connection profile + let connectionInfo: IConnectionInfo = createTestCredentials();// create test connectionInfo + + // promptForConnection is selected first time for user + let promptForConnectionStub = sinon.stub(testUtils.vscodeMssqlIExtension.object, 'promptForConnection').withArgs(true).onFirstCall().resolves(connectionInfo); + // required calls to get databases list for setting up promptForDatabase + testUtils.vscodeMssqlIExtension.setup(x => x.connect(connectionInfo)).returns(() => Promise.resolve('testConnectionURI')); + testUtils.vscodeMssqlIExtension.setup(x => x.listDatabases('testConnectionURI')).returns(() => Promise.resolve(['testDb'])); + // select the testDB for promptForDatabase + quickPickStub.onSecondCall().resolves(('testDb') as any); + + // get tables from selected database + 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: [[{ displayValue: '[schema].[testTable]' }]] })); + + // cancel out of promptForTables - select table to use + quickPickStub.onThirdCall().resolves(undefined); + // resolve promises to undefined to exit out of createFunction + promptForConnectionStub.onSecondCall().resolves(undefined); + + await azureFunctionService.createAzureFunction(); + + // promptForConnection should be prompted twice since the user cancels the quickpick to select table + should(promptForConnectionStub.callCount).equal(2, 'promptForConnection should have been called 2 times only'); + }); + + it('Should prompt select table when user cancels out of manually entering table', async function (): Promise { + // This test will have an azure function project already in the project and the azure functions extension installed (stubbed) + let connectionInfo: IConnectionInfo = createTestCredentials();// create test connectionInfo + + // no table used for connection info so prompt user to get connection info + // promptForConnection is set first time for user + let promptForConnectionStub = sinon.stub(testUtils.vscodeMssqlIExtension.object, 'promptForConnection').withArgs(true).onFirstCall().resolves(connectionInfo); + // setup listDatabases request with connectionURI + testUtils.vscodeMssqlIExtension.setup(x => x.connect(connectionInfo)).returns(() => Promise.resolve('testConnectionURI')); + testUtils.vscodeMssqlIExtension.setup(x => x.listDatabases('testConnectionURI')).returns(() => Promise.resolve(['testDb'])); + // select the testDB from list of databases based on connection info + quickPickStub.onSecondCall().resolves(('testDb') as any); + // get tables from selected database + 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: [[{ displayValue: '[schema].[testTable]' }]] })); + // select the option to manually enter table name + let manuallyEnterObjectName = constants.manuallyEnterObjectName(constants.enterObjectName); + quickPickStub.onThirdCall().resolves(manuallyEnterObjectName as any); + // cancel out of manually enter inputBox + sinon.stub(vscode.window, 'showInputBox').resolves(undefined); + // resolve promises to undefined to exit out of createFunction + quickPickStub.onCall(4).resolves(undefined); + promptForConnectionStub.onSecondCall().resolves(undefined); + + should(connectionInfo.database).equal('my_db', 'ConnectionInfo database should not be changed'); + await azureFunctionService.createAzureFunction(); + + should(connectionInfo.database).equal('testDb', 'ConnectionInfo database should be user selected database'); + should(quickPickStub.getCall(3).args).containDeepOrdered([ + [manuallyEnterObjectName, '[schema].[testTable]'], + { + canPickMany: false, + title: constants.selectTable, + ignoreFocusOut: true + }] + ); + }); + + it('Should prompt for connection profile if connection throws connection error', async function (): Promise { + // no table used for connection info so prompt user to get connection info + // promptForConnection is selected first time for user and then set undefined in order to exit out of the createFunction + let promptForConnectionStub = sinon.stub(testUtils.vscodeMssqlIExtension.object, 'promptForConnection').withArgs(true).throws('Error connecting to connection profile'); + promptForConnectionStub.onSecondCall().resolves(undefined); + + await azureFunctionService.createAzureFunction(); + + // re prompt the promptForConnection if the first connection throws an error + should(promptForConnectionStub.callCount).equal(2, 'promptForConnection should have been called 2 times only'); + }); + }); + + afterEach(function (): void { + sinon.restore(); + }); }); diff --git a/extensions/sql-bindings/src/test/testUtils.ts b/extensions/sql-bindings/src/test/testUtils.ts index bfc072d5d5..b2ce6a2ec6 100644 --- a/extensions/sql-bindings/src/test/testUtils.ts +++ b/extensions/sql-bindings/src/test/testUtils.ts @@ -14,7 +14,7 @@ export interface TestUtils { context: vscode.ExtensionContext; dacFxService: TypeMoq.IMock; outputChannel: vscode.OutputChannel; - vscodeMssqlIExtension: TypeMoq.IMock + vscodeMssqlIExtension: TypeMoq.IMock; dacFxMssqlService: TypeMoq.IMock; schemaCompareService: TypeMoq.IMock; azureFunctionsExtensionApi: TypeMoq.IMock;