SQL Bindings refactor password prompting (#19371)

* remove password enter manually - since we prompt user to include it prior

* go back to connection profile prompt

* add manual entry if connection info password is not saved

* add tests that verify all password prompt scenarios

* nit
This commit is contained in:
Vasu Bhog
2022-05-19 10:41:11 -07:00
committed by GitHub
parent dbaa78a737
commit 9cefed840e
4 changed files with 193 additions and 25 deletions

View File

@@ -9,11 +9,18 @@ import * as should from 'should';
import * as sinon from 'sinon';
import * as constants from '../../common/constants';
import * as azureFunctionsUtils from '../../common/azureFunctionsUtils';
import * as utils from '../../common/utils';
import { IConnectionInfo } from 'vscode-mssql';
import { createTestCredentials, createTestUtils, TestUtils } from '../testUtils';
const rootFolderPath = 'test';
const localSettingsPath: string = path.join(rootFolderPath, 'local.settings.json');
let testUtils: TestUtils;
describe('Tests to verify Azure Functions Utils functions', function (): void {
beforeEach(function (): void {
testUtils = createTestUtils();
});
it('Should correctly parse local.settings.json', async () => {
sinon.stub(fs, 'existsSync').withArgs(localSettingsPath).returns(true);
@@ -71,6 +78,161 @@ describe('Tests to verify Azure Functions Utils functions', function (): void {
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);
});
// Password Prompts
it('Should include password if user includes password and connection info contains the password and auth type is SQL', async () => {
sinon.stub(utils, 'getVscodeMssqlApi').resolves(testUtils.vscodeMssqlIExtension.object);
let connectionInfo: IConnectionInfo = createTestCredentials();// Mocks promptForConnection
let connectionDetails = { options: connectionInfo };
// getConnectionString should return a connection string with the password
testUtils.vscodeMssqlIExtension.setup(x => x.getConnectionString(connectionDetails, true, false)).returns(() => Promise.resolve(`Server=${connectionInfo.server};Initial Catalog=${connectionInfo.database};User ID=${connectionInfo.user};Password=${connectionInfo.password};`));
// Include Password Prompt - Yes to include password
let quickPickStub = sinon.stub(vscode.window, 'showQuickPick').onFirstCall().returns(Promise.resolve(constants.yesString) as any);
// Manually enter password
let quickInputSpy = sinon.spy(vscode.window, 'showInputBox');
// show warning window
const warningSpy = sinon.spy(vscode.window, 'showWarningMessage');
let getConnectionString = await azureFunctionsUtils.promptConnectionStringPasswordAndUpdateConnectionString(connectionInfo, localSettingsPath);
// manually entered password prompt and warning prompt should not be called
should(quickPickStub.calledOnce).be.true('showQuickPick should have been called');
should(quickInputSpy.notCalled).be.true('showInputBox should not have been called');
should(warningSpy.notCalled).be.true('showWarningMessage should not have been called');
// get connection string result
should(getConnectionString).equals(`Server=${connectionInfo.server};Initial Catalog=${connectionInfo.database};User ID=${connectionInfo.user};Password=${connectionInfo.password};`);
});
it('Should not include password and show warning if user does not want to include password prompt and connection info contains the password and auth type is SQL', async () => {
sinon.stub(utils, 'getVscodeMssqlApi').resolves(testUtils.vscodeMssqlIExtension.object);
let connectionInfo: IConnectionInfo = createTestCredentials();// Mocks promptForConnection
let connectionDetails = { options: connectionInfo };
// getConnectionString should return a connection string with password placeholder
testUtils.vscodeMssqlIExtension.setup(x => x.getConnectionString(connectionDetails, false, false)).returns(() => Promise.resolve(`Server=${connectionInfo.server};Initial Catalog=${connectionInfo.database};User ID=${connectionInfo.user};Password=${constants.passwordPlaceholder};`));
// Include Password Prompt - NO to include password
let quickPickStub = sinon.stub(vscode.window, 'showQuickPick').onFirstCall().returns(Promise.resolve(constants.noString) as any);
// Manually enter password
let quickInputSpy = sinon.spy(vscode.window, 'showInputBox');
// show warning window
const warningSpy = sinon.spy(vscode.window, 'showWarningMessage');
let getConnectionString = await azureFunctionsUtils.promptConnectionStringPasswordAndUpdateConnectionString(connectionInfo, localSettingsPath);
// warning prompt should be shown and manually entered password prompt should not be called (since user indicated they do not want to include password)
should(quickPickStub.calledOnce).be.true('showQuickPick should have been called');
should(quickInputSpy.notCalled).be.true('showInputBox should not have been called');
should(warningSpy.calledOnce).be.true('showWarningMessage should have been called');
// returned connection string should NOT include password
should(getConnectionString).equals(`Server=${connectionInfo.server};Initial Catalog=${connectionInfo.database};User ID=${connectionInfo.user};Password=${constants.passwordPlaceholder};`);
});
it('Should not include password and show warning if user cancels include password prompt and connection info contains the password and auth type is SQL', async () => {
sinon.stub(utils, 'getVscodeMssqlApi').resolves(testUtils.vscodeMssqlIExtension.object);
let connectionInfo: IConnectionInfo = createTestCredentials();// Mocks promptForConnection
let connectionDetails = { options: connectionInfo };
// getConnectionString should return a connection string with password placeholder
testUtils.vscodeMssqlIExtension.setup(x => x.getConnectionString(connectionDetails, false, false)).returns(() => Promise.resolve(`Server=${connectionInfo.server};Initial Catalog=${connectionInfo.database};User ID=${connectionInfo.user};Password=${constants.passwordPlaceholder};`));
// Include Password Prompt - cancels out of include password prompt
let quickPickStub = sinon.stub(vscode.window, 'showQuickPick').onFirstCall().resolves(undefined);
// Manually enter password
let quickInputSpy = sinon.spy(vscode.window, 'showInputBox');
// show warning window
const warningSpy = sinon.spy(vscode.window, 'showWarningMessage');
let getConnectionString = await azureFunctionsUtils.promptConnectionStringPasswordAndUpdateConnectionString(connectionInfo, localSettingsPath);
// warning prompt should be shown and manually entered password prompt should not be called (since user indicated they do not want to include password)
should(quickPickStub.calledOnce).be.true('showQuickPick should have been called');
should(quickInputSpy.notCalled).be.true('showInputBox should not have been called');
should(warningSpy.calledOnce).be.true('showWarningMessage should have been called');
// returned connection string should NOT include password
should(getConnectionString).equals(`Server=${connectionInfo.server};Initial Catalog=${connectionInfo.database};User ID=${connectionInfo.user};Password=${constants.passwordPlaceholder};`);
});
it('Should return connection string with no password saved when connection auth type is not SQL', async () => {
sinon.stub(utils, 'getVscodeMssqlApi').resolves(testUtils.vscodeMssqlIExtension.object);
let connectionInfo: IConnectionInfo = createTestCredentials();// Mocks promptForConnection
connectionInfo.authenticationType = 'TestAuth'; // auth type is not SQL
let connectionDetails = { options: connectionInfo };
// getConnectionString should return a connection string with password placeholder
testUtils.vscodeMssqlIExtension.setup(x => x.getConnectionString(connectionDetails, false, false)).returns(() => Promise.resolve(`Server=${connectionInfo.server};Initial Catalog=${connectionInfo.database};User ID=${connectionInfo.user};Password=${constants.passwordPlaceholder};`));
// Include password prompt
let quickpickStub = sinon.stub(vscode.window, 'showQuickPick');
// Manually enter password
let quickInputSpy = sinon.spy(vscode.window, 'showInputBox');
// show warning window
const warningSpy = sinon.spy(vscode.window, 'showWarningMessage');
let getConnectionString = await azureFunctionsUtils.promptConnectionStringPasswordAndUpdateConnectionString(connectionInfo, localSettingsPath);
// should not call any of the prompts (include password, manually enter password, or show warning) since connection auth type is not SQL
should(quickpickStub.notCalled).be.true('showQuickPick should not have been called');
should(quickInputSpy.notCalled).be.true('showInputBox should not have been called');
should(warningSpy.notCalled).be.true('showWarningMessage should not have been called');
// returned connection string should NOT include password
should(getConnectionString).equals(`Server=${connectionInfo.server};Initial Catalog=${connectionInfo.database};User ID=${connectionInfo.user};Password=${constants.passwordPlaceholder};`);
});
it('Should ask user to enter password and set password to connection string when connection info does not contain password and auth type is SQL', async () => {
sinon.stub(utils, 'getVscodeMssqlApi').resolves(testUtils.vscodeMssqlIExtension.object);
let connectionInfo: IConnectionInfo = createTestCredentials();// Mocks promptForConnection
connectionInfo.password = ''; // password is not saved for the connection
let connectionDetails = { options: connectionInfo };
// getConnectionString should return a connection string with password placeholder
testUtils.vscodeMssqlIExtension.setup(x => x.getConnectionString(connectionDetails, false, false)).returns(() => Promise.resolve(`Server=${connectionInfo.server};Initial Catalog=${connectionInfo.database};User ID=${connectionInfo.user};Password=${constants.passwordPlaceholder};`));
// Include password prompt
let quickpickStub = sinon.stub(vscode.window, 'showQuickPick');
// Manually enter password
let enteredPassword = 'testPassword';
let quickInputSpy = sinon.stub(vscode.window, 'showInputBox').onFirstCall().resolves(enteredPassword);
// show warning window
const warningSpy = sinon.spy(vscode.window, 'showWarningMessage');
let getConnectionString = await azureFunctionsUtils.promptConnectionStringPasswordAndUpdateConnectionString(connectionInfo, localSettingsPath);
// manually entered password prompt should be called while warning prompt and include password prompt should not be called (since user connection info does not contain password)
should(quickpickStub.notCalled).be.true('showQuickPick should not have been called');
should(quickInputSpy.calledOnce).be.true('showInputBox should have been called');
should(warningSpy.notCalled).be.true('showWarningMessage should have been called');
// returned connection string should have the entered password
should(getConnectionString).equals(`Server=${connectionInfo.server};Initial Catalog=${connectionInfo.database};User ID=${connectionInfo.user};Password=${enteredPassword};`);
});
it('Should ask user to enter password and not include password to connection string since user cancelled prompt when connection info does not contain password and auth type is SQL', async () => {
sinon.stub(utils, 'getVscodeMssqlApi').resolves(testUtils.vscodeMssqlIExtension.object);
let connectionInfo: IConnectionInfo = createTestCredentials();// Mocks promptForConnection
connectionInfo.password = ''; // password is not saved for the connection
let connectionDetails = { options: connectionInfo };
// getConnectionString should return a connection string with password placeholder
testUtils.vscodeMssqlIExtension.setup(x => x.getConnectionString(connectionDetails, false, false)).returns(() => Promise.resolve(`Server=${connectionInfo.server};Initial Catalog=${connectionInfo.database};User ID=${connectionInfo.user};Password=${constants.passwordPlaceholder};`));
// Include password prompt
let quickpickStub = sinon.stub(vscode.window, 'showQuickPick');
// Manually enter password
let quickInputSpy = sinon.stub(vscode.window, 'showInputBox').onFirstCall().resolves(undefined);
// show warning window
const warningSpy = sinon.spy(vscode.window, 'showWarningMessage');
let getConnectionString = await azureFunctionsUtils.promptConnectionStringPasswordAndUpdateConnectionString(connectionInfo, localSettingsPath);
// manually entered password prompt and warning prompt should be shown and include password prompt should not be called (since user cancelled manually enter password prompt)
should(quickpickStub.notCalled).be.true('showQuickPick should not have been called');
should(quickInputSpy.calledOnce).be.true('showInputBox should have been called');
should(warningSpy.calledOnce).be.true('showWarningMessage should have been called ');
// returned connection string should have the entered password
should(getConnectionString).equals(`Server=${connectionInfo.server};Initial Catalog=${connectionInfo.database};User ID=${connectionInfo.user};Password=${constants.passwordPlaceholder};`);
});
afterEach(async function (): Promise<void> {
sinon.restore();
});