mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
Adds SQL Binding Create Azure Function with SQL Binding tests (#19538)
* add a couple of azure function service tests * address comments * use mock for azureFunctionExtensionAPI
This commit is contained in:
@@ -458,7 +458,6 @@ export async function promptAndUpdateConnectionStringSetting(projectUri: vscode.
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const listOfConnectionStringMethods = [constants.connectionProfile, constants.userConnectionString];
|
|
||||||
let selectedConnectionStringMethod: string | undefined;
|
let selectedConnectionStringMethod: string | undefined;
|
||||||
let connectionString: string | undefined = '';
|
let connectionString: string | undefined = '';
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -467,6 +466,7 @@ export async function promptAndUpdateConnectionStringSetting(projectUri: vscode.
|
|||||||
const localSettingsPath: string = path.join(projectFolder, constants.azureFunctionLocalSettingsFileName);
|
const localSettingsPath: string = path.join(projectFolder, constants.azureFunctionLocalSettingsFileName);
|
||||||
|
|
||||||
if (!connectionInfo) {
|
if (!connectionInfo) {
|
||||||
|
const listOfConnectionStringMethods = [constants.connectionProfile, constants.userConnectionString];
|
||||||
// show the connection string methods (user input and connection profile options)
|
// show the connection string methods (user input and connection profile options)
|
||||||
selectedConnectionStringMethod = await vscode.window.showQuickPick(listOfConnectionStringMethods, {
|
selectedConnectionStringMethod = await vscode.window.showQuickPick(listOfConnectionStringMethods, {
|
||||||
canPickMany: false,
|
canPickMany: false,
|
||||||
@@ -509,7 +509,6 @@ export async function promptAndUpdateConnectionStringSetting(projectUri: vscode.
|
|||||||
// user cancelled the prompts
|
// user cancelled the prompts
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const success = await setLocalAppSetting(projectFolder, newConnectionStringSettingName, connectionString);
|
const success = await setLocalAppSetting(projectFolder, newConnectionStringSettingName, connectionString);
|
||||||
if (success) {
|
if (success) {
|
||||||
// exit both loops and insert binding
|
// exit both loops and insert binding
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export async function createAzureFunction(node?: ITreeNodeInfo): Promise<void> {
|
|||||||
|
|
||||||
isCreateNewProject = true;
|
isCreateNewProject = true;
|
||||||
telemetryStep = CreateAzureFunctionStep.getSelectedFolder;
|
telemetryStep = CreateAzureFunctionStep.getSelectedFolder;
|
||||||
// user either has not folder open or an empty workspace
|
// user either has no folder open or an empty workspace
|
||||||
// prompt user to choose a folder to create the project in
|
// prompt user to choose a folder to create the project in
|
||||||
const browseProjectLocation = await vscode.window.showQuickPick(
|
const browseProjectLocation = await vscode.window.showQuickPick(
|
||||||
[constants.browseEllipsisWithIcon],
|
[constants.browseEllipsisWithIcon],
|
||||||
@@ -190,6 +190,7 @@ export async function createAzureFunction(node?: ITreeNodeInfo): Promise<void> {
|
|||||||
// prompt for Connection String Setting Name
|
// prompt for Connection String Setting Name
|
||||||
let connectionStringInfo: IConnectionStringInfo | undefined = { connectionStringSettingName: constants.sqlConnectionStringSetting, connectionInfo: connectionInfo };
|
let connectionStringInfo: IConnectionStringInfo | undefined = { connectionStringSettingName: constants.sqlConnectionStringSetting, connectionInfo: connectionInfo };
|
||||||
if (!isCreateNewProject && projectFile) {
|
if (!isCreateNewProject && projectFile) {
|
||||||
|
// if it is not a new project, we can prompt user for connection string setting name and connection string password prompts
|
||||||
telemetryStep = CreateAzureFunctionStep.getConnectionStringSettingName;
|
telemetryStep = CreateAzureFunctionStep.getConnectionStringSettingName;
|
||||||
connectionStringInfo = await azureFunctionsUtils.promptAndUpdateConnectionStringSetting(vscode.Uri.parse(projectFile), connectionInfo);
|
connectionStringInfo = await azureFunctionsUtils.promptAndUpdateConnectionStringSetting(vscode.Uri.parse(projectFile), connectionInfo);
|
||||||
if (!connectionStringInfo) {
|
if (!connectionStringInfo) {
|
||||||
@@ -227,7 +228,6 @@ export async function createAzureFunction(node?: ITreeNodeInfo): Promise<void> {
|
|||||||
// check for the new function file to be created and dispose of the file system watcher
|
// check for the new function file to be created and dispose of the file system watcher
|
||||||
const timeoutForFunctionFile = utils.timeoutPromise(constants.timeoutAzureFunctionFileError);
|
const timeoutForFunctionFile = utils.timeoutPromise(constants.timeoutAzureFunctionFileError);
|
||||||
await Promise.race([newFunctionFileObject.filePromise, timeoutForFunctionFile]);
|
await Promise.race([newFunctionFileObject.filePromise, timeoutForFunctionFile]);
|
||||||
|
|
||||||
telemetryStep = 'finishCreateFunction';
|
telemetryStep = 'finishCreateFunction';
|
||||||
propertyBag.telemetryStep = telemetryStep;
|
propertyBag.telemetryStep = telemetryStep;
|
||||||
exitReason = ExitReason.finishCreate;
|
exitReason = ExitReason.finishCreate;
|
||||||
@@ -254,9 +254,7 @@ export async function createAzureFunction(node?: ITreeNodeInfo): Promise<void> {
|
|||||||
propertyBag.exitReason = exitReason;
|
propertyBag.exitReason = exitReason;
|
||||||
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.exitCreateAzureFunctionQuickpick)
|
TelemetryReporter.createActionEvent(TelemetryViews.CreateAzureFunctionWithSqlBinding, TelemetryActions.exitCreateAzureFunctionQuickpick)
|
||||||
.withAdditionalProperties(propertyBag).send();
|
.withAdditionalProperties(propertyBag).send();
|
||||||
if (newFunctionFileObject) {
|
newFunctionFileObject?.watcherDisposable.dispose();
|
||||||
newFunctionFileObject.watcherDisposable.dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,220 +17,223 @@ const rootFolderPath = 'test';
|
|||||||
const localSettingsPath: string = path.join(rootFolderPath, 'local.settings.json');
|
const localSettingsPath: string = path.join(rootFolderPath, 'local.settings.json');
|
||||||
let testUtils: TestUtils;
|
let testUtils: TestUtils;
|
||||||
|
|
||||||
describe('Tests to verify Azure Functions Utils functions', function (): void {
|
describe('AzureFunctionUtils', function (): void {
|
||||||
beforeEach(function (): void {
|
beforeEach(function (): void {
|
||||||
testUtils = createTestUtils();
|
testUtils = createTestUtils();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should correctly parse local.settings.json', async () => {
|
describe('Local.Settings.Json', function (): void {
|
||||||
sinon.stub(fs, 'existsSync').withArgs(localSettingsPath).returns(true);
|
it('Should correctly parse local.settings.json', async () => {
|
||||||
sinon.stub(fs, 'readFileSync').withArgs(localSettingsPath).returns(
|
sinon.stub(fs, 'existsSync').withArgs(localSettingsPath).returns(true);
|
||||||
`{"IsEncrypted": false,
|
sinon.stub(fs, 'readFileSync').withArgs(localSettingsPath).returns(
|
||||||
|
`{"IsEncrypted": false,
|
||||||
"Values": {"test1": "test1", "test2": "test2", "test3":"test3"}}`
|
"Values": {"test1": "test1", "test2": "test2", "test3":"test3"}}`
|
||||||
);
|
);
|
||||||
let settings = await azureFunctionsUtils.getLocalSettingsJson(localSettingsPath);
|
let settings = await azureFunctionsUtils.getLocalSettingsJson(localSettingsPath);
|
||||||
should(settings.IsEncrypted).equals(false);
|
should(settings.IsEncrypted).equals(false);
|
||||||
should(Object.keys(settings.Values!).length).equals(3);
|
should(Object.keys(settings.Values!).length).equals(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('setLocalAppSetting can update settings.json with new setting value', async () => {
|
it('setLocalAppSetting can update settings.json with new setting value', async () => {
|
||||||
sinon.stub(fs, 'existsSync').withArgs(localSettingsPath).returns(true);
|
sinon.stub(fs, 'existsSync').withArgs(localSettingsPath).returns(true);
|
||||||
sinon.stub(fs, 'readFileSync').withArgs(localSettingsPath).returns(
|
sinon.stub(fs, 'readFileSync').withArgs(localSettingsPath).returns(
|
||||||
`{"IsEncrypted": false,
|
`{"IsEncrypted": false,
|
||||||
"Values": {"test1": "test1", "test2": "test2", "test3":"test3"}}`
|
"Values": {"test1": "test1", "test2": "test2", "test3":"test3"}}`
|
||||||
);
|
);
|
||||||
|
|
||||||
let writeFileStub = sinon.stub(fs.promises, 'writeFile');
|
let writeFileStub = sinon.stub(fs.promises, 'writeFile');
|
||||||
await azureFunctionsUtils.setLocalAppSetting(path.dirname(localSettingsPath), 'test4', 'test4');
|
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);
|
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);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should not overwrite setting if value already exists in local.settings.json', async () => {
|
it('Should not overwrite setting if value already exists in local.settings.json', async () => {
|
||||||
sinon.stub(fs, 'existsSync').withArgs(localSettingsPath).returns(true);
|
sinon.stub(fs, 'existsSync').withArgs(localSettingsPath).returns(true);
|
||||||
sinon.stub(fs, 'readFileSync').withArgs(localSettingsPath).returns(
|
sinon.stub(fs, 'readFileSync').withArgs(localSettingsPath).returns(
|
||||||
`{"IsEncrypted": false,
|
`{"IsEncrypted": false,
|
||||||
"Values": {"test1": "test1", "test2": "test2", "test3":"test3"}}`
|
"Values": {"test1": "test1", "test2": "test2", "test3":"test3"}}`
|
||||||
);
|
);
|
||||||
|
|
||||||
let warningMsg = constants.settingAlreadyExists('test1');
|
let warningMsg = constants.settingAlreadyExists('test1');
|
||||||
const spy = sinon.stub(vscode.window, 'showWarningMessage').resolves({ title: constants.settingAlreadyExists('test1') });
|
const spy = sinon.stub(vscode.window, 'showWarningMessage').resolves({ title: constants.settingAlreadyExists('test1') });
|
||||||
|
|
||||||
await azureFunctionsUtils.setLocalAppSetting(path.dirname(localSettingsPath), 'test1', 'newValue');
|
await azureFunctionsUtils.setLocalAppSetting(path.dirname(localSettingsPath), 'test1', 'newValue');
|
||||||
should(spy.calledOnce).be.true('showWarningMessage should have been called exactly once');
|
should(spy.calledOnce).be.true('showWarningMessage should have been called exactly once');
|
||||||
should(spy.calledWith(warningMsg)).be.true(`showWarningMessage not called with expected message '${warningMsg}' Actual '${spy.getCall(0).args[0]}'`);
|
should(spy.calledWith(warningMsg)).be.true(`showWarningMessage not called with expected message '${warningMsg}' Actual '${spy.getCall(0).args[0]}'`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should get settings file given project file', async () => {
|
it('Should get settings file given project file', async () => {
|
||||||
const settingsFile = await azureFunctionsUtils.getSettingsFile(rootFolderPath);
|
const settingsFile = await azureFunctionsUtils.getSettingsFile(rootFolderPath);
|
||||||
should(settingsFile).equals(localSettingsPath);
|
should(settingsFile).equals(localSettingsPath);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should add connection string to local.settings.json', async () => {
|
it('Should add connection string to local.settings.json', async () => {
|
||||||
sinon.stub(fs, 'existsSync').withArgs(localSettingsPath).returns(true);
|
sinon.stub(fs, 'existsSync').withArgs(localSettingsPath).returns(true);
|
||||||
sinon.stub(fs, 'readFileSync').withArgs(localSettingsPath).returns(
|
sinon.stub(fs, 'readFileSync').withArgs(localSettingsPath).returns(
|
||||||
`{"IsEncrypted": false,
|
`{"IsEncrypted": false,
|
||||||
"Values": {"test1": "test1", "test2": "test2", "test3":"test3"}}`
|
"Values": {"test1": "test1", "test2": "test2", "test3":"test3"}}`
|
||||||
);
|
);
|
||||||
const connectionString = 'testConnectionString';
|
const connectionString = 'testConnectionString';
|
||||||
|
|
||||||
let writeFileStub = sinon.stub(fs.promises, 'writeFile');
|
let writeFileStub = sinon.stub(fs.promises, 'writeFile');
|
||||||
await azureFunctionsUtils.addConnectionStringToConfig(connectionString, rootFolderPath);
|
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);
|
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
|
describe('Password Prompts', function (): void {
|
||||||
it('Should include password if user includes password and connection info contains the password and auth type is SQL', async () => {
|
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);
|
sinon.stub(utils, 'getVscodeMssqlApi').resolves(testUtils.vscodeMssqlIExtension.object);
|
||||||
let connectionInfo: IConnectionInfo = createTestCredentials();// Mocks promptForConnection
|
let connectionInfo: IConnectionInfo = createTestCredentials();// Mocks promptForConnection
|
||||||
let connectionDetails = { options: connectionInfo };
|
let connectionDetails = { options: connectionInfo };
|
||||||
|
|
||||||
// getConnectionString should return a connection string with the password
|
// 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};`));
|
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
|
// Include Password Prompt - Yes to include password
|
||||||
let quickPickStub = sinon.stub(vscode.window, 'showQuickPick').onFirstCall().returns(Promise.resolve(constants.yesString) as any);
|
let quickPickStub = sinon.stub(vscode.window, 'showQuickPick').onFirstCall().returns(Promise.resolve(constants.yesString) as any);
|
||||||
// Manually enter password
|
// Manually enter password
|
||||||
let quickInputSpy = sinon.spy(vscode.window, 'showInputBox');
|
let quickInputSpy = sinon.spy(vscode.window, 'showInputBox');
|
||||||
// show warning window
|
// show warning window
|
||||||
const warningSpy = sinon.spy(vscode.window, 'showWarningMessage');
|
const warningSpy = sinon.spy(vscode.window, 'showWarningMessage');
|
||||||
|
|
||||||
let getConnectionString = await azureFunctionsUtils.promptConnectionStringPasswordAndUpdateConnectionString(connectionInfo, localSettingsPath);
|
let getConnectionString = await azureFunctionsUtils.promptConnectionStringPasswordAndUpdateConnectionString(connectionInfo, localSettingsPath);
|
||||||
|
|
||||||
// manually entered password prompt and warning prompt should not be called
|
// manually entered password prompt and warning prompt should not be called
|
||||||
should(quickPickStub.calledOnce).be.true('showQuickPick should have been called');
|
should(quickPickStub.calledOnce).be.true('showQuickPick should have been called');
|
||||||
should(quickInputSpy.notCalled).be.true('showInputBox 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');
|
should(warningSpy.notCalled).be.true('showWarningMessage should not have been called');
|
||||||
// get connection string result
|
// get connection string result
|
||||||
should(getConnectionString).equals(`Server=${connectionInfo.server};Initial Catalog=${connectionInfo.database};User ID=${connectionInfo.user};Password=${connectionInfo.password};`);
|
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 () => {
|
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);
|
sinon.stub(utils, 'getVscodeMssqlApi').resolves(testUtils.vscodeMssqlIExtension.object);
|
||||||
let connectionInfo: IConnectionInfo = createTestCredentials();// Mocks promptForConnection
|
let connectionInfo: IConnectionInfo = createTestCredentials();// Mocks promptForConnection
|
||||||
let connectionDetails = { options: connectionInfo };
|
let connectionDetails = { options: connectionInfo };
|
||||||
|
|
||||||
// getConnectionString should return a connection string with password placeholder
|
// 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};`));
|
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
|
// Include Password Prompt - NO to include password
|
||||||
let quickPickStub = sinon.stub(vscode.window, 'showQuickPick').onFirstCall().returns(Promise.resolve(constants.noString) as any);
|
let quickPickStub = sinon.stub(vscode.window, 'showQuickPick').onFirstCall().returns(Promise.resolve(constants.noString) as any);
|
||||||
// Manually enter password
|
// Manually enter password
|
||||||
let quickInputSpy = sinon.spy(vscode.window, 'showInputBox');
|
let quickInputSpy = sinon.spy(vscode.window, 'showInputBox');
|
||||||
// show warning window
|
// show warning window
|
||||||
const warningSpy = sinon.spy(vscode.window, 'showWarningMessage');
|
const warningSpy = sinon.spy(vscode.window, 'showWarningMessage');
|
||||||
|
|
||||||
let getConnectionString = await azureFunctionsUtils.promptConnectionStringPasswordAndUpdateConnectionString(connectionInfo, localSettingsPath);
|
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)
|
// 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(quickPickStub.calledOnce).be.true('showQuickPick should have been called');
|
||||||
should(quickInputSpy.notCalled).be.true('showInputBox should not have been called');
|
should(quickInputSpy.notCalled).be.true('showInputBox should not have been called');
|
||||||
should(warningSpy.calledOnce).be.true('showWarningMessage should have been called');
|
should(warningSpy.calledOnce).be.true('showWarningMessage should have been called');
|
||||||
// returned connection string should NOT include password
|
// returned connection string should NOT include password
|
||||||
should(getConnectionString).equals(`Server=${connectionInfo.server};Initial Catalog=${connectionInfo.database};User ID=${connectionInfo.user};Password=${constants.passwordPlaceholder};`);
|
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 () => {
|
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);
|
sinon.stub(utils, 'getVscodeMssqlApi').resolves(testUtils.vscodeMssqlIExtension.object);
|
||||||
let connectionInfo: IConnectionInfo = createTestCredentials();// Mocks promptForConnection
|
let connectionInfo: IConnectionInfo = createTestCredentials();// Mocks promptForConnection
|
||||||
let connectionDetails = { options: connectionInfo };
|
let connectionDetails = { options: connectionInfo };
|
||||||
|
|
||||||
// getConnectionString should return a connection string with password placeholder
|
// 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};`));
|
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
|
// Include Password Prompt - cancels out of include password prompt
|
||||||
let quickPickStub = sinon.stub(vscode.window, 'showQuickPick').onFirstCall().resolves(undefined);
|
let quickPickStub = sinon.stub(vscode.window, 'showQuickPick').onFirstCall().resolves(undefined);
|
||||||
// Manually enter password
|
// Manually enter password
|
||||||
let quickInputSpy = sinon.spy(vscode.window, 'showInputBox');
|
let quickInputSpy = sinon.spy(vscode.window, 'showInputBox');
|
||||||
// show warning window
|
// show warning window
|
||||||
const warningSpy = sinon.spy(vscode.window, 'showWarningMessage');
|
const warningSpy = sinon.spy(vscode.window, 'showWarningMessage');
|
||||||
|
|
||||||
let getConnectionString = await azureFunctionsUtils.promptConnectionStringPasswordAndUpdateConnectionString(connectionInfo, localSettingsPath);
|
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)
|
// 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(quickPickStub.calledOnce).be.true('showQuickPick should have been called');
|
||||||
should(quickInputSpy.notCalled).be.true('showInputBox should not have been called');
|
should(quickInputSpy.notCalled).be.true('showInputBox should not have been called');
|
||||||
should(warningSpy.calledOnce).be.true('showWarningMessage should have been called');
|
should(warningSpy.calledOnce).be.true('showWarningMessage should have been called');
|
||||||
// returned connection string should NOT include password
|
// returned connection string should NOT include password
|
||||||
should(getConnectionString).equals(`Server=${connectionInfo.server};Initial Catalog=${connectionInfo.database};User ID=${connectionInfo.user};Password=${constants.passwordPlaceholder};`);
|
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 () => {
|
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);
|
sinon.stub(utils, 'getVscodeMssqlApi').resolves(testUtils.vscodeMssqlIExtension.object);
|
||||||
let connectionInfo: IConnectionInfo = createTestCredentials();// Mocks promptForConnection
|
let connectionInfo: IConnectionInfo = createTestCredentials();// Mocks promptForConnection
|
||||||
connectionInfo.authenticationType = 'TestAuth'; // auth type is not SQL
|
connectionInfo.authenticationType = 'TestAuth'; // auth type is not SQL
|
||||||
let connectionDetails = { options: connectionInfo };
|
let connectionDetails = { options: connectionInfo };
|
||||||
|
|
||||||
// getConnectionString should return a connection string with password placeholder
|
// 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};`));
|
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
|
// Include password prompt
|
||||||
let quickpickStub = sinon.stub(vscode.window, 'showQuickPick');
|
let quickpickStub = sinon.stub(vscode.window, 'showQuickPick');
|
||||||
// Manually enter password
|
// Manually enter password
|
||||||
let quickInputSpy = sinon.spy(vscode.window, 'showInputBox');
|
let quickInputSpy = sinon.spy(vscode.window, 'showInputBox');
|
||||||
// show warning window
|
// show warning window
|
||||||
const warningSpy = sinon.spy(vscode.window, 'showWarningMessage');
|
const warningSpy = sinon.spy(vscode.window, 'showWarningMessage');
|
||||||
|
|
||||||
let getConnectionString = await azureFunctionsUtils.promptConnectionStringPasswordAndUpdateConnectionString(connectionInfo, localSettingsPath);
|
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 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(quickpickStub.notCalled).be.true('showQuickPick should not have been called');
|
||||||
should(quickInputSpy.notCalled).be.true('showInputBox 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');
|
should(warningSpy.notCalled).be.true('showWarningMessage should not have been called');
|
||||||
// returned connection string should NOT include password
|
// returned connection string should NOT include password
|
||||||
should(getConnectionString).equals(`Server=${connectionInfo.server};Initial Catalog=${connectionInfo.database};User ID=${connectionInfo.user};`);
|
should(getConnectionString).equals(`Server=${connectionInfo.server};Initial Catalog=${connectionInfo.database};User ID=${connectionInfo.user};`);
|
||||||
});
|
});
|
||||||
|
|
||||||
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 () => {
|
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);
|
sinon.stub(utils, 'getVscodeMssqlApi').resolves(testUtils.vscodeMssqlIExtension.object);
|
||||||
let connectionInfo: IConnectionInfo = createTestCredentials();// Mocks promptForConnection
|
let connectionInfo: IConnectionInfo = createTestCredentials();// Mocks promptForConnection
|
||||||
connectionInfo.password = ''; // password is not saved for the connection
|
connectionInfo.password = ''; // password is not saved for the connection
|
||||||
let connectionDetails = { options: connectionInfo };
|
let connectionDetails = { options: connectionInfo };
|
||||||
|
|
||||||
// getConnectionString should return a connection string with password placeholder
|
// 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};`));
|
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
|
// Include password prompt
|
||||||
let quickpickStub = sinon.stub(vscode.window, 'showQuickPick');
|
let quickpickStub = sinon.stub(vscode.window, 'showQuickPick');
|
||||||
// Manually enter password
|
// Manually enter password
|
||||||
let enteredPassword = 'testPassword';
|
let enteredPassword = 'testPassword';
|
||||||
let quickInputSpy = sinon.stub(vscode.window, 'showInputBox').onFirstCall().resolves(enteredPassword);
|
let quickInputSpy = sinon.stub(vscode.window, 'showInputBox').onFirstCall().resolves(enteredPassword);
|
||||||
// show warning window
|
// show warning window
|
||||||
const warningSpy = sinon.spy(vscode.window, 'showWarningMessage');
|
const warningSpy = sinon.spy(vscode.window, 'showWarningMessage');
|
||||||
|
|
||||||
let getConnectionString = await azureFunctionsUtils.promptConnectionStringPasswordAndUpdateConnectionString(connectionInfo, localSettingsPath);
|
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)
|
// 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(quickpickStub.notCalled).be.true('showQuickPick should not have been called');
|
||||||
should(quickInputSpy.calledOnce).be.true('showInputBox should have been called');
|
should(quickInputSpy.calledOnce).be.true('showInputBox should have been called');
|
||||||
should(warningSpy.notCalled).be.true('showWarningMessage should have been called');
|
should(warningSpy.notCalled).be.true('showWarningMessage should have been called');
|
||||||
// returned connection string should have the entered password
|
// returned connection string should have the entered password
|
||||||
should(getConnectionString).equals(`Server=${connectionInfo.server};Initial Catalog=${connectionInfo.database};User ID=${connectionInfo.user};Password=${enteredPassword};`);
|
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 () => {
|
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);
|
sinon.stub(utils, 'getVscodeMssqlApi').resolves(testUtils.vscodeMssqlIExtension.object);
|
||||||
let connectionInfo: IConnectionInfo = createTestCredentials();// Mocks promptForConnection
|
let connectionInfo: IConnectionInfo = createTestCredentials();// Mocks promptForConnection
|
||||||
connectionInfo.password = ''; // password is not saved for the connection
|
connectionInfo.password = ''; // password is not saved for the connection
|
||||||
let connectionDetails = { options: connectionInfo };
|
let connectionDetails = { options: connectionInfo };
|
||||||
|
|
||||||
// getConnectionString should return a connection string with password placeholder
|
// 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};`));
|
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
|
// Include password prompt
|
||||||
let quickpickStub = sinon.stub(vscode.window, 'showQuickPick');
|
let quickpickStub = sinon.stub(vscode.window, 'showQuickPick');
|
||||||
// Manually enter password
|
// Manually enter password
|
||||||
let quickInputSpy = sinon.stub(vscode.window, 'showInputBox').onFirstCall().resolves(undefined);
|
let quickInputSpy = sinon.stub(vscode.window, 'showInputBox').onFirstCall().resolves(undefined);
|
||||||
// show warning window
|
// show warning window
|
||||||
const warningSpy = sinon.spy(vscode.window, 'showWarningMessage');
|
const warningSpy = sinon.spy(vscode.window, 'showWarningMessage');
|
||||||
|
|
||||||
let getConnectionString = await azureFunctionsUtils.promptConnectionStringPasswordAndUpdateConnectionString(connectionInfo, localSettingsPath);
|
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)
|
// 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(quickpickStub.notCalled).be.true('showQuickPick should not have been called');
|
||||||
should(quickInputSpy.calledOnce).be.true('showInputBox should have been called');
|
should(quickInputSpy.calledOnce).be.true('showInputBox should have been called');
|
||||||
should(warningSpy.calledOnce).be.true('showWarningMessage should have been called ');
|
should(warningSpy.calledOnce).be.true('showWarningMessage should have been called ');
|
||||||
// returned connection string should have the entered password
|
// 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};`);
|
should(getConnectionString).equals(`Server=${connectionInfo.server};Initial Catalog=${connectionInfo.database};User ID=${connectionInfo.user};Password=${constants.passwordPlaceholder};`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async function (): Promise<void> {
|
afterEach(async function (): Promise<void> {
|
||||||
|
|||||||
@@ -0,0 +1,201 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as should from 'should';
|
||||||
|
import * as sinon from 'sinon';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as TypeMoq from 'typemoq';
|
||||||
|
import * as utils from '../../common/utils';
|
||||||
|
import * as constants from '../../common/constants';
|
||||||
|
import * as azureFunctionUtils from '../../common/azureFunctionsUtils';
|
||||||
|
import * as azureFunctionsContracts from '../../contracts/azureFunctions/azureFunctionsContracts';
|
||||||
|
import * as azureFunctionService from '../../services/azureFunctionsService';
|
||||||
|
|
||||||
|
import { createTestUtils, TestUtils, createTestCredentials, createTestTableNode } from '../testUtils';
|
||||||
|
import { IConnectionInfo } from 'vscode-mssql';
|
||||||
|
import { BindingType } from 'sql-bindings';
|
||||||
|
|
||||||
|
const rootFolderPath = 'test';
|
||||||
|
const projectFilePath: string = path.join(rootFolderPath, 'test.csproj');
|
||||||
|
let testUtils: TestUtils;
|
||||||
|
describe('AzureFunctionsService', () => {
|
||||||
|
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<void> {
|
||||||
|
const infoStub = sinon.stub(vscode.window, 'showInformationMessage').resolves(undefined);
|
||||||
|
await azureFunctionService.createAzureFunction();
|
||||||
|
should(infoStub.calledOnce).be.true('showInformationMessage should be called once');
|
||||||
|
should(infoStub.args).containEql([constants.azureFunctionsExtensionNotFound,
|
||||||
|
constants.install, constants.learnMore, constants.doNotInstall]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should create azure function project using the command from command palette (no connection info)', async function (): Promise<void> {
|
||||||
|
// This test will have an azure function project already in the project and the azure functions extension installed (stubbed)
|
||||||
|
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);
|
||||||
|
|
||||||
|
let connectionInfo: IConnectionInfo = createTestCredentials();// Mocks promptForConnection
|
||||||
|
connectionInfo.database = 'testDb';
|
||||||
|
|
||||||
|
let connectionDetails = { options: connectionInfo };
|
||||||
|
testUtils.vscodeMssqlIExtension.setup(x => x.getConnectionString(connectionDetails, true, false)).returns(() => Promise.resolve('testConnectionString'));
|
||||||
|
|
||||||
|
const spy = sinon.spy(vscode.window, 'showErrorMessage');
|
||||||
|
|
||||||
|
// select input or output binding
|
||||||
|
let quickpickStub = sinon.stub(vscode.window, 'showQuickPick').resolves(<any>{ 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().returns(Promise.resolve('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]']] }));
|
||||||
|
// select the schema.testTable from list of tables based on connection info and database
|
||||||
|
quickpickStub.onThirdCall().returns(Promise.resolve('[schema].[testTable]') as any);
|
||||||
|
|
||||||
|
// set azure function name
|
||||||
|
let inputStub = sinon.stub(vscode.window, 'showInputBox').resolves('testFunctionName');
|
||||||
|
|
||||||
|
// promptAndUpdateConnectionStringSetting
|
||||||
|
quickpickStub.onCall(3).resolves(<any>{ label: constants.createNewLocalAppSettingWithIcon });
|
||||||
|
inputStub.onSecondCall().resolves('SqlConnectionString');
|
||||||
|
// promptConnectionStringPasswordAndUpdateConnectionString - tested in AzureFunctionUtils.test.ts
|
||||||
|
quickpickStub.onCall(4).returns(Promise.resolve(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(azureFunctionUtils, 'setLocalAppSetting').withArgs(sinon.match.any, 'SqlConnectionString', 'testConnectionString').returns(Promise.resolve(true));
|
||||||
|
sinon.stub(utils, 'executeCommand').resolves('downloaded nuget package');
|
||||||
|
|
||||||
|
const testWatcher = TypeMoq.Mock.ofType<vscode.FileSystemWatcher>().object;
|
||||||
|
sinon.stub(azureFunctionUtils, 'waitForNewFunctionFile').withArgs(sinon.match.any).returns({ filePromise: Promise.resolve('TestFileCreated'), watcherDisposable: testWatcher });
|
||||||
|
await azureFunctionService.createAzureFunction();
|
||||||
|
|
||||||
|
should(spy.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');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should create azure function project using command via the sql server table OE', async function (): Promise<void> {
|
||||||
|
// This test will have an azure function project already in the project and the azure functions extension installed (stubbed)
|
||||||
|
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);
|
||||||
|
|
||||||
|
let connectionInfo: IConnectionInfo = createTestCredentials();// Mocks promptForConnection
|
||||||
|
let connectionDetails = { options: connectionInfo };
|
||||||
|
testUtils.vscodeMssqlIExtension.setup(x => x.getConnectionString(connectionDetails, true, false)).returns(() => Promise.resolve('testConnectionString'));
|
||||||
|
|
||||||
|
const spy = sinon.spy(vscode.window, 'showErrorMessage');
|
||||||
|
|
||||||
|
// select input or output binding
|
||||||
|
let quickpickStub = sinon.stub(vscode.window, 'showQuickPick').resolves(<any>{ label: constants.input, type: BindingType.input });
|
||||||
|
// table node used when creating azure function project
|
||||||
|
let tableTestNode = createTestTableNode(connectionInfo);
|
||||||
|
|
||||||
|
// set azure function name
|
||||||
|
let inputStub = sinon.stub(vscode.window, 'showInputBox').resolves('testFunctionName');
|
||||||
|
|
||||||
|
// promptAndUpdateConnectionStringSetting
|
||||||
|
quickpickStub.onSecondCall().resolves(<any>{ label: constants.createNewLocalAppSettingWithIcon });
|
||||||
|
inputStub.onSecondCall().resolves('SqlConnectionString');
|
||||||
|
// promptConnectionStringPasswordAndUpdateConnectionString - tested in AzureFunctionUtils.test.ts
|
||||||
|
quickpickStub.onThirdCall().returns(Promise.resolve(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(azureFunctionUtils, 'setLocalAppSetting').withArgs(sinon.match.any, 'SqlConnectionString', 'testConnectionString').returns(Promise.resolve(true));
|
||||||
|
sinon.stub(utils, 'executeCommand').resolves('downloaded nuget package');
|
||||||
|
|
||||||
|
const testWatcher = TypeMoq.Mock.ofType<vscode.FileSystemWatcher>().object;
|
||||||
|
sinon.stub(azureFunctionUtils, 'waitForNewFunctionFile').withArgs(sinon.match.any).returns({ filePromise: Promise.resolve('TestFileCreated'), watcherDisposable: testWatcher });
|
||||||
|
|
||||||
|
await azureFunctionService.createAzureFunction(tableTestNode);
|
||||||
|
|
||||||
|
should(spy.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');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should open link to learn more about SQL bindings when no azure function project found in folder or workspace', async function (): Promise<void> {
|
||||||
|
// This test will ask user that an azure function project must be opened to create an azure function with sql binding
|
||||||
|
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 connectionDetails = { options: connectionInfo };
|
||||||
|
testUtils.vscodeMssqlIExtension.setup(x => x.getConnectionString(connectionDetails, true, false)).returns(() => Promise.resolve('testConnectionString'));
|
||||||
|
|
||||||
|
const executeCommandSpy = sinon.stub(vscode.commands, 'executeCommand').withArgs(sinon.match.any, sinon.match.any).returns(Promise.resolve());
|
||||||
|
const showErrorStub = sinon.stub(vscode.window, 'showErrorMessage').returns(Promise.resolve(constants.learnMore) as any);
|
||||||
|
await azureFunctionService.createAzureFunction();
|
||||||
|
|
||||||
|
should(executeCommandSpy.calledOnce).be.true('showErrorMessage should have been called');
|
||||||
|
should(showErrorStub.calledOnce).be.true('showErrorMessage should have been called');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should ask the user to choose a folder to use for the azure project and create an azure function with the selected folder', async function (): Promise<void> {
|
||||||
|
// This test will ask user that an azure function project must be opened to create an azure function with sql binding
|
||||||
|
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 connectionDetails = { options: connectionInfo };
|
||||||
|
testUtils.vscodeMssqlIExtension.setup(x => x.getConnectionString(connectionDetails, true, false)).returns(() => Promise.resolve('testConnectionString'));
|
||||||
|
|
||||||
|
// error since no project file found in workspace or folder
|
||||||
|
const showErrorStub = sinon.stub(vscode.window, 'showErrorMessage').returns(Promise.resolve(constants.createProject) as any);
|
||||||
|
|
||||||
|
// user chooses to browse for folder
|
||||||
|
let quickpickStub = sinon.stub(vscode.window, 'showQuickPick').returns(Promise.resolve(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(<any>{ label: constants.input, type: BindingType.input });
|
||||||
|
// table node used when creating azure function project
|
||||||
|
let tableTestNode = createTestTableNode(connectionInfo);
|
||||||
|
|
||||||
|
// set azure function name
|
||||||
|
let inputStub = sinon.stub(vscode.window, 'showInputBox').resolves('testFunctionName');
|
||||||
|
|
||||||
|
// promptAndUpdateConnectionStringSetting
|
||||||
|
quickpickStub.onThirdCall().resolves(<any>{ label: constants.createNewLocalAppSettingWithIcon });
|
||||||
|
inputStub.onSecondCall().resolves('SqlConnectionString');
|
||||||
|
// promptConnectionStringPasswordAndUpdateConnectionString - tested in AzureFunctionUtils.test.ts
|
||||||
|
quickpickStub.onCall(3).returns(Promise.resolve(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(azureFunctionUtils, 'setLocalAppSetting').withArgs(sinon.match.any, 'SqlConnectionString', 'testConnectionString').returns(Promise.resolve(true));
|
||||||
|
sinon.stub(utils, 'executeCommand').resolves('downloaded nuget package');
|
||||||
|
|
||||||
|
const testWatcher = TypeMoq.Mock.ofType<vscode.FileSystemWatcher>().object;
|
||||||
|
sinon.stub(azureFunctionUtils, 'waitForNewFunctionFile').withArgs(sinon.match.any).returns({ filePromise: Promise.resolve('TestFileCreated'), watcherDisposable: testWatcher });
|
||||||
|
|
||||||
|
await azureFunctionService.createAzureFunction(tableTestNode);
|
||||||
|
|
||||||
|
should(showErrorStub.calledOnce).be.true('showErrorMessage should have been called');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -8,6 +8,7 @@ import * as TypeMoq from 'typemoq';
|
|||||||
import * as mssql from 'mssql';
|
import * as mssql from 'mssql';
|
||||||
import * as vscodeMssql from 'vscode-mssql';
|
import * as vscodeMssql from 'vscode-mssql';
|
||||||
import { RequestType } from 'vscode-languageclient';
|
import { RequestType } from 'vscode-languageclient';
|
||||||
|
import { AzureFunctionsExtensionApi } from '../../../types/vscode-azurefunctions.api';
|
||||||
|
|
||||||
export interface TestUtils {
|
export interface TestUtils {
|
||||||
context: vscode.ExtensionContext;
|
context: vscode.ExtensionContext;
|
||||||
@@ -16,6 +17,7 @@ export interface TestUtils {
|
|||||||
vscodeMssqlIExtension: TypeMoq.IMock<vscodeMssql.IExtension>
|
vscodeMssqlIExtension: TypeMoq.IMock<vscodeMssql.IExtension>
|
||||||
dacFxMssqlService: TypeMoq.IMock<vscodeMssql.IDacFxService>;
|
dacFxMssqlService: TypeMoq.IMock<vscodeMssql.IDacFxService>;
|
||||||
schemaCompareService: TypeMoq.IMock<vscodeMssql.ISchemaCompareService>;
|
schemaCompareService: TypeMoq.IMock<vscodeMssql.ISchemaCompareService>;
|
||||||
|
azureFunctionsExtensionApi: TypeMoq.IMock<AzureFunctionsExtensionApi>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MockVscodeMssqlIExtension implements vscodeMssql.IExtension {
|
export class MockVscodeMssqlIExtension implements vscodeMssql.IExtension {
|
||||||
@@ -59,13 +61,17 @@ export class MockVscodeMssqlIExtension implements vscodeMssql.IExtension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createTestUtils(): TestUtils {
|
export function createTestUtils(): TestUtils {
|
||||||
|
// Need to setup then when Promise.resolving a mocked object : https://github.com/florinn/typemoq/issues/66
|
||||||
|
const azureFunctionsExtensionApi = TypeMoq.Mock.ofType<AzureFunctionsExtensionApi>();
|
||||||
|
azureFunctionsExtensionApi.setup((x: any) => x.then).returns(() => undefined);
|
||||||
return {
|
return {
|
||||||
context: TypeMoq.Mock.ofType<vscode.ExtensionContext>().object,
|
context: TypeMoq.Mock.ofType<vscode.ExtensionContext>().object,
|
||||||
dacFxService: TypeMoq.Mock.ofType<mssql.IDacFxService>(),
|
dacFxService: TypeMoq.Mock.ofType<mssql.IDacFxService>(),
|
||||||
vscodeMssqlIExtension: TypeMoq.Mock.ofType(MockVscodeMssqlIExtension),
|
vscodeMssqlIExtension: TypeMoq.Mock.ofType(MockVscodeMssqlIExtension),
|
||||||
dacFxMssqlService: TypeMoq.Mock.ofType<vscodeMssql.IDacFxService>(),
|
dacFxMssqlService: TypeMoq.Mock.ofType<vscodeMssql.IDacFxService>(),
|
||||||
schemaCompareService: TypeMoq.Mock.ofType<vscodeMssql.ISchemaCompareService>(),
|
schemaCompareService: TypeMoq.Mock.ofType<vscodeMssql.ISchemaCompareService>(),
|
||||||
outputChannel: TypeMoq.Mock.ofType<vscode.OutputChannel>().object
|
outputChannel: TypeMoq.Mock.ofType<vscode.OutputChannel>().object,
|
||||||
|
azureFunctionsExtensionApi
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,3 +113,39 @@ export function createTestCredentials(): vscodeMssql.IConnectionInfo {
|
|||||||
};
|
};
|
||||||
return creds;
|
return creds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create SQL server table node used for testing
|
||||||
|
* @param connectionInfo the connection info used for the test case
|
||||||
|
* @returns SQL Server table node
|
||||||
|
*/
|
||||||
|
export function createTestTableNode(connectionInfo: vscodeMssql.IConnectionInfo): vscodeMssql.ITreeNodeInfo {
|
||||||
|
return {
|
||||||
|
connectionInfo: connectionInfo,
|
||||||
|
nodeType: 'Table',
|
||||||
|
metadata: {
|
||||||
|
metadataType: 0,
|
||||||
|
metadataTypeName: 'Table',
|
||||||
|
urn: '',
|
||||||
|
name: 'testTable',
|
||||||
|
schema: 'testSchema',
|
||||||
|
},
|
||||||
|
parentNode: {
|
||||||
|
connectionInfo: connectionInfo,
|
||||||
|
nodeType: 'Folder',
|
||||||
|
metadata: null!,
|
||||||
|
parentNode: {
|
||||||
|
connectionInfo: connectionInfo,
|
||||||
|
nodeType: 'Database',
|
||||||
|
metadata: {
|
||||||
|
metadataType: 0,
|
||||||
|
metadataTypeName: 'Database',
|
||||||
|
urn: '',
|
||||||
|
name: 'testDb',
|
||||||
|
schema: null!,
|
||||||
|
},
|
||||||
|
parentNode: undefined! // set to undefined since we do not need further parent node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user